mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-11 15:59:03 +00:00
fix(panel): switch between multiple views correctly
update single select instance to avoid conflicting rerender listeners
This commit is contained in:
parent
951b199b54
commit
b910f82acf
@ -50,27 +50,27 @@ function View({ _get }) {
|
|||||||
|
|
||||||
function Switcher({ _get, _set, minWidth, maxWidth }) {
|
function Switcher({ _get, _set, minWidth, maxWidth }) {
|
||||||
const { html, useState } = globalThis.__enhancerApi,
|
const { html, useState } = globalThis.__enhancerApi,
|
||||||
$switcher = html`<div
|
$select = html`<${Select}
|
||||||
class="relative flex items-center grow
|
popupMode="dropdown"
|
||||||
font-medium p-[8.5px] ml-[4px] select-none"
|
class="w-full text-left"
|
||||||
></div>`,
|
maxWidth=${maxWidth - 56}
|
||||||
setView = (view) => _set?.(view);
|
minWidth=${minWidth - 56}
|
||||||
|
...${{ _get, _set }}
|
||||||
|
/>`;
|
||||||
useState(["panelViews"], ([panelViews = []]) => {
|
useState(["panelViews"], ([panelViews = []]) => {
|
||||||
const values = panelViews.map(([{ title, $icon }]) => {
|
const values = panelViews.map(([{ title, $icon }]) => {
|
||||||
// panel switcher internally uses the select island,
|
// panel switcher internally uses the select island,
|
||||||
// which expects an option value rather than a title
|
// which expects an option value rather than a title
|
||||||
return { value: title, $icon };
|
return { value: title, $icon };
|
||||||
});
|
});
|
||||||
$switcher.innerHTML = "";
|
$select.setValues(values);
|
||||||
$switcher.append(html`<${Select}
|
|
||||||
popupMode="dropdown"
|
|
||||||
class="w-full text-left"
|
|
||||||
maxWidth=${maxWidth - 56}
|
|
||||||
minWidth=${minWidth - 56}
|
|
||||||
...${{ _get, _set: setView, values }}
|
|
||||||
/>`);
|
|
||||||
});
|
});
|
||||||
return $switcher;
|
return html`<div
|
||||||
|
class="relative flex items-center grow
|
||||||
|
font-medium p-[8.5px] ml-[4px] select-none"
|
||||||
|
>
|
||||||
|
${$select}
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Panel({
|
function Panel({
|
||||||
|
@ -7,46 +7,40 @@
|
|||||||
import { Popup } from "./Popup.mjs";
|
import { Popup } from "./Popup.mjs";
|
||||||
|
|
||||||
function Option({ $icon = "", value = "", _get, _set }) {
|
function Option({ $icon = "", value = "", _get, _set }) {
|
||||||
const { html, useState } = globalThis.__enhancerApi,
|
const { html, useState } = globalThis.__enhancerApi;
|
||||||
$selected = html`<i class="ml-auto i-check size-[16px]"></i>`,
|
return html`<div
|
||||||
$option = html`<div
|
tabindex="0"
|
||||||
tabindex="0"
|
role="option"
|
||||||
role="option"
|
class="select-none cursor-pointer rounded-[3px]
|
||||||
class="select-none cursor-pointer rounded-[3px]
|
flex items-center w-full h-[28px] px-[12px] leading-[1.2]
|
||||||
flex items-center w-full h-[28px] px-[12px] leading-[1.2]
|
transition duration-[20ms] focus:bg-[color:var(--theme--bg-hover)]"
|
||||||
transition duration-[20ms] focus:bg-[color:var(--theme--bg-hover)]"
|
onmouseover=${(event) => event.target.focus()}
|
||||||
onmouseover=${(event) => event.target.focus()}
|
onclick=${() => _set?.(value)}
|
||||||
onclick=${() => _set?.(value)}
|
onkeydown=${(event) => {
|
||||||
onkeydown=${(event) => {
|
if (["Enter", " "].includes(event.key)) _set?.(value);
|
||||||
// if (["Enter", " "].includes(event.key)) _set?.(value);
|
}}
|
||||||
}}
|
>
|
||||||
|
<div
|
||||||
|
class="mr-[6px] inline-flex items-center gap-[6px]
|
||||||
|
text-[14px] text-ellipsis overflow-hidden"
|
||||||
>
|
>
|
||||||
<div
|
${$icon}<span>${value}</span>
|
||||||
class="mr-[6px] inline-flex items-center gap-[6px]
|
</div>
|
||||||
text-[14px] text-ellipsis overflow-hidden"
|
</div>`;
|
||||||
>
|
|
||||||
${$icon}<span>${value}</span>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
useState(["rerender"], async () => {
|
|
||||||
if ((await _get?.()) === value) {
|
|
||||||
$option.append($selected);
|
|
||||||
} else $selected.remove();
|
|
||||||
});
|
|
||||||
return $option;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Select({
|
function Select({
|
||||||
values,
|
|
||||||
_get,
|
_get,
|
||||||
_set,
|
_set,
|
||||||
_requireReload = true,
|
_requireReload = true,
|
||||||
|
values = [],
|
||||||
popupMode = "left",
|
popupMode = "left",
|
||||||
maxWidth = 256,
|
maxWidth = 256,
|
||||||
minWidth = 48,
|
minWidth = 48,
|
||||||
...props
|
...props
|
||||||
}) {
|
}) {
|
||||||
const { html, extendProps, setState, useState } = globalThis.__enhancerApi,
|
const { html, extendProps, setState, useState } = globalThis.__enhancerApi,
|
||||||
|
$selected = html`<i class="ml-auto i-check size-[16px]"></i>`,
|
||||||
// dir="rtl" overflows to the left during transition
|
// dir="rtl" overflows to the left during transition
|
||||||
$select = html`<div
|
$select = html`<div
|
||||||
dir="rtl"
|
dir="rtl"
|
||||||
@ -57,64 +51,66 @@ function Select({
|
|||||||
cursor-pointer text-[14px] overflow-hidden pr-[28px]
|
cursor-pointer text-[14px] overflow-hidden pr-[28px]
|
||||||
transition duration-[20ms] leading-[28px] pl-[8px]
|
transition duration-[20ms] leading-[28px] pl-[8px]
|
||||||
hover:bg-[color:var(--theme--bg-hover)]"
|
hover:bg-[color:var(--theme--bg-hover)]"
|
||||||
></div>`;
|
></div>`,
|
||||||
|
$popup = html`<div></div>`,
|
||||||
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`<i class="i-${opt.$icon} size-[16px]" />`;
|
|
||||||
} else delete opt.$icon;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...opt,
|
|
||||||
$option: html`<${Option} ...${{ ...opt, _get, _set }} />`,
|
|
||||||
$value: html`<div
|
|
||||||
class="inline-flex text-nowrap items-center gap-[6px]"
|
|
||||||
>
|
|
||||||
<!-- swap icon/value order for correct display when dir="rtl" -->
|
|
||||||
<span>${opt.value}</span>${opt.$icon?.cloneNode(true) ?? ""}
|
|
||||||
</div>`,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
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];
|
|
||||||
},
|
|
||||||
onKeydown = (event) => {
|
onKeydown = (event) => {
|
||||||
// const intercept = () => {
|
const intercept = () => {
|
||||||
// event.preventDefault();
|
event.preventDefault();
|
||||||
// event.stopPropagation();
|
event.stopPropagation();
|
||||||
// };
|
};
|
||||||
// if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
// intercept(setState({ rerender: true }));
|
intercept(setState({ rerender: true }));
|
||||||
// } else if (!options.length) return;
|
} else if (!options.length) return;
|
||||||
// // prettier-ignore
|
// prettier-ignore
|
||||||
// const $next = options.find(({ $option }) => $option === event.target)
|
const $next = options.find(({ $option }) => $option === event.target)
|
||||||
// ?.$option.nextElementSibling ?? options.at(0).$option,
|
?.$option.nextElementSibling ?? options.at(0).$option,
|
||||||
// $prev = options.find(({ $option }) => $option === event.target)
|
$prev = options.find(({ $option }) => $option === event.target)
|
||||||
// ?.$option.previousElementSibling ?? options.at(-1).$option;
|
?.$option.previousElementSibling ?? options.at(-1).$option;
|
||||||
// // overflow to opposite end of list from dir of travel
|
// overflow to opposite end of list from dir of travel
|
||||||
// if (event.key === "ArrowUp") intercept($prev.focus());
|
if (event.key === "ArrowUp") intercept($prev.focus());
|
||||||
// if (event.key === "ArrowDown") intercept($next.focus());
|
if (event.key === "ArrowDown") intercept($next.focus());
|
||||||
// // re-enable natural tab behaviour in notion interface
|
// re-enable natural tab behaviour in notion interface
|
||||||
// if (event.key === "Tab") event.stopPropagation();
|
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`<i class="i-${opt.$icon} size-[16px]" />`;
|
||||||
|
} else delete opt.$icon;
|
||||||
|
}
|
||||||
|
const $icon = opt.$icon?.cloneNode(true);
|
||||||
|
return {
|
||||||
|
...opt,
|
||||||
|
$option: html`<${Option} ...${{ ...opt, _get, _set }} />`,
|
||||||
|
$value: html`<div class="inline-flex text-nowrap items-center gap-[6px]">
|
||||||
|
<!-- swap icon/value order for correct display when dir="rtl" -->
|
||||||
|
<span>${opt.label || opt.value}</span>${$icon ?? ""}
|
||||||
|
</div>`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
$select.setValues = (values) => {
|
||||||
|
options = values.map(valueToOption);
|
||||||
|
$popup.innerHTML = "";
|
||||||
|
$popup.append(...options.map(({ $option }) => $option));
|
||||||
|
};
|
||||||
|
$select.setValues(values);
|
||||||
|
|
||||||
let _initialValue;
|
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 () => {
|
useState(["rerender"], async () => {
|
||||||
if (!options.length) return;
|
if (!options.length) return;
|
||||||
const { value, $value } = await getSelected();
|
const { value, $value, $option } = await getSelected();
|
||||||
$select.innerHTML = "";
|
$select.innerHTML = "";
|
||||||
$select.append($value);
|
$select.append($value);
|
||||||
|
$option.append($selected);
|
||||||
if (_requireReload) {
|
if (_requireReload) {
|
||||||
_initialValue ??= value;
|
_initialValue ??= value;
|
||||||
if (value !== _initialValue) setState({ databaseUpdated: true });
|
if (value !== _initialValue) setState({ databaseUpdated: true });
|
||||||
@ -122,7 +118,7 @@ function Select({
|
|||||||
});
|
});
|
||||||
|
|
||||||
extendProps(props, { class: "notion-enhancer--menu-select relative" });
|
extendProps(props, { class: "notion-enhancer--menu-select relative" });
|
||||||
return html`<div ...${props}>
|
return html`<div ...${props} setValues=${$select.setValues}>
|
||||||
${$select}<${Popup}
|
${$select}<${Popup}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
trigger=${$select}
|
trigger=${$select}
|
||||||
@ -137,7 +133,7 @@ function Select({
|
|||||||
$select.style.width = "";
|
$select.style.width = "";
|
||||||
$select.style.background = "";
|
$select.style.background = "";
|
||||||
}}
|
}}
|
||||||
>${options.map(({ $option }) => $option)}
|
>${$popup}
|
||||||
<//>
|
<//>
|
||||||
<i
|
<i
|
||||||
class="i-chevron-down pointer-events-none
|
class="i-chevron-down pointer-events-none
|
||||||
|
@ -42,6 +42,7 @@ export default async (api, db) => {
|
|||||||
|
|
||||||
let $page, $scroller;
|
let $page, $scroller;
|
||||||
const getHeadings = () => {
|
const getHeadings = () => {
|
||||||
|
if (!$page) return [];
|
||||||
return [...$page.querySelectorAll(headings.join(", "))];
|
return [...$page.querySelectorAll(headings.join(", "))];
|
||||||
},
|
},
|
||||||
getHeadingLevel = ($heading) => {
|
getHeadingLevel = ($heading) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user