From b910f82acfa4b44a6f96b7a28efcd0344c66242a Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sat, 24 Feb 2024 23:36:43 +1100 Subject: [PATCH] fix(panel): switch between multiple views correctly update single select instance to avoid conflicting rerender listeners --- src/core/islands/Panel.mjs | 28 +++--- src/core/menu/islands/Select.mjs | 154 ++++++++++++++--------------- src/extensions/outliner/client.mjs | 1 + 3 files changed, 90 insertions(+), 93 deletions(-) diff --git a/src/core/islands/Panel.mjs b/src/core/islands/Panel.mjs index 7217e19..1149b98 100644 --- a/src/core/islands/Panel.mjs +++ b/src/core/islands/Panel.mjs @@ -50,27 +50,27 @@ function View({ _get }) { function Switcher({ _get, _set, minWidth, maxWidth }) { const { html, useState } = globalThis.__enhancerApi, - $switcher = html`
`, - setView = (view) => _set?.(view); + $select = html`<${Select} + popupMode="dropdown" + class="w-full text-left" + maxWidth=${maxWidth - 56} + minWidth=${minWidth - 56} + ...${{ _get, _set }} + />`; useState(["panelViews"], ([panelViews = []]) => { const values = panelViews.map(([{ title, $icon }]) => { // panel switcher internally uses the select island, // which expects an option value rather than a title return { value: title, $icon }; }); - $switcher.innerHTML = ""; - $switcher.append(html`<${Select} - popupMode="dropdown" - class="w-full text-left" - maxWidth=${maxWidth - 56} - minWidth=${minWidth - 56} - ...${{ _get, _set: setView, values }} - />`); + $select.setValues(values); }); - return $switcher; + return html`
+ ${$select} +
`; } function Panel({ diff --git a/src/core/menu/islands/Select.mjs b/src/core/menu/islands/Select.mjs index 1d753dd..93b6b54 100644 --- a/src/core/menu/islands/Select.mjs +++ b/src/core/menu/islands/Select.mjs @@ -7,46 +7,40 @@ import { Popup } from "./Popup.mjs"; function Option({ $icon = "", value = "", _get, _set }) { - const { html, useState } = globalThis.__enhancerApi, - $selected = html``, - $option = html`
event.target.focus()} - onclick=${() => _set?.(value)} - onkeydown=${(event) => { - // if (["Enter", " "].includes(event.key)) _set?.(value); - }} + const { html, useState } = globalThis.__enhancerApi; + return html`
event.target.focus()} + onclick=${() => _set?.(value)} + onkeydown=${(event) => { + if (["Enter", " "].includes(event.key)) _set?.(value); + }} + > +
-
- ${$icon}${value} -
-
`; - useState(["rerender"], async () => { - if ((await _get?.()) === value) { - $option.append($selected); - } else $selected.remove(); - }); - return $option; + ${$icon}${value} +
+
`; } function Select({ - values, _get, _set, _requireReload = true, + values = [], popupMode = "left", maxWidth = 256, minWidth = 48, ...props }) { const { html, extendProps, setState, useState } = globalThis.__enhancerApi, + $selected = html``, // dir="rtl" overflows to the left during transition $select = html`
`; - - let xyz; - const options = values.map((opt) => { - if (["string", "number"].includes(typeof opt)) opt = { value: opt }; - if (!(opt?.$icon instanceof Element)) { - if (typeof opt?.$icon === "string") { - opt.$icon = html``; - } else delete opt.$icon; - } - return { - ...opt, - $option: html`<${Option} ...${{ ...opt, _get, _set }} />`, - $value: html`
- - ${opt.value}${opt.$icon?.cloneNode(true) ?? ""} -
`, - }; - }), - getSelected = async () => { - const value = (await _get?.()) ?? $select.innerText, - option = options.find((opt) => opt.value === value); - if (!option) { - console.log(1, options, options.length, options === xyz); - _set?.(options[0].value); - } - return option || options[0]; - }, + >`, + $popup = html`
`, onKeydown = (event) => { - // const intercept = () => { - // event.preventDefault(); - // event.stopPropagation(); - // }; - // if (event.key === "Escape") { - // intercept(setState({ rerender: true })); - // } else if (!options.length) return; - // // prettier-ignore - // const $next = options.find(({ $option }) => $option === event.target) - // ?.$option.nextElementSibling ?? options.at(0).$option, - // $prev = options.find(({ $option }) => $option === event.target) - // ?.$option.previousElementSibling ?? options.at(-1).$option; - // // overflow to opposite end of list from dir of travel - // if (event.key === "ArrowUp") intercept($prev.focus()); - // if (event.key === "ArrowDown") intercept($next.focus()); - // // re-enable natural tab behaviour in notion interface - // if (event.key === "Tab") event.stopPropagation(); + const intercept = () => { + event.preventDefault(); + event.stopPropagation(); + }; + if (event.key === "Escape") { + intercept(setState({ rerender: true })); + } else if (!options.length) return; + // prettier-ignore + const $next = options.find(({ $option }) => $option === event.target) + ?.$option.nextElementSibling ?? options.at(0).$option, + $prev = options.find(({ $option }) => $option === event.target) + ?.$option.previousElementSibling ?? options.at(-1).$option; + // overflow to opposite end of list from dir of travel + if (event.key === "ArrowUp") intercept($prev.focus()); + if (event.key === "ArrowDown") intercept($next.focus()); + // re-enable natural tab behaviour in notion interface + if (event.key === "Tab") event.stopPropagation(); }; - xyz = options; - console.log(2, options, options.length, options === xyz); + + let options = []; + const valueToOption = (opt) => { + if (["string", "number"].includes(typeof opt)) opt = { value: opt }; + if (!(opt?.$icon instanceof Element)) { + if (typeof opt?.$icon === "string") { + opt.$icon = html``; + } else delete opt.$icon; + } + const $icon = opt.$icon?.cloneNode(true); + return { + ...opt, + $option: html`<${Option} ...${{ ...opt, _get, _set }} />`, + $value: html`
+ + ${opt.label || opt.value}${$icon ?? ""} +
`, + }; + }; + $select.setValues = (values) => { + options = values.map(valueToOption); + $popup.innerHTML = ""; + $popup.append(...options.map(({ $option }) => $option)); + }; + $select.setValues(values); let _initialValue; + const getSelected = async () => { + const value = (await _get?.()) ?? $select.innerText, + option = options.find((opt) => opt.value === value); + if (!option) _set?.(options[0].value); + return option || options[0]; + }; useState(["rerender"], async () => { if (!options.length) return; - const { value, $value } = await getSelected(); + const { value, $value, $option } = await getSelected(); $select.innerHTML = ""; $select.append($value); + $option.append($selected); if (_requireReload) { _initialValue ??= value; if (value !== _initialValue) setState({ databaseUpdated: true }); @@ -122,7 +118,7 @@ function Select({ }); extendProps(props, { class: "notion-enhancer--menu-select relative" }); - return html`
+ return html`
${$select}<${Popup} tabindex="0" trigger=${$select} @@ -137,7 +133,7 @@ function Select({ $select.style.width = ""; $select.style.background = ""; }} - >${options.map(({ $option }) => $option)} + >${$popup} {