diff options
author | Hsieh Chin Fan <pham@topo.tw> | 2024-10-17 12:40:50 +0800 |
---|---|---|
committer | Hsieh Chin Fan <pham@topo.tw> | 2024-10-17 21:32:49 +0800 |
commit | 4dd0fa87859e6ffa1b230eedd1388098a8ba81a1 (patch) | |
tree | bd1c163a4091c812cfe6b569aeb38c439f97b56c /src/Layout.mjs | |
parent | 5cbe9c55eca1b5b0a264414f9a255ea2d019d6e2 (diff) |
feat: set "sticky" as one of default layouts
* move function addDraggable() into module scope
* showcase in sticky layout is draggable now
* add query paramerter for "use"
* FIX: set initialLayout after observer is set
Diffstat (limited to 'src/Layout.mjs')
-rw-r--r-- | src/Layout.mjs | 179 |
1 files changed, 111 insertions, 68 deletions
diff --git a/src/Layout.mjs b/src/Layout.mjs index 485b513..f79c70e 100644 --- a/src/Layout.mjs +++ b/src/Layout.mjs | |||
@@ -86,6 +86,83 @@ export class SideBySide extends Layout { | |||
86 | } | 86 | } |
87 | 87 | ||
88 | /** | 88 | /** |
89 | * addDraggable. | ||
90 | * | ||
91 | * @param {HTMLElement} element | ||
92 | */ | ||
93 | const addDraggable = (element, { snap, left, top } = {}) => { | ||
94 | element.classList.add('draggable-block') | ||
95 | |||
96 | // Make sure current element always on top | ||
97 | const siblings = Array.from( | ||
98 | element.parentElement?.querySelectorAll(':scope > *') ?? [], | ||
99 | ) | ||
100 | let popTimer = null | ||
101 | const onmouseover = () => { | ||
102 | popTimer = setTimeout(() => { | ||
103 | siblings.forEach(e => e.style.removeProperty('z-index')) | ||
104 | element.style.zIndex = '9001' | ||
105 | }, 200) | ||
106 | } | ||
107 | const onmouseout = () => { | ||
108 | clearTimeout(popTimer) | ||
109 | } | ||
110 | element.addEventListener('mouseover', onmouseover) | ||
111 | element.addEventListener('mouseout', onmouseout) | ||
112 | |||
113 | // Add draggable part | ||
114 | const draggablePart = document.createElement('div') | ||
115 | element.appendChild(draggablePart) | ||
116 | draggablePart.className = 'draggable-part' | ||
117 | draggablePart.innerHTML = '<div class="handle">\u2630</div>' | ||
118 | |||
119 | // Add draggable instance | ||
120 | const draggable = new PlainDraggable(element, { | ||
121 | left, | ||
122 | top, | ||
123 | handle: draggablePart, | ||
124 | snap, | ||
125 | }) | ||
126 | |||
127 | // FIXME use pure CSS to hide utils | ||
128 | const utils = element.querySelector('.utils') | ||
129 | draggable.onDragStart = () => { | ||
130 | if (utils) utils.style.display = 'none' | ||
131 | element.classList.add('drag') | ||
132 | } | ||
133 | |||
134 | draggable.onDragEnd = () => { | ||
135 | if (utils) utils.style = '' | ||
136 | element.classList.remove('drag') | ||
137 | element.style.zIndex = '9000' | ||
138 | } | ||
139 | |||
140 | // Reposition draggable instance when resized | ||
141 | const resizeObserver = new window.ResizeObserver(() => { | ||
142 | draggable?.position() | ||
143 | }) | ||
144 | resizeObserver.observe(element) | ||
145 | |||
146 | // Callback for remove | ||
147 | onRemove(element, () => { | ||
148 | resizeObserver.disconnect() | ||
149 | }) | ||
150 | |||
151 | new window.MutationObserver(() => { | ||
152 | if (!element.classList.contains('draggable-block') && draggable) { | ||
153 | element.removeEventListener('mouseover', onmouseover) | ||
154 | element.removeEventListener('mouseout', onmouseout) | ||
155 | resizeObserver.disconnect() | ||
156 | } | ||
157 | }).observe(element, { | ||
158 | attributes: true, | ||
159 | attributeFilter: ['class'], | ||
160 | }) | ||
161 | |||
162 | return draggable | ||
163 | } | ||
164 | |||
165 | /** | ||
89 | * Overlay Layout, Showcase occupies viewport, and HTML content becomes draggable blocks | 166 | * Overlay Layout, Showcase occupies viewport, and HTML content becomes draggable blocks |
90 | * | 167 | * |
91 | * @extends {Layout} | 168 | * @extends {Layout} |
@@ -103,70 +180,6 @@ export class Overlay extends Layout { | |||
103 | } | 180 | } |
104 | 181 | ||
105 | /** | 182 | /** |
106 | * addDraggable. | ||
107 | * | ||
108 | * @param {HTMLElement} element | ||
109 | */ | ||
110 | addDraggable = element => { | ||
111 | // Make sure current element always on top | ||
112 | const siblings = Array.from( | ||
113 | element.parentElement?.querySelectorAll(':scope > *') ?? [], | ||
114 | ) | ||
115 | let popTimer = null | ||
116 | element.onmouseover = () => { | ||
117 | popTimer = setTimeout(() => { | ||
118 | siblings.forEach(e => e.style.removeProperty('z-index')) | ||
119 | element.style.zIndex = '9001' | ||
120 | }, 200) | ||
121 | } | ||
122 | element.onmouseout = () => { | ||
123 | clearTimeout(popTimer) | ||
124 | } | ||
125 | |||
126 | // Add draggable part | ||
127 | const draggablePart = document.createElement('div') | ||
128 | element.appendChild(draggablePart) | ||
129 | draggablePart.className = 'draggable-part' | ||
130 | draggablePart.innerHTML = '<div class="handle">\u2630</div>' | ||
131 | |||
132 | // Add draggable instance | ||
133 | const { left, top } = element.getBoundingClientRect() | ||
134 | const draggable = new PlainDraggable(element, { | ||
135 | top, | ||
136 | left, | ||
137 | handle: draggablePart, | ||
138 | snap: { x: { step: 20 }, y: { step: 20 } }, | ||
139 | }) | ||
140 | |||
141 | // FIXME use pure CSS to hide utils | ||
142 | const utils = element.querySelector('.utils') | ||
143 | draggable.onDragStart = () => { | ||
144 | utils.style.display = 'none' | ||
145 | element.classList.add('drag') | ||
146 | } | ||
147 | |||
148 | draggable.onDragEnd = () => { | ||
149 | utils.style = '' | ||
150 | element.classList.remove('drag') | ||
151 | element.style.zIndex = '9000' | ||
152 | } | ||
153 | |||
154 | // Reposition draggable instance when resized | ||
155 | new window.ResizeObserver(() => { | ||
156 | try { | ||
157 | draggable.position() | ||
158 | } catch (err) { | ||
159 | console.warn(err) | ||
160 | } | ||
161 | }).observe(element) | ||
162 | |||
163 | // Callback for remove | ||
164 | onRemove(element, () => { | ||
165 | draggable.remove() | ||
166 | }) | ||
167 | } | ||
168 | |||
169 | /** | ||
170 | * enterHandler. | 183 | * enterHandler. |
171 | * | 184 | * |
172 | * @param {HTMLElement} options.hemlHolder - Parent element for block | 185 | * @param {HTMLElement} options.hemlHolder - Parent element for block |
@@ -182,7 +195,7 @@ export class Overlay extends Layout { | |||
182 | } | 195 | } |
183 | 196 | ||
184 | // Create draggable blocks and set each position by previous one | 197 | // Create draggable blocks and set each position by previous one |
185 | let [left, top] = [20, 20] | 198 | const [left, top] = [20, 20] |
186 | blocks.forEach(block => { | 199 | blocks.forEach(block => { |
187 | const originLeft = Number(block.dataset.left) | 200 | const originLeft = Number(block.dataset.left) |
188 | const originTop = Number(block.dataset.top) | 201 | const originTop = Number(block.dataset.top) |
@@ -210,8 +223,8 @@ export class Overlay extends Layout { | |||
210 | wrapper.style.left = left + 'px' | 223 | wrapper.style.left = left + 'px' |
211 | wrapper.style.top = top + 'px' | 224 | wrapper.style.top = top + 'px' |
212 | htmlHolder.appendChild(wrapper) | 225 | htmlHolder.appendChild(wrapper) |
213 | const { width } = wrapper.getBoundingClientRect() | 226 | const rect = wrapper.getBoundingClientRect() |
214 | left += width + 30 | 227 | left += rect.width + 30 |
215 | if (left > window.innerWidth) { | 228 | if (left > window.innerWidth) { |
216 | top += 200 | 229 | top += 200 |
217 | left = left % window.innerWidth | 230 | left = left % window.innerWidth |
@@ -222,7 +235,14 @@ export class Overlay extends Layout { | |||
222 | wrapper, | 235 | wrapper, |
223 | { left: originLeft, top: originTop }, | 236 | { left: originLeft, top: originTop }, |
224 | { resume: true, duration: 300 }, | 237 | { resume: true, duration: 300 }, |
225 | ).finished.finally(() => this.addDraggable(wrapper)) | 238 | ).finished.finally(() => addDraggable(wrapper, { |
239 | left: rect.left, | ||
240 | top: rect.top, | ||
241 | snap: { | ||
242 | x: { step: 20 }, | ||
243 | y: { step: 20 }, | ||
244 | }, | ||
245 | })) | ||
226 | 246 | ||
227 | // Trivial case: | 247 | // Trivial case: |
228 | // This hack make sure utils remains at the same place even when wrapper resized | 248 | // This hack make sure utils remains at the same place even when wrapper resized |
@@ -271,3 +291,26 @@ export class Overlay extends Layout { | |||
271 | blocks.forEach(resumeFromDraggable) | 291 | blocks.forEach(resumeFromDraggable) |
272 | } | 292 | } |
273 | } | 293 | } |
294 | |||
295 | /** | ||
296 | * Sticky Layout, Showcase is draggable and stick to viewport | ||
297 | * | ||
298 | * @extends {Layout} | ||
299 | */ | ||
300 | export class Sticky extends Layout { | ||
301 | draggable = document.createElement('div') | ||
302 | |||
303 | enterHandler = ({ showcase }) => { | ||
304 | showcase.replaceWith(this.draggable) | ||
305 | this.draggable.appendChild(showcase) | ||
306 | this.draggableInstance = addDraggable(this.draggable) | ||
307 | const rect = this.draggable.getBoundingClientRect() | ||
308 | this.draggable.style.cssText = `left: ${window.innerWidth - rect.width - 20}px; top: ${window.innerHeight - rect.height - 20}px;` | ||
309 | } | ||
310 | |||
311 | leaveHandler = ({ showcase }) => { | ||
312 | this.draggableInstance?.remove() | ||
313 | this.draggable.replaceWith(showcase) | ||
314 | this.draggable.querySelectorAll(':scope > :not(.mapclay)').forEach(e => e.remove()) | ||
315 | } | ||
316 | } | ||