diff options
-rw-r--r-- | src/dumbymap.mjs | 70 |
1 files changed, 40 insertions, 30 deletions
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index d18fab8..d7a29e9 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs | |||
@@ -266,9 +266,6 @@ export const generateMaps = async (container, callback) => { | |||
266 | container.appendChild(showcase) | 266 | container.appendChild(showcase) |
267 | showcase.classList.add('Showcase') | 267 | showcase.classList.add('Showcase') |
268 | 268 | ||
269 | const toShowcaseWithThrottle = throttle(animateRectTransition, 300) | ||
270 | const fromShowCaseWithThrottle = throttle(animateRectTransition, 300) | ||
271 | |||
272 | const mapFocusObserver = () => new MutationObserver((mutations) => { | 269 | const mapFocusObserver = () => new MutationObserver((mutations) => { |
273 | const mutation = mutations.at(-1) | 270 | const mutation = mutations.at(-1) |
274 | const target = mutation.target | 271 | const target = mutation.target |
@@ -289,16 +286,14 @@ export const generateMaps = async (container, callback) => { | |||
289 | showcase.appendChild(target) | 286 | showcase.appendChild(target) |
290 | 287 | ||
291 | // Resume rect from Semantic HTML to Showcase, with animation | 288 | // Resume rect from Semantic HTML to Showcase, with animation |
292 | toShowcaseWithThrottle(target, placeholder.getBoundingClientRect(), { | 289 | animateRectTransition(target, placeholder.getBoundingClientRect(), { |
293 | duration: 300, | 290 | duration: 300, |
294 | resume: true | 291 | resume: true |
295 | }) | 292 | }) |
296 | } else if (showcase.contains(target)) { | 293 | } else if (showcase.contains(target)) { |
297 | const placeholder = htmlHolder.querySelector(`[data-placeholder="${target.id}"]`) | 294 | const placeholder = htmlHolder.querySelector(`[data-placeholder="${target.id}"]`) |
298 | if (!placeholder) throw Error(`Cannot fine placeholder for map "${target.id}"`) | 295 | if (!placeholder) throw Error(`Cannot fine placeholder for map "${target.id}"`) |
299 | const animation = fromShowCaseWithThrottle(target, placeholder.getBoundingClientRect(), { | 296 | const animation = animateRectTransition(target, placeholder.getBoundingClientRect(), { duration: 300 }) |
300 | duration: 300 | ||
301 | }) | ||
302 | 297 | ||
303 | const afterAnimation = () => { | 298 | const afterAnimation = () => { |
304 | placeholder.parentElement.replaceChild(target, placeholder) | 299 | placeholder.parentElement.replaceChild(target, placeholder) |
@@ -322,7 +317,40 @@ export const generateMaps = async (container, callback) => { | |||
322 | const layouts = ['none', 'side', 'overlay'] | 317 | const layouts = ['none', 'side', 'overlay'] |
323 | container.setAttribute("data-layout", layouts[0]) | 318 | container.setAttribute("data-layout", layouts[0]) |
324 | 319 | ||
325 | // FIXME Use UI to switch layouts | 320 | const switchToNextLayout = throttle(() => { |
321 | let currentLayout = container.getAttribute('data-layout') | ||
322 | currentLayout = currentLayout ? currentLayout : 'none' | ||
323 | const nextIndex = (layouts.indexOf(currentLayout) + 1) % layouts.length | ||
324 | const nextLayout = layouts[nextIndex] | ||
325 | |||
326 | container.setAttribute("data-layout", nextLayout) | ||
327 | }, 300) | ||
328 | |||
329 | // TODO Use UI to switch layouts | ||
330 | // Focus to next map with throttle | ||
331 | const focusNextMap = throttle((reverse = false) => { | ||
332 | // Decide how many candidates could be focused | ||
333 | const selector = '.map-container, [data-placeholder]' | ||
334 | const candidates = Array.from(htmlHolder.querySelectorAll(selector)) | ||
335 | if (candidates.length <= 1) return | ||
336 | |||
337 | // Get current focused element | ||
338 | const currentFocus = htmlHolder.querySelector('.map-container[data-focus=true]') | ||
339 | ?? htmlHolder.querySelector('[data-placeholder]') | ||
340 | |||
341 | // Remove class name of focus for ALL candidates | ||
342 | // This may trigger animation | ||
343 | Array.from(container.querySelectorAll('.map-container')) | ||
344 | .forEach(ele => ele.removeAttribute('data-focus')); | ||
345 | |||
346 | // Focus next focus element | ||
347 | const nextIndex = currentFocus | ||
348 | ? (candidates.indexOf(currentFocus) + (reverse ? -1 : 1)) % candidates.length | ||
349 | : 0 | ||
350 | const nextFocus = candidates.at(nextIndex) | ||
351 | nextFocus.setAttribute('data-focus', "true") | ||
352 | }, 300) | ||
353 | |||
326 | const originalKeyDown = document.onkeydown | 354 | const originalKeyDown = document.onkeydown |
327 | document.onkeydown = (e) => { | 355 | document.onkeydown = (e) => { |
328 | const event = originalKeyDown(e) | 356 | const event = originalKeyDown(e) |
@@ -330,32 +358,13 @@ export const generateMaps = async (container, callback) => { | |||
330 | 358 | ||
331 | if (event.key === 'x' && container.querySelector('.map-container')) { | 359 | if (event.key === 'x' && container.querySelector('.map-container')) { |
332 | e.preventDefault() | 360 | e.preventDefault() |
333 | let currentLayout = container.getAttribute('data-layout') | 361 | switchToNextLayout() |
334 | currentLayout = currentLayout ? currentLayout : 'none' | ||
335 | const nextIndex = (layouts.indexOf(currentLayout) + 1) % layouts.length | ||
336 | const nextLayout = layouts[nextIndex] | ||
337 | |||
338 | container.setAttribute("data-layout", nextLayout) | ||
339 | } | 362 | } |
340 | 363 | ||
341 | // Use Tab to change focus map | 364 | // Use Tab to change focus map |
342 | if (event.key === 'Tab') { | 365 | if (event.key === 'Tab') { |
343 | e.preventDefault() | 366 | e.preventDefault() |
344 | 367 | focusNextMap(event.shiftkey) | |
345 | const selector = '.map-container, [data-placeholder]' | ||
346 | const candidates = Array.from(htmlHolder.querySelectorAll(selector)) | ||
347 | if (candidates.length <= 1) return | ||
348 | |||
349 | const currentFocus = htmlHolder.querySelector('.map-container[data-focus=true]') | ||
350 | ?? htmlHolder.querySelector('[data-placeholder]') | ||
351 | Array.from(container.querySelectorAll('.map-container')).forEach(e => | ||
352 | e.removeAttribute('data-focus') | ||
353 | ); | ||
354 | const index = currentFocus | ||
355 | ? (candidates.indexOf(currentFocus) + (event.shiftKey ? -1 : 1)) % candidates.length | ||
356 | : 0 | ||
357 | const nextFocus = candidates.at(index) | ||
358 | nextFocus.setAttribute('data-focus', "true") | ||
359 | } | 368 | } |
360 | } | 369 | } |
361 | 370 | ||
@@ -402,7 +411,8 @@ export const generateMaps = async (container, callback) => { | |||
402 | layoutObserver.observe(container, { | 411 | layoutObserver.observe(container, { |
403 | attributes: true, | 412 | attributes: true, |
404 | attributeFilter: ["data-layout"], | 413 | attributeFilter: ["data-layout"], |
405 | attributeOldValue: true | 414 | attributeOldValue: true, |
415 | characterDataOldValue: true | ||
406 | }); | 416 | }); |
407 | 417 | ||
408 | onRemove(htmlHolder, () => layoutObserver.disconnect()) | 418 | onRemove(htmlHolder, () => layoutObserver.disconnect()) |