aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/Layout.mjs
diff options
context:
space:
mode:
authorHsieh Chin Fan <pham@topo.tw>2024-10-17 12:40:50 +0800
committerHsieh Chin Fan <pham@topo.tw>2024-10-17 21:32:49 +0800
commit4dd0fa87859e6ffa1b230eedd1388098a8ba81a1 (patch)
treebd1c163a4091c812cfe6b569aeb38c439f97b56c /src/Layout.mjs
parent5cbe9c55eca1b5b0a264414f9a255ea2d019d6e2 (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.mjs179
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 */
93const 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 */
300export 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}