@k2b/ui 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/content/Calendar.js +1086 -1049
- package/dist/browser/content/Calendar.js.map +1 -1
- package/dist/browser/content/CalendarDayView.js +1187 -0
- package/dist/browser/content/CalendarDayView.js.map +1 -0
- package/dist/browser/content/CalendarWeekView.js +1571 -0
- package/dist/browser/content/CalendarWeekView.js.map +1 -0
- package/dist/browser/content/ChartExplorer.js +174 -82
- package/dist/browser/content/ChartExplorer.js.map +1 -1
- package/dist/browser/content/ChartFilterControls.js +90 -33
- package/dist/browser/content/ChartFilterControls.js.map +1 -1
- package/dist/browser/content/DataTable.js +199 -171
- package/dist/browser/content/DataTable.js.map +1 -1
- package/dist/browser/content/calendar-day-scroll.js +98 -0
- package/dist/browser/content/calendar-day-scroll.js.map +1 -0
- package/dist/browser/content/calendar-days.js +408 -0
- package/dist/browser/content/calendar-days.js.map +1 -0
- package/dist/browser/content/calendar-dialogs.js +236 -0
- package/dist/browser/content/calendar-dialogs.js.map +1 -0
- package/dist/browser/content/calendar-messages.js +210 -2
- package/dist/browser/content/calendar-messages.js.map +1 -1
- package/dist/browser/content/calendar-pointer.js +15 -3
- package/dist/browser/content/calendar-pointer.js.map +1 -1
- package/dist/browser/content/calendar-shared.js +328 -0
- package/dist/browser/content/calendar-shared.js.map +1 -0
- package/dist/browser/content/calendar-time-view.js +1649 -0
- package/dist/browser/content/calendar-time-view.js.map +1 -0
- package/dist/browser/content/chart-messages.js +10 -0
- package/dist/browser/content/chart-messages.js.map +1 -1
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/inputs/Combobox.js +0 -1
- package/dist/browser/inputs/Combobox.js.map +1 -1
- package/dist/browser/layout/AppWorkspace.js +123 -97
- package/dist/browser/layout/AppWorkspace.js.map +1 -1
- package/dist/browser/layout/PageTitle.js +4 -1
- package/dist/browser/layout/PageTitle.js.map +1 -1
- package/dist/browser/layout/app-workspace-controller.js +164 -86
- package/dist/browser/layout/app-workspace-controller.js.map +1 -1
- package/dist/browser/surfaces/StatCell.js +18 -6
- package/dist/browser/surfaces/StatCell.js.map +1 -1
- package/dist/ssr/content/Calendar.js +524 -335
- package/dist/ssr/content/Calendar.js.map +1 -1
- package/dist/ssr/content/CalendarDayView.js +800 -0
- package/dist/ssr/content/CalendarDayView.js.map +1 -0
- package/dist/ssr/content/CalendarWeekView.js +1069 -0
- package/dist/ssr/content/CalendarWeekView.js.map +1 -0
- package/dist/ssr/content/ChartExplorer.js +93 -34
- package/dist/ssr/content/ChartExplorer.js.map +1 -1
- package/dist/ssr/content/ChartFilterControls.js +49 -7
- package/dist/ssr/content/ChartFilterControls.js.map +1 -1
- package/dist/ssr/content/DataTable.js +69 -51
- package/dist/ssr/content/DataTable.js.map +1 -1
- package/dist/ssr/content/calendar-day-scroll.js +98 -0
- package/dist/ssr/content/calendar-day-scroll.js.map +1 -0
- package/dist/ssr/content/calendar-days.js +408 -0
- package/dist/ssr/content/calendar-days.js.map +1 -0
- package/dist/ssr/content/calendar-dialogs.js +159 -0
- package/dist/ssr/content/calendar-dialogs.js.map +1 -0
- package/dist/ssr/content/calendar-messages.js +210 -2
- package/dist/ssr/content/calendar-messages.js.map +1 -1
- package/dist/ssr/content/calendar-pointer.js +15 -3
- package/dist/ssr/content/calendar-pointer.js.map +1 -1
- package/dist/ssr/content/calendar-shared.js +261 -0
- package/dist/ssr/content/calendar-shared.js.map +1 -0
- package/dist/ssr/content/calendar-time-view.js +1306 -0
- package/dist/ssr/content/calendar-time-view.js.map +1 -0
- package/dist/ssr/content/chart-messages.js +10 -0
- package/dist/ssr/content/chart-messages.js.map +1 -1
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/inputs/Combobox.js.map +1 -1
- package/dist/ssr/layout/AppWorkspace.js +38 -29
- package/dist/ssr/layout/AppWorkspace.js.map +1 -1
- package/dist/ssr/layout/PageTitle.js +4 -1
- package/dist/ssr/layout/PageTitle.js.map +1 -1
- package/dist/ssr/layout/app-workspace-controller.js +164 -86
- package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
- package/dist/ssr/surfaces/StatCell.js +2 -2
- package/dist/ssr/surfaces/StatCell.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/content/Calendar.d.ts +91 -0
- package/dist/types/content/CalendarDayView.d.ts +7 -0
- package/dist/types/content/CalendarWeekView.d.ts +7 -0
- package/dist/types/content/ChartExplorer.d.ts +36 -1
- package/dist/types/content/ChartFilterControls.d.ts +6 -1
- package/dist/types/content/DataTable.d.ts +5 -0
- package/dist/types/content/calendar-day-scroll.d.ts +42 -0
- package/dist/types/content/calendar-days.d.ts +192 -0
- package/dist/types/content/calendar-dialogs.d.ts +22 -0
- package/dist/types/content/calendar-messages.d.ts +99 -0
- package/dist/types/content/calendar-pointer.d.ts +9 -0
- package/dist/types/content/calendar-shared.d.ts +69 -0
- package/dist/types/content/calendar-time-view.d.ts +252 -0
- package/dist/types/content/chart-messages.d.ts +5 -0
- package/dist/types/content/index.d.ts +2 -2
- package/dist/types/layout/AppWorkspace.d.ts +25 -3
- package/dist/types/layout/PageTitle.d.ts +2 -1
- package/dist/types/layout/app-workspace-controller.d.ts +9 -1
- package/dist/types/surfaces/StatCell.d.ts +5 -0
- package/package.json +1 -1
|
@@ -316,7 +316,8 @@ export function PageTitle(props) {
|
|
|
316
316
|
|
|
317
317
|
/**
|
|
318
318
|
* Keeps server-rendered titles of the host's trail current: a workspace head
|
|
319
|
-
* that never hydrates, and an app's own header title from the
|
|
319
|
+
* that never hydrates, its heading, and an app's own header title from the
|
|
320
|
+
* host's trail.
|
|
320
321
|
* When the host publishes a new trail, for example after `setPageTrail()`,
|
|
321
322
|
* each such title is replaced in place by a live one with the same element,
|
|
322
323
|
* class, and subtitle, and a head learns whether it now has a page above it.
|
|
@@ -358,6 +359,8 @@ export const followPageTrail = (root = document) => {
|
|
|
358
359
|
return dispose;
|
|
359
360
|
}));
|
|
360
361
|
}
|
|
362
|
+
// A server-rendered workspace heading carries only the title.
|
|
363
|
+
for (const heading of root.querySelectorAll("[data-k2b-page-trail-title][data-k2b-server]")) if (trail && !heading.closest("solid-island, solid-client")) heading.textContent = trail.title;
|
|
361
364
|
// The row's `data-trail` decides whether it shows; it follows the depth.
|
|
362
365
|
for (const row of root.querySelectorAll("[data-trail]:has(> .k2b-page-title[data-fallback])")) if (!row.closest("solid-island, solid-client")) row.dataset.trail = trail?.ancestors.length ? "up" : "title";
|
|
363
366
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["layout/PageTitle.tsx"],"sourcesContent":["import { createContext, createRoot, createSignal, createUniqueId, For, type JSX, onCleanup, onMount, Show, useContext } from \"solid-js\";\nimport { Dynamic, insert, isServer } from \"solid-js/web\";\nimport { IconButtonLink } from \"../actions/Button\";\nimport { installPageTitleController, PAGE_TITLE_CONTROLLER_ATTRIBUTE } from \"./page-title-controller\";\nimport { useWorkspaceMessages } from \"./workspace-messages\";\n\n/** A page above the current one. Only pages someone can go back to are levels, so each has a link. */\nexport type PageTrailLink = { title: string; href: string };\n/** The current page and the pages above it, nearest last. */\nexport type PageTrailState = { title: string; ancestors: PageTrailLink[] };\n\nexport type PageTitleProps = {\n trail: PageTrailState;\n /** Quiet text after the title, for example a count. */\n subtitle?: JSX.Element;\n /** Defaults to `\"h1\"`. Chrome that repeats a title the page already shows uses `\"p\"`. */\n as?: \"h1\" | \"h2\" | \"p\";\n /**\n * Marks a title drawn by surrounding chrome, such as the workspace head or a\n * host shell. It steps back when the page renders its own `PageTitle`, so a\n * page has one title with one way up.\n */\n fallback?: boolean;\n class?: string;\n};\n\n/** Window event a host dispatches after publishing a new page trail. */\nexport const PAGE_TRAIL_EVENT = \"k2b:page-trail\";\nconst SOURCE_SELECTOR = \"script[data-k2b-page-trail]\";\n\nconst isTrail = (value: unknown): value is PageTrailState => {\n if (!value || typeof value !== \"object\") return false;\n const trail = value as Partial<PageTrailState>;\n return (\n typeof trail.title === \"string\" &&\n Array.isArray(trail.ancestors) &&\n trail.ancestors.every((link) => typeof link?.title === \"string\" && typeof link?.href === \"string\")\n );\n};\n\n/** The trail a host published for this document, or `undefined` outside a browser or without one. */\nexport const readPageTrail = (): PageTrailState | undefined => {\n if (typeof document === \"undefined\") return undefined;\n const source = document.querySelector(SOURCE_SELECTOR);\n if (!source?.textContent) return undefined;\n try {\n const value: unknown = JSON.parse(source.textContent);\n return isTrail(value) ? value : undefined;\n } catch {\n return undefined;\n }\n};\n\n/** `<` is escaped so a title can never end the script element it travels in. */\nconst serializeTrail = (trail: PageTrailState): string => JSON.stringify(trail).replace(/</g, \"\\\\u003c\");\n\n/**\n * Publishes a new trail for the document in the browser, for example after an\n * in-page navigation. Workspace heads and page titles that read the host's\n * trail follow it.\n */\nexport const setPageTrail = (trail: PageTrailState): void => {\n if (typeof document === \"undefined\") return;\n let source = document.querySelector(SOURCE_SELECTOR);\n if (!source) {\n source = document.createElement(\"script\");\n source.setAttribute(\"type\", \"application/json\");\n source.setAttribute(\"data-k2b-page-trail\", \"\");\n document.body.append(source);\n }\n source.textContent = serializeTrail(trail);\n window.dispatchEvent(new Event(PAGE_TRAIL_EVENT));\n};\n\nconst PageTrailContext = createContext<() => PageTrailState | undefined>();\n\n/**\n * Gives a host's page trail to everything below it. During server rendering\n * the context carries it; the provider also publishes it into the document,\n * where browser-rendered parts such as islands read it. Below a provider that\n * renders in the browser, its `trail` prop is the source: update the prop.\n */\nexport function PageTrailProvider(props: { trail: PageTrailState | undefined; children: JSX.Element }): JSX.Element {\n return (\n <PageTrailContext.Provider value={() => props.trail}>\n <Show when={props.trail}>\n {(trail) => <script type=\"application/json\" data-k2b-page-trail=\"\" innerHTML={serializeTrail(trail())} />}\n </Show>\n {props.children}\n </PageTrailContext.Provider>\n );\n}\n\n/** The host's page trail: from the provider while rendering below it, otherwise from the document. */\nexport const usePageTrail = (): (() => PageTrailState | undefined) => {\n const context = useContext(PageTrailContext);\n if (context) return context;\n if (typeof window === \"undefined\") return () => undefined;\n const [trail, setTrail] = createSignal(readPageTrail());\n const update = () => setTrail(readPageTrail());\n window.addEventListener(PAGE_TRAIL_EVENT, update);\n onCleanup(() => window.removeEventListener(PAGE_TRAIL_EVENT, update));\n return trail;\n};\n\n/**\n * A page title with its way up. The number of pages above decides the form:\n * none shows the title alone; one puts \"‹ Parent\" above it; more put a path\n * row above it whose middle collapses into \"…\" only when it does not fit.\n * Both rows share the same height, so the title never moves between depths.\n * On phones a back arrow left of the title replaces the row above.\n */\nexport function PageTitle(props: PageTitleProps): JSX.Element {\n const messages = useWorkspaceMessages();\n // A title rendered on the server from the host's trail follows the host's later trails (see `followPageTrail`).\n const host = isServer ? useContext(PageTrailContext) : undefined;\n const menuId = `k2b-page-title-menu-${createUniqueId()}`;\n const ancestors = () => props.trail.ancestors;\n const parent = () => ancestors().at(-1);\n let root: HTMLDivElement | undefined;\n // A host may control every title in the document at once; a title outside\n // one still collapses its path.\n onMount(() => {\n if (!root || root.closest(`[${PAGE_TITLE_CONTROLLER_ATTRIBUTE}]`)) return;\n onCleanup(installPageTitleController(root));\n });\n return (\n <div\n ref={root}\n class={`k2b-page-title ${props.class ?? \"\"}`}\n data-depth={ancestors().length}\n data-fallback={props.fallback ? \"\" : undefined}\n data-k2b-server={isServer && props.trail === host?.() ? \"\" : undefined}\n >\n <Show when={parent()}>\n {(up) => (\n <IconButtonLink\n class=\"k2b-page-title__back\"\n href={up().href}\n label={messages().backTo({ page: up().title })}\n data-k2b-page-trail-link=\"\"\n data-k2b-page-trail-up=\"\"\n >\n <i class=\"ti ti-chevron-left\" aria-hidden=\"true\" />\n </IconButtonLink>\n )}\n </Show>\n <div class=\"k2b-page-title__copy\">\n <Show when={ancestors().length === 1 && parent()}>\n {(up) => (\n <nav class=\"k2b-page-title__trail\" aria-label={messages().path}>\n <a\n class=\"k2b-page-title__up\"\n href={up().href}\n aria-label={messages().backTo({ page: up().title })}\n data-k2b-page-trail-link=\"\"\n data-k2b-page-trail-up=\"\"\n >\n <i class=\"ti ti-chevron-left\" aria-hidden=\"true\" />\n <span class=\"k2b-page-title__label\">{up().title}</span>\n </a>\n </nav>\n )}\n </Show>\n <Show when={ancestors().length > 1}>\n <nav class=\"k2b-page-title__trail\" aria-label={messages().path}>\n <ol class=\"k2b-page-title__path\">\n <For each={ancestors()}>\n {(link, index) => (\n <>\n <Show when={index() === 1}>\n <li class=\"k2b-page-title__more\" hidden>\n <i class=\"ti ti-chevron-right\" aria-hidden=\"true\" />\n <button type=\"button\" class=\"k2b-page-title__more-button\" popovertarget={menuId} aria-label={messages().moreLevels}>\n …\n </button>\n </li>\n </Show>\n <li class=\"k2b-page-title__crumb\" data-index={index()}>\n <Show when={index() > 0}>\n <i class=\"ti ti-chevron-right\" aria-hidden=\"true\" />\n </Show>\n <a\n href={link.href}\n data-k2b-page-trail-link=\"\"\n data-k2b-page-trail-up={index() === ancestors().length - 1 ? \"\" : undefined}\n class=\"k2b-page-title__label\"\n >\n {link.title}\n </a>\n </li>\n </>\n )}\n </For>\n </ol>\n <div id={menuId} popover=\"auto\" class=\"k2b-dropdown__menu k2b-page-title__menu\">\n <For each={ancestors()}>\n {(link, index) => (\n <a class=\"k2b-dropdown__item\" href={link.href} data-index={index()} data-k2b-page-trail-link=\"\" hidden>\n {link.title}\n </a>\n )}\n </For>\n </div>\n </nav>\n </Show>\n <Dynamic component={props.as ?? \"h1\"} class=\"k2b-page-title__title\">\n <span class=\"k2b-page-title__text\">{props.trail.title}</span>\n <Show when={props.subtitle}>\n <small class=\"k2b-page-title__subtitle\">{props.subtitle}</small>\n </Show>\n </Dynamic>\n </div>\n </div>\n );\n}\n\n/**\n * Keeps server-rendered titles of the host's trail current: a workspace head\n * that never hydrates, and an app's own header title from the host's trail.\n * When the host publishes a new trail, for example after `setPageTrail()`,\n * each such title is replaced in place by a live one with the same element,\n * class, and subtitle, and a head learns whether it now has a page above it.\n * Titles inside islands render in the browser anyway. Returns the disposer.\n */\nexport const followPageTrail = (root: ParentNode = document): (() => void) => {\n const disposers: Array<() => void> = [];\n const upgrade = () => {\n const trail = readPageTrail();\n for (const element of root.querySelectorAll<HTMLElement>(\".k2b-page-title[data-k2b-server]\")) {\n const row = element.parentElement;\n if (!row || element.closest(\"solid-island, solid-client\")) continue;\n const tag = element.querySelector(\".k2b-page-title__title\")?.localName;\n const as = tag === \"p\" || tag === \"h2\" ? tag : \"h1\";\n const fallback = element.hasAttribute(\"data-fallback\");\n const extra = [...element.classList].filter((name) => name !== \"k2b-page-title\").join(\" \") || undefined;\n const subtitle = element.querySelector(\".k2b-page-title__subtitle\");\n const subtitleNodes = subtitle ? [...subtitle.childNodes] : undefined;\n // The live title takes the server title's place, before whatever follows it in the row.\n const place = document.createTextNode(\"\");\n row.insertBefore(place, element);\n element.remove();\n disposers.push(\n createRoot((dispose) => {\n const live = usePageTrail();\n insert(\n row,\n () => (\n <Show when={live()}>\n {(current) => <PageTitle trail={current()} as={as} fallback={fallback} class={extra} subtitle={subtitleNodes} />}\n </Show>\n ),\n place,\n );\n return dispose;\n }),\n );\n }\n // The row's `data-trail` decides whether it shows; it follows the depth.\n for (const row of root.querySelectorAll<HTMLElement>(\"[data-trail]:has(> .k2b-page-title[data-fallback])\"))\n if (!row.closest(\"solid-island, solid-client\")) row.dataset.trail = trail?.ancestors.length ? \"up\" : \"title\";\n };\n window.addEventListener(PAGE_TRAIL_EVENT, upgrade);\n return () => {\n window.removeEventListener(PAGE_TRAIL_EVENT, upgrade);\n for (const dispose of disposers.splice(0)) dispose();\n };\n};\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":[],"sources":["layout/PageTitle.tsx"],"sourcesContent":["import { createContext, createRoot, createSignal, createUniqueId, For, type JSX, onCleanup, onMount, Show, useContext } from \"solid-js\";\nimport { Dynamic, insert, isServer } from \"solid-js/web\";\nimport { IconButtonLink } from \"../actions/Button\";\nimport { installPageTitleController, PAGE_TITLE_CONTROLLER_ATTRIBUTE } from \"./page-title-controller\";\nimport { useWorkspaceMessages } from \"./workspace-messages\";\n\n/** A page above the current one. Only pages someone can go back to are levels, so each has a link. */\nexport type PageTrailLink = { title: string; href: string };\n/** The current page and the pages above it, nearest last. */\nexport type PageTrailState = { title: string; ancestors: PageTrailLink[] };\n\nexport type PageTitleProps = {\n trail: PageTrailState;\n /** Quiet text after the title, for example a count. */\n subtitle?: JSX.Element;\n /** Defaults to `\"h1\"`. Chrome that repeats a title the page already shows uses `\"p\"`. */\n as?: \"h1\" | \"h2\" | \"p\";\n /**\n * Marks a title drawn by surrounding chrome, such as the workspace head or a\n * host shell. It steps back when the page renders its own `PageTitle`, so a\n * page has one title with one way up.\n */\n fallback?: boolean;\n class?: string;\n};\n\n/** Window event a host dispatches after publishing a new page trail. */\nexport const PAGE_TRAIL_EVENT = \"k2b:page-trail\";\nconst SOURCE_SELECTOR = \"script[data-k2b-page-trail]\";\n\nconst isTrail = (value: unknown): value is PageTrailState => {\n if (!value || typeof value !== \"object\") return false;\n const trail = value as Partial<PageTrailState>;\n return (\n typeof trail.title === \"string\" &&\n Array.isArray(trail.ancestors) &&\n trail.ancestors.every((link) => typeof link?.title === \"string\" && typeof link?.href === \"string\")\n );\n};\n\n/** The trail a host published for this document, or `undefined` outside a browser or without one. */\nexport const readPageTrail = (): PageTrailState | undefined => {\n if (typeof document === \"undefined\") return undefined;\n const source = document.querySelector(SOURCE_SELECTOR);\n if (!source?.textContent) return undefined;\n try {\n const value: unknown = JSON.parse(source.textContent);\n return isTrail(value) ? value : undefined;\n } catch {\n return undefined;\n }\n};\n\n/** `<` is escaped so a title can never end the script element it travels in. */\nconst serializeTrail = (trail: PageTrailState): string => JSON.stringify(trail).replace(/</g, \"\\\\u003c\");\n\n/**\n * Publishes a new trail for the document in the browser, for example after an\n * in-page navigation. Workspace heads and page titles that read the host's\n * trail follow it.\n */\nexport const setPageTrail = (trail: PageTrailState): void => {\n if (typeof document === \"undefined\") return;\n let source = document.querySelector(SOURCE_SELECTOR);\n if (!source) {\n source = document.createElement(\"script\");\n source.setAttribute(\"type\", \"application/json\");\n source.setAttribute(\"data-k2b-page-trail\", \"\");\n document.body.append(source);\n }\n source.textContent = serializeTrail(trail);\n window.dispatchEvent(new Event(PAGE_TRAIL_EVENT));\n};\n\nconst PageTrailContext = createContext<() => PageTrailState | undefined>();\n\n/**\n * Gives a host's page trail to everything below it. During server rendering\n * the context carries it; the provider also publishes it into the document,\n * where browser-rendered parts such as islands read it. Below a provider that\n * renders in the browser, its `trail` prop is the source: update the prop.\n */\nexport function PageTrailProvider(props: { trail: PageTrailState | undefined; children: JSX.Element }): JSX.Element {\n return (\n <PageTrailContext.Provider value={() => props.trail}>\n <Show when={props.trail}>\n {(trail) => <script type=\"application/json\" data-k2b-page-trail=\"\" innerHTML={serializeTrail(trail())} />}\n </Show>\n {props.children}\n </PageTrailContext.Provider>\n );\n}\n\n/** The host's page trail: from the provider while rendering below it, otherwise from the document. */\nexport const usePageTrail = (): (() => PageTrailState | undefined) => {\n const context = useContext(PageTrailContext);\n if (context) return context;\n if (typeof window === \"undefined\") return () => undefined;\n const [trail, setTrail] = createSignal(readPageTrail());\n const update = () => setTrail(readPageTrail());\n window.addEventListener(PAGE_TRAIL_EVENT, update);\n onCleanup(() => window.removeEventListener(PAGE_TRAIL_EVENT, update));\n return trail;\n};\n\n/**\n * A page title with its way up. The number of pages above decides the form:\n * none shows the title alone; one puts \"‹ Parent\" above it; more put a path\n * row above it whose middle collapses into \"…\" only when it does not fit.\n * Both rows share the same height, so the title never moves between depths.\n * On phones a back arrow left of the title replaces the row above.\n */\nexport function PageTitle(props: PageTitleProps): JSX.Element {\n const messages = useWorkspaceMessages();\n // A title rendered on the server from the host's trail follows the host's later trails (see `followPageTrail`).\n const host = isServer ? useContext(PageTrailContext) : undefined;\n const menuId = `k2b-page-title-menu-${createUniqueId()}`;\n const ancestors = () => props.trail.ancestors;\n const parent = () => ancestors().at(-1);\n let root: HTMLDivElement | undefined;\n // A host may control every title in the document at once; a title outside\n // one still collapses its path.\n onMount(() => {\n if (!root || root.closest(`[${PAGE_TITLE_CONTROLLER_ATTRIBUTE}]`)) return;\n onCleanup(installPageTitleController(root));\n });\n return (\n <div\n ref={root}\n class={`k2b-page-title ${props.class ?? \"\"}`}\n data-depth={ancestors().length}\n data-fallback={props.fallback ? \"\" : undefined}\n data-k2b-server={isServer && props.trail === host?.() ? \"\" : undefined}\n >\n <Show when={parent()}>\n {(up) => (\n <IconButtonLink\n class=\"k2b-page-title__back\"\n href={up().href}\n label={messages().backTo({ page: up().title })}\n data-k2b-page-trail-link=\"\"\n data-k2b-page-trail-up=\"\"\n >\n <i class=\"ti ti-chevron-left\" aria-hidden=\"true\" />\n </IconButtonLink>\n )}\n </Show>\n <div class=\"k2b-page-title__copy\">\n <Show when={ancestors().length === 1 && parent()}>\n {(up) => (\n <nav class=\"k2b-page-title__trail\" aria-label={messages().path}>\n <a\n class=\"k2b-page-title__up\"\n href={up().href}\n aria-label={messages().backTo({ page: up().title })}\n data-k2b-page-trail-link=\"\"\n data-k2b-page-trail-up=\"\"\n >\n <i class=\"ti ti-chevron-left\" aria-hidden=\"true\" />\n <span class=\"k2b-page-title__label\">{up().title}</span>\n </a>\n </nav>\n )}\n </Show>\n <Show when={ancestors().length > 1}>\n <nav class=\"k2b-page-title__trail\" aria-label={messages().path}>\n <ol class=\"k2b-page-title__path\">\n <For each={ancestors()}>\n {(link, index) => (\n <>\n <Show when={index() === 1}>\n <li class=\"k2b-page-title__more\" hidden>\n <i class=\"ti ti-chevron-right\" aria-hidden=\"true\" />\n <button type=\"button\" class=\"k2b-page-title__more-button\" popovertarget={menuId} aria-label={messages().moreLevels}>\n …\n </button>\n </li>\n </Show>\n <li class=\"k2b-page-title__crumb\" data-index={index()}>\n <Show when={index() > 0}>\n <i class=\"ti ti-chevron-right\" aria-hidden=\"true\" />\n </Show>\n <a\n href={link.href}\n data-k2b-page-trail-link=\"\"\n data-k2b-page-trail-up={index() === ancestors().length - 1 ? \"\" : undefined}\n class=\"k2b-page-title__label\"\n >\n {link.title}\n </a>\n </li>\n </>\n )}\n </For>\n </ol>\n <div id={menuId} popover=\"auto\" class=\"k2b-dropdown__menu k2b-page-title__menu\">\n <For each={ancestors()}>\n {(link, index) => (\n <a class=\"k2b-dropdown__item\" href={link.href} data-index={index()} data-k2b-page-trail-link=\"\" hidden>\n {link.title}\n </a>\n )}\n </For>\n </div>\n </nav>\n </Show>\n <Dynamic component={props.as ?? \"h1\"} class=\"k2b-page-title__title\">\n <span class=\"k2b-page-title__text\">{props.trail.title}</span>\n <Show when={props.subtitle}>\n <small class=\"k2b-page-title__subtitle\">{props.subtitle}</small>\n </Show>\n </Dynamic>\n </div>\n </div>\n );\n}\n\n/**\n * Keeps server-rendered titles of the host's trail current: a workspace head\n * that never hydrates, its heading, and an app's own header title from the\n * host's trail.\n * When the host publishes a new trail, for example after `setPageTrail()`,\n * each such title is replaced in place by a live one with the same element,\n * class, and subtitle, and a head learns whether it now has a page above it.\n * Titles inside islands render in the browser anyway. Returns the disposer.\n */\nexport const followPageTrail = (root: ParentNode = document): (() => void) => {\n const disposers: Array<() => void> = [];\n const upgrade = () => {\n const trail = readPageTrail();\n for (const element of root.querySelectorAll<HTMLElement>(\".k2b-page-title[data-k2b-server]\")) {\n const row = element.parentElement;\n if (!row || element.closest(\"solid-island, solid-client\")) continue;\n const tag = element.querySelector(\".k2b-page-title__title\")?.localName;\n const as = tag === \"p\" || tag === \"h2\" ? tag : \"h1\";\n const fallback = element.hasAttribute(\"data-fallback\");\n const extra = [...element.classList].filter((name) => name !== \"k2b-page-title\").join(\" \") || undefined;\n const subtitle = element.querySelector(\".k2b-page-title__subtitle\");\n const subtitleNodes = subtitle ? [...subtitle.childNodes] : undefined;\n // The live title takes the server title's place, before whatever follows it in the row.\n const place = document.createTextNode(\"\");\n row.insertBefore(place, element);\n element.remove();\n disposers.push(\n createRoot((dispose) => {\n const live = usePageTrail();\n insert(\n row,\n () => (\n <Show when={live()}>\n {(current) => <PageTitle trail={current()} as={as} fallback={fallback} class={extra} subtitle={subtitleNodes} />}\n </Show>\n ),\n place,\n );\n return dispose;\n }),\n );\n }\n // A server-rendered workspace heading carries only the title.\n for (const heading of root.querySelectorAll<HTMLElement>(\"[data-k2b-page-trail-title][data-k2b-server]\"))\n if (trail && !heading.closest(\"solid-island, solid-client\")) heading.textContent = trail.title;\n // The row's `data-trail` decides whether it shows; it follows the depth.\n for (const row of root.querySelectorAll<HTMLElement>(\"[data-trail]:has(> .k2b-page-title[data-fallback])\"))\n if (!row.closest(\"solid-island, solid-client\")) row.dataset.trail = trail?.ancestors.length ? \"up\" : \"title\";\n };\n window.addEventListener(PAGE_TRAIL_EVENT, upgrade);\n return () => {\n window.removeEventListener(PAGE_TRAIL_EVENT, upgrade);\n for (const dispose of disposers.splice(0)) dispose();\n };\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -35,7 +35,11 @@ const sidebarHideable = root => sidebarElement(root)?.dataset.workspaceHideable
|
|
|
35
35
|
export const APP_WORKSPACE_SIDEBAR_HIDE_EVENT = "k2b-app-workspace-sidebar-hide";
|
|
36
36
|
/** Asks the owning `AppWorkspace.Detail` to close; its `onClose` decides. */
|
|
37
37
|
export const APP_WORKSPACE_DETAIL_CLOSE_EVENT = "k2b-app-workspace-detail-close";
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Asks the owning Detail to step by `detail.direction` (`-1` or `1`). It writes
|
|
40
|
+
* the Main element showing the selection to `detail.item`, read synchronously
|
|
41
|
+
* by the controller after dispatch.
|
|
42
|
+
*/
|
|
39
43
|
export const APP_WORKSPACE_DETAIL_STEP_EVENT = "k2b-app-workspace-detail-step";
|
|
40
44
|
const previewSidebarHidden = (root, hidden) => {
|
|
41
45
|
const sidebar = sidebarElement(root);
|
|
@@ -64,16 +68,17 @@ const sizeLimits = (root, handle, kind) => {
|
|
|
64
68
|
const controlled = controlledPanel(root, handle);
|
|
65
69
|
const main = kind === "pane" ? handle.closest(".k2b-app-workspace__main") : mainElement(root);
|
|
66
70
|
const panes = main ? Array.from(main.querySelectorAll(".k2b-app-workspace__main-pane")).filter(pane => pane.closest(".k2b-app-workspace__main") === main && !pane.classList.contains("is-primary") && isVisible(pane)) : [];
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
71
|
+
// Content ends before the detail track, so its width alone would shrink a
|
|
72
|
+
// persisted panel again on every reconciliation. Measure from Content's
|
|
73
|
+
// start to the workspace's inner end: this includes the detail column and
|
|
74
|
+
// leaves out a desktop sidebar, while an overlay sidebar takes no width.
|
|
70
75
|
const content = kind === "detail" ? controlled?.closest(".k2b-app-workspace__content") : null;
|
|
71
76
|
const defaultMin = kind === "sidebar" ? APP_WORKSPACE_SIDEBAR_MIN : kind === "pane" ? APP_WORKSPACE_PANE_MIN : kind === "detail" ? APP_WORKSPACE_DETAIL_MIN : APP_WORKSPACE_DRAWER_MIN;
|
|
72
77
|
const defaultMax = kind === "sidebar" ? APP_WORKSPACE_SIDEBAR_MAX : kind === "pane" ? APP_WORKSPACE_PANE_MAX : kind === "detail" ? APP_WORKSPACE_DETAIL_MAX : APP_WORKSPACE_DRAWER_MAX;
|
|
73
78
|
return appWorkspaceResizeLimits({
|
|
74
79
|
kind,
|
|
75
|
-
workspaceSize: kind === "drawer" ? root.getBoundingClientRect().height : kind === "pane" && main ? main.getBoundingClientRect().width :
|
|
76
|
-
reservedSize: kind === "detail" ?
|
|
80
|
+
workspaceSize: kind === "drawer" ? root.getBoundingClientRect().height : kind === "pane" && main ? main.getBoundingClientRect().width : content ? root.getBoundingClientRect().right - root.clientLeft - content.getBoundingClientRect().left : root.getBoundingClientRect().width,
|
|
81
|
+
reservedSize: kind === "detail" ? 8 : kind === "pane" ? panes.reduce((total, pane) => total + (pane === controlled ? 0 : elementSize(pane, "pane")), 0) : 0,
|
|
77
82
|
min: numberData(handle, "workspaceMinSize", defaultMin),
|
|
78
83
|
max: numberData(handle, "workspaceMaxSize", defaultMax),
|
|
79
84
|
sidebarCollapsible: kind === "sidebar" && sidebarCollapsible(root)
|
|
@@ -317,18 +322,17 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
317
322
|
const reducedMotion = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
|
|
318
323
|
const detailSelector = ".k2b-app-workspace__detail";
|
|
319
324
|
const grabberSelector = ".k2b-app-workspace__detail-grabber";
|
|
320
|
-
const trailSelector = ".k2b-detail-trail";
|
|
321
325
|
const detailHidden = new WeakMap();
|
|
322
326
|
const remembered = new WeakMap();
|
|
323
327
|
const lastFocus = new WeakMap();
|
|
324
|
-
const trailPadding = new WeakMap();
|
|
325
|
-
const heldTrails = new Set();
|
|
326
328
|
const touchedDetails = new Set();
|
|
327
329
|
// Only a panel blocks scrolling for its touch gestures; a document-wide
|
|
328
330
|
// non-passive listener would make every scroll on the page wait for script.
|
|
329
331
|
const touchMoveAsides = new Set();
|
|
330
332
|
const titleTabIndices = new Set();
|
|
331
333
|
const motionFrames = new Map();
|
|
334
|
+
const pendingAnchors = new Map();
|
|
335
|
+
const slides = new Map();
|
|
332
336
|
const motionRoots = new Set();
|
|
333
337
|
const revealFrames = new Map();
|
|
334
338
|
const openDetails = root => rootElements(root, detailSelector).filter(aside => !aside.hidden);
|
|
@@ -337,17 +341,19 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
337
341
|
const requestClose = aside => {
|
|
338
342
|
if (aside.hasAttribute("data-workspace-detail-closable")) aside.dispatchEvent(new CustomEvent(APP_WORKSPACE_DETAIL_CLOSE_EVENT));else closeControl(aside)?.click();
|
|
339
343
|
};
|
|
340
|
-
const
|
|
344
|
+
const scrollerOn = (axis, target, boundary) => {
|
|
341
345
|
for (let element = target; element && boundary.contains(element); element = element.parentElement) {
|
|
342
|
-
if (element instanceof HTMLElement && /^(auto|scroll)$/.test(getComputedStyle(element).overflowY) && element.scrollHeight > element.clientHeight) return element;
|
|
346
|
+
if (element instanceof HTMLElement && /^(auto|scroll)$/.test(axis === "y" ? getComputedStyle(element).overflowY : getComputedStyle(element).overflowX) && (axis === "y" ? element.scrollHeight > element.clientHeight : element.scrollWidth > element.clientWidth)) return element;
|
|
343
347
|
if (element === boundary) break;
|
|
344
348
|
}
|
|
345
349
|
return null;
|
|
346
350
|
};
|
|
351
|
+
const verticalScroller = (target, boundary) => scrollerOn("y", target, boundary);
|
|
347
352
|
const refreshItem = snapshot => {
|
|
348
353
|
snapshot.top = snapshot.item.getBoundingClientRect().top;
|
|
349
354
|
snapshot.scrollTop = snapshot.scroller?.scrollTop ?? 0;
|
|
350
355
|
};
|
|
356
|
+
// A table row can exceed its scroller's width, so keep the clicked cell for horizontal reveal.
|
|
351
357
|
const rememberInteraction = event => {
|
|
352
358
|
const target = eventElement(event);
|
|
353
359
|
const root = target?.closest(ROOT_SELECTOR);
|
|
@@ -358,8 +364,10 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
358
364
|
const item = target.closest(FOCUSABLE_SELECTOR);
|
|
359
365
|
if (!main || !item || !main.contains(item) || item.closest(ROOT_SELECTOR) !== root) return;
|
|
360
366
|
if (event instanceof KeyboardEvent && event.key !== "Enter" && event.key !== " ") return;
|
|
367
|
+
const cell = target.closest("td, th");
|
|
361
368
|
const snapshot = {
|
|
362
369
|
item,
|
|
370
|
+
cell: cell && main.contains(cell) && cell.closest(ROOT_SELECTOR) === root ? cell : null,
|
|
363
371
|
interaction: event instanceof KeyboardEvent ? "keyboard" : "pointer",
|
|
364
372
|
scroller: verticalScroller(item, main),
|
|
365
373
|
scrollTop: 0,
|
|
@@ -417,7 +425,12 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
417
425
|
pendingTitle?.observer.disconnect();
|
|
418
426
|
pendingTitle = null;
|
|
419
427
|
};
|
|
428
|
+
// A step from inside the panel can remove the focused element with the old
|
|
429
|
+
// content, so focus falls to the body. The panel keeps owning the steps until
|
|
430
|
+
// focus or a pointer goes elsewhere, so a quick second J still ends on the title.
|
|
431
|
+
let panelStep = null;
|
|
420
432
|
const cancelTitleOnInteraction = event => {
|
|
433
|
+
if (event.type !== "keydown" && !(event.target instanceof Node && panelStep?.contains(event.target))) panelStep = null;
|
|
421
434
|
if (event.type !== "focusin" || event.target !== focusingTitle) cancelTitle();
|
|
422
435
|
};
|
|
423
436
|
const requestTitle = (aside, waitForChange = false) => {
|
|
@@ -463,42 +476,94 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
463
476
|
aside.style.removeProperty("transform");
|
|
464
477
|
delete aside.dataset.workspaceDetailDragging;
|
|
465
478
|
};
|
|
466
|
-
const trailsIn = root => {
|
|
467
|
-
const main = mainElement(root);
|
|
468
|
-
return main ? rootElements(root, trailSelector).filter(trail => main.contains(trail)) : [];
|
|
469
|
-
};
|
|
470
|
-
const clearHolds = root => {
|
|
471
|
-
for (const trail of trailsIn(root)) {
|
|
472
|
-
trail.style.removeProperty("--k2b-detail-trail-hold");
|
|
473
|
-
heldTrails.delete(trail);
|
|
474
|
-
}
|
|
475
|
-
};
|
|
476
|
-
const rememberTrailPadding = root => {
|
|
477
|
-
if (!openDetails(root).some(isVisible)) return;
|
|
478
|
-
for (const trail of trailsIn(root)) trailPadding.set(trail, Number.parseFloat(getComputedStyle(trail).paddingInlineEnd) || 0);
|
|
479
|
-
};
|
|
480
|
-
const shrinkHold = trail => {
|
|
481
|
-
const hold = Number.parseFloat(trail.style.getPropertyValue("--k2b-detail-trail-hold")) || 0;
|
|
482
|
-
const visible = trail.scrollWidth <= trail.clientWidth || trail.scrollLeft < 1 ? 0 : Math.min(hold, Math.max(0, Math.ceil(trail.scrollLeft + trail.clientWidth - (trail.scrollWidth - hold))));
|
|
483
|
-
if (visible > 0) trail.style.setProperty("--k2b-detail-trail-hold", `${visible}px`);else {
|
|
484
|
-
trail.style.removeProperty("--k2b-detail-trail-hold");
|
|
485
|
-
heldTrails.delete(trail);
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
const onTrailScroll = event => {
|
|
489
|
-
const target = eventElement(event);
|
|
490
|
-
if (target instanceof HTMLElement && heldTrails.has(target)) shrinkHold(target);
|
|
491
|
-
};
|
|
492
479
|
const anchorItem = (root, aside, snapshot) => {
|
|
493
|
-
if (!wide?.matches || touchDrawer?.matches && closable(aside) ||
|
|
480
|
+
if (!wide?.matches || touchDrawer?.matches && closable(aside) || !snapshot?.item.isConnected) return;
|
|
494
481
|
if (!mainElement(root)?.contains(snapshot.item) || snapshot.item.closest(ROOT_SELECTOR) !== root) return;
|
|
495
482
|
const scroller = snapshot.scroller;
|
|
496
483
|
if (scroller?.isConnected && scroller.scrollTop === snapshot.scrollTop) scroller.scrollTop += snapshot.item.getBoundingClientRect().top - snapshot.top;
|
|
497
484
|
};
|
|
485
|
+
// From 64 rem the workspace does not clip, so the panel's shadow shows on
|
|
486
|
+
// every edge. A panel sliding in or out would cross a framed workspace's
|
|
487
|
+
// edge, so the workspace clips while the panel moves (see the stylesheet).
|
|
488
|
+
const slide = (root, aside) => {
|
|
489
|
+
slides.get(root)?.();
|
|
490
|
+
if (!root.hasAttribute("data-workspace-motion") || reducedMotion?.matches || !wide?.matches) return;
|
|
491
|
+
const end = () => {
|
|
492
|
+
aside.removeEventListener("transitionend", onEnd);
|
|
493
|
+
aside.removeEventListener("transitioncancel", onEnd);
|
|
494
|
+
cancelAnimationFrame(frame);
|
|
495
|
+
slides.delete(root);
|
|
496
|
+
delete root.dataset.workspaceDetailSliding;
|
|
497
|
+
};
|
|
498
|
+
const onEnd = event => {
|
|
499
|
+
if (event.target === aside && event.propertyName === "transform") end();
|
|
500
|
+
};
|
|
501
|
+
aside.addEventListener("transitionend", onEnd);
|
|
502
|
+
aside.addEventListener("transitioncancel", onEnd);
|
|
503
|
+
// A slow first frame must not end the clip early, so only a panel that
|
|
504
|
+
// does not slide at all ends it in that frame.
|
|
505
|
+
const frame = requestAnimationFrame(() => {
|
|
506
|
+
const sliding = aside.getAnimations?.().some(animation => "transitionProperty" in animation && animation.transitionProperty === "transform");
|
|
507
|
+
if (!sliding) end();
|
|
508
|
+
});
|
|
509
|
+
slides.set(root, end);
|
|
510
|
+
root.dataset.workspaceDetailSliding = "";
|
|
511
|
+
};
|
|
512
|
+
const revealItem = (root, aside, snapshot) => {
|
|
513
|
+
const pending = revealFrames.get(root);
|
|
514
|
+
if (pending !== undefined) cancelAnimationFrame(pending);
|
|
515
|
+
revealFrames.set(root, requestAnimationFrame(() => {
|
|
516
|
+
revealFrames.delete(root);
|
|
517
|
+
if (!aside.hidden && aside.isConnected && snapshot.item.isConnected && mainElement(root)?.contains(snapshot.item) && snapshot.item.closest(ROOT_SELECTOR) === root) {
|
|
518
|
+
const behavior = reducedMotion?.matches ? "auto" : "smooth";
|
|
519
|
+
const main = mainElement(root);
|
|
520
|
+
const cell = snapshot.cell;
|
|
521
|
+
const target = cell?.isConnected && main?.contains(cell) ? cell : snapshot.item;
|
|
522
|
+
if (phone?.matches && main) {
|
|
523
|
+
// The sheet covers Main's lower part, so the item ends 8px above it;
|
|
524
|
+
// a step can also land above Main's top or beside it, as in a board.
|
|
525
|
+
const page = ownerDocument.scrollingElement;
|
|
526
|
+
const vertical = verticalScroller(target, main) ?? page;
|
|
527
|
+
const horizontal = scrollerOn("x", target, main) ?? page;
|
|
528
|
+
const bounds = scroller => scroller && scroller !== page ? scroller.getBoundingClientRect() : null;
|
|
529
|
+
const box = target.getBoundingClientRect();
|
|
530
|
+
const top = Math.max(0, bounds(vertical)?.top ?? 0);
|
|
531
|
+
const left = Math.max(0, bounds(horizontal)?.left ?? 0);
|
|
532
|
+
const right = Math.min(ownerWindow.innerWidth, bounds(horizontal)?.right ?? Infinity);
|
|
533
|
+
const dy = box.top < top ? box.top - top - 8 : Math.max(0, box.bottom - (ownerWindow.innerHeight - aside.offsetHeight) + 8);
|
|
534
|
+
// As with scrollIntoView's "nearest", an item past both edges stays put
|
|
535
|
+
// and one wider than the view keeps its start in view.
|
|
536
|
+
const dx = box.left < left && box.right > right ? 0 : box.left < left ? box.left - left - 8 : box.right > right ? Math.min(box.right - right + 8, Math.max(0, box.left - left - 8)) : 0;
|
|
537
|
+
if (vertical && vertical === horizontal) {
|
|
538
|
+
if (dy || dx) vertical.scrollBy({
|
|
539
|
+
top: dy,
|
|
540
|
+
left: dx,
|
|
541
|
+
behavior
|
|
542
|
+
});
|
|
543
|
+
} else {
|
|
544
|
+
if (dy) vertical?.scrollBy({
|
|
545
|
+
top: dy,
|
|
546
|
+
behavior
|
|
547
|
+
});
|
|
548
|
+
if (dx) horizontal?.scrollBy({
|
|
549
|
+
left: dx,
|
|
550
|
+
behavior
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
} else target.scrollIntoView({
|
|
554
|
+
block: "nearest",
|
|
555
|
+
inline: "nearest",
|
|
556
|
+
behavior
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}));
|
|
560
|
+
};
|
|
498
561
|
const detailTransition = (aside, wasHidden) => {
|
|
499
562
|
const root = aside.closest(ROOT_SELECTOR);
|
|
500
563
|
detailHidden.set(aside, Boolean(aside.hidden));
|
|
501
564
|
if (!root || !observedRoots.has(root) || Boolean(aside.hidden) === wasHidden) return;
|
|
565
|
+
pendingAnchors.get(root)?.();
|
|
566
|
+
slide(root, aside);
|
|
502
567
|
touchedDetails.add(aside);
|
|
503
568
|
const pending = revealFrames.get(root);
|
|
504
569
|
if (pending !== undefined) cancelAnimationFrame(pending);
|
|
@@ -508,18 +573,25 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
508
573
|
if (snapshot) snapshot.fresh = false;
|
|
509
574
|
if (aside.hidden) {
|
|
510
575
|
if (pendingTitle?.aside === aside) cancelTitle();
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
576
|
+
if (panelStep === aside) panelStep = null;
|
|
577
|
+
if (root.hasAttribute("data-workspace-motion") && !reducedMotion?.matches && wide?.matches) {
|
|
578
|
+
// Closing holds the detail column until the slide ends; anchor after Main widens.
|
|
579
|
+
const cancel = () => {
|
|
580
|
+
root.removeEventListener("transitionend", onTransitionEnd);
|
|
581
|
+
clearTimeout(timer);
|
|
582
|
+
pendingAnchors.delete(root);
|
|
583
|
+
};
|
|
584
|
+
const finish = () => {
|
|
585
|
+
cancel();
|
|
586
|
+
if (aside.hidden && aside.isConnected) anchorItem(root, aside, snapshot);
|
|
587
|
+
};
|
|
588
|
+
const onTransitionEnd = event => {
|
|
589
|
+
if (event.target === root && event.propertyName === "--k2b-workspace-detail-track") finish();
|
|
590
|
+
};
|
|
591
|
+
const timer = setTimeout(finish, 300);
|
|
592
|
+
root.addEventListener("transitionend", onTransitionEnd);
|
|
593
|
+
pendingAnchors.set(root, cancel);
|
|
594
|
+
} else anchorItem(root, aside, snapshot);
|
|
523
595
|
const focused = ownerDocument.activeElement;
|
|
524
596
|
if (snapshot?.item.isConnected && (aside.contains(lastFocus.get(root) ?? null) || aside.contains(focused) || focused === ownerDocument.body)) snapshot.item.focus({
|
|
525
597
|
preventScroll: true
|
|
@@ -528,30 +600,10 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
528
600
|
if (detailDrag?.aside === aside) finishDetailDrag(true);
|
|
529
601
|
return;
|
|
530
602
|
}
|
|
531
|
-
clearHolds(root);
|
|
532
603
|
anchorItem(root, aside, snapshot);
|
|
533
604
|
if (phone?.matches) setDetent(aside, false);
|
|
534
|
-
rememberTrailPadding(root);
|
|
535
605
|
if (followedInteraction && snapshot) {
|
|
536
|
-
|
|
537
|
-
revealFrames.delete(root);
|
|
538
|
-
if (!aside.hidden && aside.isConnected && snapshot.item.isConnected && mainElement(root)?.contains(snapshot.item) && snapshot.item.closest(ROOT_SELECTOR) === root) {
|
|
539
|
-
const behavior = reducedMotion?.matches ? "auto" : "smooth";
|
|
540
|
-
if (phone?.matches) {
|
|
541
|
-
const sheetTop = ownerWindow.innerHeight - aside.offsetHeight;
|
|
542
|
-
const overlap = snapshot.item.getBoundingClientRect().bottom - sheetTop + 8;
|
|
543
|
-
const main = mainElement(root);
|
|
544
|
-
if (overlap > 0 && main) (verticalScroller(snapshot.item, main) ?? ownerDocument.scrollingElement)?.scrollBy({
|
|
545
|
-
top: overlap,
|
|
546
|
-
behavior
|
|
547
|
-
});
|
|
548
|
-
} else snapshot.item.scrollIntoView({
|
|
549
|
-
block: "nearest",
|
|
550
|
-
inline: "nearest",
|
|
551
|
-
behavior
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
}));
|
|
606
|
+
revealItem(root, aside, snapshot);
|
|
555
607
|
if (snapshot.interaction === "keyboard" || phone?.matches) requestTitle(aside);
|
|
556
608
|
}
|
|
557
609
|
};
|
|
@@ -575,11 +627,45 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
575
627
|
}
|
|
576
628
|
const owningRoot = aside?.closest(ROOT_SELECTOR);
|
|
577
629
|
if (!aside || !owningRoot) return;
|
|
578
|
-
if (event.key === "Escape" && closable(aside)) requestClose(aside);else if ((event.key.toLowerCase() === "j" || event.key.toLowerCase() === "k") && aside.hasAttribute("data-workspace-detail-steppable"))
|
|
579
|
-
|
|
630
|
+
if (event.key === "Escape" && closable(aside)) requestClose(aside);else if ((event.key.toLowerCase() === "j" || event.key.toLowerCase() === "k") && aside.hasAttribute("data-workspace-detail-steppable")) {
|
|
631
|
+
const main = mainElement(owningRoot);
|
|
632
|
+
const inMain = remembered.get(owningRoot)?.item.contains(target) || main?.contains(target);
|
|
633
|
+
const inPanel = aside.contains(target) || target === ownerDocument.body && panelStep === aside;
|
|
634
|
+
panelStep = inPanel ? aside : null;
|
|
635
|
+
const previousTitle = inPanel ? titleIn(aside) : null;
|
|
636
|
+
const previousText = previousTitle?.textContent;
|
|
637
|
+
const detail = {
|
|
580
638
|
direction: event.key.toLowerCase() === "j" ? 1 : -1
|
|
639
|
+
};
|
|
640
|
+
aside.dispatchEvent(new CustomEvent(APP_WORKSPACE_DETAIL_STEP_EVENT, {
|
|
641
|
+
detail
|
|
642
|
+
}));
|
|
643
|
+
const shown = detail.item;
|
|
644
|
+
const item = shown instanceof HTMLElement && main?.contains(shown) && shown.closest(ROOT_SELECTOR) === owningRoot ? shown : null;
|
|
645
|
+
if (item && main) {
|
|
646
|
+
const snapshot = {
|
|
647
|
+
item,
|
|
648
|
+
cell: null,
|
|
649
|
+
interaction: "keyboard",
|
|
650
|
+
scroller: verticalScroller(item, main),
|
|
651
|
+
scrollTop: 0,
|
|
652
|
+
top: 0,
|
|
653
|
+
fresh: false
|
|
654
|
+
};
|
|
655
|
+
refreshItem(snapshot);
|
|
656
|
+
remembered.set(owningRoot, snapshot);
|
|
657
|
+
revealItem(owningRoot, aside, snapshot);
|
|
581
658
|
}
|
|
582
|
-
|
|
659
|
+
const title = titleIn(aside);
|
|
660
|
+
if (item && inMain) item.focus({
|
|
661
|
+
preventScroll: true
|
|
662
|
+
});
|
|
663
|
+
// A panel that swapped its title during the event has the new one now.
|
|
664
|
+
else if (item && inPanel && title && (title !== previousTitle || title.textContent !== previousText)) focusTitle(aside);
|
|
665
|
+
// This key's capture cancelled an earlier step's title request, so a
|
|
666
|
+
// step from the body, even past the last item, asks again.
|
|
667
|
+
else if (inPanel && (item || target === ownerDocument.body)) requestTitle(aside, Boolean(item));
|
|
668
|
+
} else if (event.key === "F6") {
|
|
583
669
|
if (aside.contains(target)) {
|
|
584
670
|
const item = remembered.get(owningRoot)?.item;
|
|
585
671
|
const main = mainElement(owningRoot);
|
|
@@ -781,7 +867,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
781
867
|
root.style.removeProperty(variable);
|
|
782
868
|
if (getComputedStyle(root).getPropertyValue(variable).trim()) root.style.setProperty(variable, `${numberData(handle, "workspaceDefaultSize", APP_WORKSPACE_DETAIL_DEFAULT)}px`);
|
|
783
869
|
updateHandleValue(root, handle, "detail", currentSize(root, handle, "detail"));
|
|
784
|
-
rememberTrailPadding(root);
|
|
785
870
|
options.writeState?.(layoutState);
|
|
786
871
|
};
|
|
787
872
|
const stopScrollFades = installScrollFades(eventRoot);
|
|
@@ -863,7 +948,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
863
948
|
const size = finished.kind === "pane" ? finished.liveSize : applySize(finished.root, finished.handle, finished.kind, finished.liveSize);
|
|
864
949
|
persistSize(finished.handle, finished.kind, size);
|
|
865
950
|
if (finished.kind !== "drawer") reconcilePanes(finished.root, layoutState);
|
|
866
|
-
if (finished.kind === "detail") rememberTrailPadding(finished.root);
|
|
867
951
|
}
|
|
868
952
|
if (finished.handle.hasPointerCapture?.(finished.pointerId)) finished.handle.releasePointerCapture(finished.pointerId);
|
|
869
953
|
finished.releaseTextSelection();
|
|
@@ -929,7 +1013,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
929
1013
|
event.preventDefault();
|
|
930
1014
|
persistSize(handle, kind, applySize(root, handle, kind, requested));
|
|
931
1015
|
if (kind === "sidebar" || kind === "detail") reconcilePanes(root, layoutState);
|
|
932
|
-
if (kind === "detail") rememberTrailPadding(root);
|
|
933
1016
|
};
|
|
934
1017
|
const allRoots = () => {
|
|
935
1018
|
const nested = Array.from(eventRoot.querySelectorAll(ROOT_SELECTOR));
|
|
@@ -949,7 +1032,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
949
1032
|
if (aside.dataset.workspaceDetailSheet === "full") setDetent(aside, false);
|
|
950
1033
|
}
|
|
951
1034
|
}
|
|
952
|
-
rememberTrailPadding(root);
|
|
953
1035
|
});
|
|
954
1036
|
roots().forEach(root => {
|
|
955
1037
|
// `data-workspace-resizable="false"` already refuses pointer and keyboard
|
|
@@ -974,7 +1056,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
974
1056
|
});
|
|
975
1057
|
reconcilePanes(root, layoutState, active?.root === root ? active : null);
|
|
976
1058
|
});
|
|
977
|
-
heldTrails.forEach(shrinkHold);
|
|
978
1059
|
};
|
|
979
1060
|
/**
|
|
980
1061
|
* A handle's limits move whenever anything around it does — opening a detail
|
|
@@ -1036,6 +1117,8 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1036
1117
|
const next = new Set(allRoots());
|
|
1037
1118
|
observedRoots.forEach(root => {
|
|
1038
1119
|
if (!next.has(root)) {
|
|
1120
|
+
pendingAnchors.get(root)?.();
|
|
1121
|
+
slides.get(root)?.();
|
|
1039
1122
|
resizeObserver?.unobserve(root);
|
|
1040
1123
|
const frame = motionFrames.get(root);
|
|
1041
1124
|
if (frame !== undefined) cancelAnimationFrame(frame);
|
|
@@ -1069,7 +1152,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1069
1152
|
}));
|
|
1070
1153
|
}));
|
|
1071
1154
|
}
|
|
1072
|
-
rememberTrailPadding(root);
|
|
1073
1155
|
});
|
|
1074
1156
|
};
|
|
1075
1157
|
const layoutNode = node => node instanceof Element && (node.matches(LAYOUT_REGION_SELECTOR) || Boolean(node.querySelector(LAYOUT_REGION_SELECTOR)));
|
|
@@ -1099,10 +1181,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1099
1181
|
eventRoot.addEventListener("pointerdown", rememberInteraction, true);
|
|
1100
1182
|
eventRoot.addEventListener("keydown", rememberInteraction, true);
|
|
1101
1183
|
eventRoot.addEventListener("focusin", onDetailFocusIn);
|
|
1102
|
-
eventRoot.addEventListener("scroll", onTrailScroll, {
|
|
1103
|
-
passive: true,
|
|
1104
|
-
capture: true
|
|
1105
|
-
});
|
|
1106
1184
|
eventRoot.addEventListener("pointerdown", onSheetPointerDown);
|
|
1107
1185
|
eventRoot.addEventListener("click", onGrabberClick);
|
|
1108
1186
|
eventRoot.addEventListener("dblclick", onDetailDoubleClick);
|
|
@@ -1131,13 +1209,14 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1131
1209
|
scheduleReconcile();
|
|
1132
1210
|
return () => {
|
|
1133
1211
|
cancelTitle();
|
|
1212
|
+
pendingAnchors.forEach(cancel => cancel());
|
|
1213
|
+
slides.forEach(end => end());
|
|
1134
1214
|
stopScrollFades();
|
|
1135
1215
|
finishDetailDrag(true);
|
|
1136
1216
|
if (suppressedClickTimer !== null) clearTimeout(suppressedClickTimer);
|
|
1137
1217
|
motionFrames.forEach(cancelAnimationFrame);
|
|
1138
1218
|
revealFrames.forEach(cancelAnimationFrame);
|
|
1139
1219
|
motionRoots.forEach(root => delete root.dataset.workspaceMotion);
|
|
1140
|
-
heldTrails.forEach(trail => trail.style.removeProperty("--k2b-detail-trail-hold"));
|
|
1141
1220
|
touchedDetails.forEach(aside => {
|
|
1142
1221
|
clearDrag(aside);
|
|
1143
1222
|
delete aside.dataset.workspaceDetailSheet;
|
|
@@ -1151,7 +1230,6 @@ export const installAppWorkspaceController = (options = {}) => {
|
|
|
1151
1230
|
eventRoot.removeEventListener("pointerdown", rememberInteraction, true);
|
|
1152
1231
|
eventRoot.removeEventListener("keydown", rememberInteraction, true);
|
|
1153
1232
|
eventRoot.removeEventListener("focusin", onDetailFocusIn);
|
|
1154
|
-
eventRoot.removeEventListener("scroll", onTrailScroll, true);
|
|
1155
1233
|
eventRoot.removeEventListener("pointerdown", onSheetPointerDown);
|
|
1156
1234
|
eventRoot.removeEventListener("click", onGrabberClick);
|
|
1157
1235
|
eventRoot.removeEventListener("dblclick", onDetailDoubleClick);
|