@k2b/ui 0.22.0 → 0.24.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.
Files changed (117) hide show
  1. package/dist/browser/content/Calendar.js +1122 -1065
  2. package/dist/browser/content/Calendar.js.map +1 -1
  3. package/dist/browser/content/CalendarDayView.js +1187 -0
  4. package/dist/browser/content/CalendarDayView.js.map +1 -0
  5. package/dist/browser/content/CalendarWeekView.js +1593 -0
  6. package/dist/browser/content/CalendarWeekView.js.map +1 -0
  7. package/dist/browser/content/ChartExplorer.js +174 -82
  8. package/dist/browser/content/ChartExplorer.js.map +1 -1
  9. package/dist/browser/content/ChartFilterControls.js +90 -33
  10. package/dist/browser/content/ChartFilterControls.js.map +1 -1
  11. package/dist/browser/content/DataTable.js +235 -197
  12. package/dist/browser/content/DataTable.js.map +1 -1
  13. package/dist/browser/content/calendar-day-scroll.js +111 -0
  14. package/dist/browser/content/calendar-day-scroll.js.map +1 -0
  15. package/dist/browser/content/calendar-days.js +408 -0
  16. package/dist/browser/content/calendar-days.js.map +1 -0
  17. package/dist/browser/content/calendar-dialogs.js +236 -0
  18. package/dist/browser/content/calendar-dialogs.js.map +1 -0
  19. package/dist/browser/content/calendar-messages.js +210 -2
  20. package/dist/browser/content/calendar-messages.js.map +1 -1
  21. package/dist/browser/content/calendar-pointer.js +15 -3
  22. package/dist/browser/content/calendar-pointer.js.map +1 -1
  23. package/dist/browser/content/calendar-shared.js +328 -0
  24. package/dist/browser/content/calendar-shared.js.map +1 -0
  25. package/dist/browser/content/calendar-time-view.js +1654 -0
  26. package/dist/browser/content/calendar-time-view.js.map +1 -0
  27. package/dist/browser/content/chart-messages.js +10 -0
  28. package/dist/browser/content/chart-messages.js.map +1 -1
  29. package/dist/browser/content/index.js.map +1 -1
  30. package/dist/browser/inputs/Combobox.js +0 -1
  31. package/dist/browser/inputs/Combobox.js.map +1 -1
  32. package/dist/browser/layout/AppWorkspace.js +125 -97
  33. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  34. package/dist/browser/layout/Navigation.js +18 -3
  35. package/dist/browser/layout/Navigation.js.map +1 -1
  36. package/dist/browser/layout/PageTitle.js +18 -1
  37. package/dist/browser/layout/PageTitle.js.map +1 -1
  38. package/dist/browser/layout/SidebarSwitcher.js +505 -0
  39. package/dist/browser/layout/SidebarSwitcher.js.map +1 -0
  40. package/dist/browser/layout/app-workspace-controller.js +174 -86
  41. package/dist/browser/layout/app-workspace-controller.js.map +1 -1
  42. package/dist/browser/layout/index.js +1 -0
  43. package/dist/browser/layout/index.js.map +1 -1
  44. package/dist/browser/layout/navigation-model.js +1 -0
  45. package/dist/browser/layout/navigation-model.js.map +1 -1
  46. package/dist/browser/surfaces/StatCell.js +18 -6
  47. package/dist/browser/surfaces/StatCell.js.map +1 -1
  48. package/dist/ssr/content/Calendar.js +553 -341
  49. package/dist/ssr/content/Calendar.js.map +1 -1
  50. package/dist/ssr/content/CalendarDayView.js +800 -0
  51. package/dist/ssr/content/CalendarDayView.js.map +1 -0
  52. package/dist/ssr/content/CalendarWeekView.js +1090 -0
  53. package/dist/ssr/content/CalendarWeekView.js.map +1 -0
  54. package/dist/ssr/content/ChartExplorer.js +93 -34
  55. package/dist/ssr/content/ChartExplorer.js.map +1 -1
  56. package/dist/ssr/content/ChartFilterControls.js +49 -7
  57. package/dist/ssr/content/ChartFilterControls.js.map +1 -1
  58. package/dist/ssr/content/DataTable.js +88 -66
  59. package/dist/ssr/content/DataTable.js.map +1 -1
  60. package/dist/ssr/content/calendar-day-scroll.js +111 -0
  61. package/dist/ssr/content/calendar-day-scroll.js.map +1 -0
  62. package/dist/ssr/content/calendar-days.js +408 -0
  63. package/dist/ssr/content/calendar-days.js.map +1 -0
  64. package/dist/ssr/content/calendar-dialogs.js +159 -0
  65. package/dist/ssr/content/calendar-dialogs.js.map +1 -0
  66. package/dist/ssr/content/calendar-messages.js +210 -2
  67. package/dist/ssr/content/calendar-messages.js.map +1 -1
  68. package/dist/ssr/content/calendar-pointer.js +15 -3
  69. package/dist/ssr/content/calendar-pointer.js.map +1 -1
  70. package/dist/ssr/content/calendar-shared.js +261 -0
  71. package/dist/ssr/content/calendar-shared.js.map +1 -0
  72. package/dist/ssr/content/calendar-time-view.js +1311 -0
  73. package/dist/ssr/content/calendar-time-view.js.map +1 -0
  74. package/dist/ssr/content/chart-messages.js +10 -0
  75. package/dist/ssr/content/chart-messages.js.map +1 -1
  76. package/dist/ssr/content/index.js.map +1 -1
  77. package/dist/ssr/inputs/Combobox.js.map +1 -1
  78. package/dist/ssr/layout/AppWorkspace.js +40 -29
  79. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  80. package/dist/ssr/layout/Navigation.js +12 -2
  81. package/dist/ssr/layout/Navigation.js.map +1 -1
  82. package/dist/ssr/layout/PageTitle.js +18 -1
  83. package/dist/ssr/layout/PageTitle.js.map +1 -1
  84. package/dist/ssr/layout/SidebarSwitcher.js +271 -0
  85. package/dist/ssr/layout/SidebarSwitcher.js.map +1 -0
  86. package/dist/ssr/layout/app-workspace-controller.js +174 -86
  87. package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
  88. package/dist/ssr/layout/index.js +1 -0
  89. package/dist/ssr/layout/index.js.map +1 -1
  90. package/dist/ssr/layout/navigation-model.js +1 -0
  91. package/dist/ssr/layout/navigation-model.js.map +1 -1
  92. package/dist/ssr/surfaces/StatCell.js +2 -2
  93. package/dist/ssr/surfaces/StatCell.js.map +1 -1
  94. package/dist/styles.css +1 -1
  95. package/dist/types/content/Calendar.d.ts +95 -1
  96. package/dist/types/content/CalendarDayView.d.ts +7 -0
  97. package/dist/types/content/CalendarWeekView.d.ts +7 -0
  98. package/dist/types/content/ChartExplorer.d.ts +36 -1
  99. package/dist/types/content/ChartFilterControls.d.ts +6 -1
  100. package/dist/types/content/DataTable.d.ts +14 -0
  101. package/dist/types/content/calendar-day-scroll.d.ts +47 -0
  102. package/dist/types/content/calendar-days.d.ts +192 -0
  103. package/dist/types/content/calendar-dialogs.d.ts +22 -0
  104. package/dist/types/content/calendar-messages.d.ts +99 -0
  105. package/dist/types/content/calendar-pointer.d.ts +9 -0
  106. package/dist/types/content/calendar-shared.d.ts +69 -0
  107. package/dist/types/content/calendar-time-view.d.ts +252 -0
  108. package/dist/types/content/chart-messages.d.ts +5 -0
  109. package/dist/types/content/index.d.ts +2 -2
  110. package/dist/types/layout/AppWorkspace.d.ts +27 -3
  111. package/dist/types/layout/PageTitle.d.ts +2 -1
  112. package/dist/types/layout/SidebarSwitcher.d.ts +65 -0
  113. package/dist/types/layout/app-workspace-controller.d.ts +9 -1
  114. package/dist/types/layout/index.d.ts +1 -0
  115. package/dist/types/layout/navigation-model.d.ts +4 -0
  116. package/dist/types/surfaces/StatCell.d.ts +5 -0
  117. package/package.json +3 -3
@@ -20,11 +20,12 @@ var _tmpl$ = ["<ul class=\"k2b-navigation__list\"", ">", "</ul>"],
20
20
  _tmpl$10 = ["<div", ">", "</div>"],
21
21
  _tmpl$11 = ["<li><div class=\"k2b-navigation__row\"", ">", "", "", "", "</div>", "</li>"],
22
22
  _tmpl$12 = ["<button type=\"button\" class=\"k2b-navigation__control\"", ">", "", "</button>"],
23
- _tmpl$13 = ["<nav class=\"k2b-navigation\"", ">", "</nav>"];
23
+ _tmpl$13 = ["<nav class=\"k2b-navigation\"", ">", "", "</nav>"];
24
24
  import { documentNavigate, Link } from "@k2b/ssr/nav";
25
25
  import { createSignal, createUniqueId, For, Show } from "solid-js";
26
26
  import { IconButton } from "../actions/Button";
27
27
  import { Dropdown } from "../actions/Dropdown";
28
+ import { AppWorkspaceSidebarSwitcher } from "./SidebarSwitcher";
28
29
  import { useWorkspaceMessages } from "./workspace-messages";
29
30
  /** A named icon is an image screen readers announce; any other icon is decoration. */
30
31
  const iconAccessibility = label => label ? {
@@ -251,7 +252,16 @@ export default function Navigation(props) {
251
252
  });
252
253
  }
253
254
  })));
254
- return _$ssr(_tmpl$13, _$ssrAttribute("aria-label", _$escape(props.label, true), false), _$escape(_$createComponent(Rows, {
255
+ return _$ssr(_tmpl$13, _$ssrAttribute("aria-label", _$escape(props.label, true), false), _$escape(_$createComponent(Show, {
256
+ get when() {
257
+ return props.navigation.switcher?.();
258
+ },
259
+ children: switcher => _$createComponent(AppWorkspaceSidebarSwitcher, _$mergeProps(switcher, {
260
+ get beforeSelect() {
261
+ return props.beforeSelect;
262
+ }
263
+ }))
264
+ })), _$escape(_$createComponent(Rows, {
255
265
  get items() {
256
266
  return props.navigation.items();
257
267
  }
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["layout/Navigation.tsx"],"sourcesContent":["import { documentNavigate, Link } from \"@k2b/ssr/nav\";\nimport { createSignal, createUniqueId, For, Show } from \"solid-js\";\nimport { IconButton } from \"../actions/Button\";\nimport { Dropdown } from \"../actions/Dropdown\";\nimport type { NavigationController, NavigationItem } from \"./navigation-model\";\nimport { useWorkspaceMessages } from \"./workspace-messages\";\n\nexport type NavigationProps = {\n navigation: NavigationController;\n label: string;\n /** A host can finish dismissing its menu before the selected operation runs. */\n beforeSelect?: () => boolean | void | Promise<boolean | void>;\n};\n\n/** A named icon is an image screen readers announce; any other icon is decoration. */\nconst iconAccessibility = (label: string | undefined) => (label ? { role: \"img\" as const, \"aria-label\": label } : { \"aria-hidden\": true });\n\nexport default function Navigation(props: NavigationProps) {\n const workspaceMessages = useWorkspaceMessages();\n const [expanded, setExpanded] = createSignal<Record<string, boolean>>({});\n const ready = async () => (await props.beforeSelect?.()) !== false;\n /** `keepOpen` changes only what the menu shows, so the host does not dismiss it first. */\n const activate = async (id: string, keepOpen = false) => {\n if (keepOpen || (await ready())) await props.navigation.activate(id);\n };\n const Rows = (rows: { items: readonly NavigationItem[]; disabled?: boolean; labelledBy?: string }) => (\n <ul class=\"k2b-navigation__list\" aria-labelledby={rows.labelledBy}>\n <For each={rows.items.map((item) => item.id)}>\n {(id) => {\n const item = () => rows.items.find((entry) => entry.id === id)!;\n const disabled = () => rows.disabled || item().disabled;\n const headingId = `k2b-navigation-section-${createUniqueId()}`;\n // `expanded` hands disclosure to the item's owner; every other toggle stays local to this renderer.\n const open = () => item().expanded ?? expanded()[id] ?? item().defaultExpanded ?? true;\n const toggle = () => {\n if (item().expanded !== undefined) props.navigation.onExpandedChange?.(id, !open());\n else setExpanded((current) => ({ ...current, [id]: !open() }));\n };\n const content = () => (\n <>\n <Show when={item().icon}>\n <i class={item().icon} {...iconAccessibility(item().iconLabel)} />\n </Show>\n <span class=\"k2b-navigation__copy\">\n <span>{item().label}</span>\n <Show when={item().description}>\n <small>{item().description}</small>\n </Show>\n </span>\n <Show when={item().color}>\n <span class=\"k2b-navigation__color\" style={{ \"background-color\": item().color }} />\n </Show>\n <Show when={item().badge !== undefined}>\n <span class=\"k2b-navigation__badge\">{item().badge}</span>\n </Show>\n {/* `null` keeps the empty slot, so a status that appears later takes no width from the label. */}\n <Show when={item().status !== undefined}>\n <span class=\"k2b-navigation__status\" data-tone={item().status?.tone} title={item().status?.label}>\n <Show when={item().status}>\n {(status) => (\n <>\n <i class={status().icon} aria-hidden=\"true\" />\n <span class=\"k2b-sr-only\">{status().label}</span>\n </>\n )}\n </Show>\n </span>\n </Show>\n </>\n );\n return (\n <Show\n when={item().section}\n fallback={\n <li>\n <div class=\"k2b-navigation__row\" data-active={item().active || undefined}>\n <Show\n when={item().href}\n fallback={\n <button\n type=\"button\"\n class=\"k2b-navigation__control\"\n disabled={disabled()}\n aria-expanded={!item().action ? open() : undefined}\n onClick={() => (item().action ? void activate(id, item().keepOpen) : toggle())}\n >\n {content()}\n <Show when={!item().action && item().children?.length}>\n <i class={open() ? \"ti ti-chevron-down\" : \"ti ti-chevron-right\"} aria-hidden=\"true\" />\n </Show>\n </button>\n }\n >\n {(href) => (\n <Link\n class=\"k2b-navigation__control\"\n href={href()}\n aria-current={item().active ? \"page\" : undefined}\n aria-disabled={disabled() || undefined}\n tabIndex={disabled() ? -1 : undefined}\n scroll={item().scroll}\n onNavigate={async (event) => {\n if (!(await ready())) return;\n if (item().navigation === \"enhanced\" && props.navigation.onNavigate) await props.navigation.onNavigate(event);\n else event.fallback();\n }}\n onClick={(event) => {\n if (disabled()) {\n event.preventDefault();\n return;\n }\n if (!item().action || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)\n return;\n // Local opening can fetch before committing navigation, outside a view transition.\n event.preventDefault();\n void activate(id);\n }}\n >\n {content()}\n </Link>\n )}\n </Show>\n <Show when={(item().href || item().action) && item().children?.length}>\n <button\n type=\"button\"\n class=\"k2b-navigation__disclosure\"\n aria-label={item().label}\n aria-expanded={open()}\n disabled={disabled()}\n onClick={toggle}\n >\n <i class={open() ? \"ti ti-chevron-down\" : \"ti ti-chevron-right\"} aria-hidden=\"true\" />\n </button>\n </Show>\n {/* Keyed by id like the rows, so a button keeps its identity and focus when the model updates. */}\n <For each={(item().inlineActions ?? []).map((action) => action.id)}>\n {(actionId) => {\n const action = () => item().inlineActions?.find((entry) => entry.id === actionId);\n return (\n <Show when={action()}>\n {(current) => (\n <IconButton\n class=\"k2b-navigation__inline-action\"\n label={current().label}\n disabled={disabled() || current().disabled}\n onClick={() => void activate(actionId)}\n >\n <i class={current().icon} aria-hidden=\"true\" />\n </IconButton>\n )}\n </Show>\n );\n }}\n </For>\n <Show when={item().actions?.length}>\n <Dropdown.Root\n items={(item().actions ?? []).map((action) => ({\n label: action.label,\n icon: action.icon,\n description: action.description,\n disabled: disabled() || action.disabled,\n action: () => {\n if (action.href)\n void ready().then((allowed) => {\n if (allowed) documentNavigate(action.href!);\n });\n else void activate(action.id);\n },\n }))}\n >\n <Dropdown.Trigger iconOnly label={`${item().label}: ${workspaceMessages().rowAction}`} variant=\"ghost\">\n <i class=\"ti ti-dots\" aria-hidden=\"true\" />\n </Dropdown.Trigger>\n </Dropdown.Root>\n </Show>\n </div>\n <Show when={item().children?.length}>\n <div hidden={!open()}>\n <Rows items={item().children ?? []} disabled={disabled()} />\n </div>\n </Show>\n </li>\n }\n >\n <li class=\"k2b-navigation__section\">\n <div class=\"k2b-navigation__heading\" id={headingId}>\n {item().label}\n </div>\n <Rows items={item().children ?? []} disabled={disabled()} labelledBy={headingId} />\n </li>\n </Show>\n );\n }}\n </For>\n </ul>\n );\n return (\n <nav class=\"k2b-navigation\" aria-label={props.label}>\n <Rows items={props.navigation.items()} />\n </nav>\n );\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["layout/Navigation.tsx"],"sourcesContent":["import { documentNavigate, Link } from \"@k2b/ssr/nav\";\nimport { createSignal, createUniqueId, For, Show } from \"solid-js\";\nimport { IconButton } from \"../actions/Button\";\nimport { Dropdown } from \"../actions/Dropdown\";\nimport type { NavigationController, NavigationItem } from \"./navigation-model\";\nimport { AppWorkspaceSidebarSwitcher } from \"./SidebarSwitcher\";\nimport { useWorkspaceMessages } from \"./workspace-messages\";\n\nexport type NavigationProps = {\n navigation: NavigationController;\n label: string;\n /** A host can finish dismissing its menu before the selected operation runs. */\n beforeSelect?: () => boolean | void | Promise<boolean | void>;\n};\n\n/** A named icon is an image screen readers announce; any other icon is decoration. */\nconst iconAccessibility = (label: string | undefined) => (label ? { role: \"img\" as const, \"aria-label\": label } : { \"aria-hidden\": true });\n\nexport default function Navigation(props: NavigationProps) {\n const workspaceMessages = useWorkspaceMessages();\n const [expanded, setExpanded] = createSignal<Record<string, boolean>>({});\n const ready = async () => (await props.beforeSelect?.()) !== false;\n /** `keepOpen` changes only what the menu shows, so the host does not dismiss it first. */\n const activate = async (id: string, keepOpen = false) => {\n if (keepOpen || (await ready())) await props.navigation.activate(id);\n };\n const Rows = (rows: { items: readonly NavigationItem[]; disabled?: boolean; labelledBy?: string }) => (\n <ul class=\"k2b-navigation__list\" aria-labelledby={rows.labelledBy}>\n <For each={rows.items.map((item) => item.id)}>\n {(id) => {\n const item = () => rows.items.find((entry) => entry.id === id)!;\n const disabled = () => rows.disabled || item().disabled;\n const headingId = `k2b-navigation-section-${createUniqueId()}`;\n // `expanded` hands disclosure to the item's owner; every other toggle stays local to this renderer.\n const open = () => item().expanded ?? expanded()[id] ?? item().defaultExpanded ?? true;\n const toggle = () => {\n if (item().expanded !== undefined) props.navigation.onExpandedChange?.(id, !open());\n else setExpanded((current) => ({ ...current, [id]: !open() }));\n };\n const content = () => (\n <>\n <Show when={item().icon}>\n <i class={item().icon} {...iconAccessibility(item().iconLabel)} />\n </Show>\n <span class=\"k2b-navigation__copy\">\n <span>{item().label}</span>\n <Show when={item().description}>\n <small>{item().description}</small>\n </Show>\n </span>\n <Show when={item().color}>\n <span class=\"k2b-navigation__color\" style={{ \"background-color\": item().color }} />\n </Show>\n <Show when={item().badge !== undefined}>\n <span class=\"k2b-navigation__badge\">{item().badge}</span>\n </Show>\n {/* `null` keeps the empty slot, so a status that appears later takes no width from the label. */}\n <Show when={item().status !== undefined}>\n <span class=\"k2b-navigation__status\" data-tone={item().status?.tone} title={item().status?.label}>\n <Show when={item().status}>\n {(status) => (\n <>\n <i class={status().icon} aria-hidden=\"true\" />\n <span class=\"k2b-sr-only\">{status().label}</span>\n </>\n )}\n </Show>\n </span>\n </Show>\n </>\n );\n return (\n <Show\n when={item().section}\n fallback={\n <li>\n <div class=\"k2b-navigation__row\" data-active={item().active || undefined}>\n <Show\n when={item().href}\n fallback={\n <button\n type=\"button\"\n class=\"k2b-navigation__control\"\n disabled={disabled()}\n aria-expanded={!item().action ? open() : undefined}\n onClick={() => (item().action ? void activate(id, item().keepOpen) : toggle())}\n >\n {content()}\n <Show when={!item().action && item().children?.length}>\n <i class={open() ? \"ti ti-chevron-down\" : \"ti ti-chevron-right\"} aria-hidden=\"true\" />\n </Show>\n </button>\n }\n >\n {(href) => (\n <Link\n class=\"k2b-navigation__control\"\n href={href()}\n aria-current={item().active ? \"page\" : undefined}\n aria-disabled={disabled() || undefined}\n tabIndex={disabled() ? -1 : undefined}\n scroll={item().scroll}\n onNavigate={async (event) => {\n if (!(await ready())) return;\n if (item().navigation === \"enhanced\" && props.navigation.onNavigate) await props.navigation.onNavigate(event);\n else event.fallback();\n }}\n onClick={(event) => {\n if (disabled()) {\n event.preventDefault();\n return;\n }\n if (!item().action || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)\n return;\n // Local opening can fetch before committing navigation, outside a view transition.\n event.preventDefault();\n void activate(id);\n }}\n >\n {content()}\n </Link>\n )}\n </Show>\n <Show when={(item().href || item().action) && item().children?.length}>\n <button\n type=\"button\"\n class=\"k2b-navigation__disclosure\"\n aria-label={item().label}\n aria-expanded={open()}\n disabled={disabled()}\n onClick={toggle}\n >\n <i class={open() ? \"ti ti-chevron-down\" : \"ti ti-chevron-right\"} aria-hidden=\"true\" />\n </button>\n </Show>\n {/* Keyed by id like the rows, so a button keeps its identity and focus when the model updates. */}\n <For each={(item().inlineActions ?? []).map((action) => action.id)}>\n {(actionId) => {\n const action = () => item().inlineActions?.find((entry) => entry.id === actionId);\n return (\n <Show when={action()}>\n {(current) => (\n <IconButton\n class=\"k2b-navigation__inline-action\"\n label={current().label}\n disabled={disabled() || current().disabled}\n onClick={() => void activate(actionId)}\n >\n <i class={current().icon} aria-hidden=\"true\" />\n </IconButton>\n )}\n </Show>\n );\n }}\n </For>\n <Show when={item().actions?.length}>\n <Dropdown.Root\n items={(item().actions ?? []).map((action) => ({\n label: action.label,\n icon: action.icon,\n description: action.description,\n disabled: disabled() || action.disabled,\n action: () => {\n if (action.href)\n void ready().then((allowed) => {\n if (allowed) documentNavigate(action.href!);\n });\n else void activate(action.id);\n },\n }))}\n >\n <Dropdown.Trigger iconOnly label={`${item().label}: ${workspaceMessages().rowAction}`} variant=\"ghost\">\n <i class=\"ti ti-dots\" aria-hidden=\"true\" />\n </Dropdown.Trigger>\n </Dropdown.Root>\n </Show>\n </div>\n <Show when={item().children?.length}>\n <div hidden={!open()}>\n <Rows items={item().children ?? []} disabled={disabled()} />\n </div>\n </Show>\n </li>\n }\n >\n <li class=\"k2b-navigation__section\">\n <div class=\"k2b-navigation__heading\" id={headingId}>\n {item().label}\n </div>\n <Rows items={item().children ?? []} disabled={disabled()} labelledBy={headingId} />\n </li>\n </Show>\n );\n }}\n </For>\n </ul>\n );\n return (\n <nav class=\"k2b-navigation\" aria-label={props.label}>\n <Show when={props.navigation.switcher?.()}>\n {(switcher) => <AppWorkspaceSidebarSwitcher {...switcher()} beforeSelect={props.beforeSelect} />}\n </Show>\n <Rows items={props.navigation.items()} />\n </nav>\n );\n}\n"],"mappings":"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","ignoreList":[]}
@@ -199,7 +199,8 @@ export function PageTitle(props) {
199
199
 
200
200
  /**
201
201
  * Keeps server-rendered titles of the host's trail current: a workspace head
202
- * that never hydrates, and an app's own header title from the host's trail.
202
+ * that never hydrates, its heading, and an app's own header title from the
203
+ * host's trail.
203
204
  * When the host publishes a new trail, for example after `setPageTrail()`,
204
205
  * each such title is replaced in place by a live one with the same element,
205
206
  * class, and subtitle, and a head learns whether it now has a page above it.
@@ -218,6 +219,12 @@ export const followPageTrail = (root = document) => {
218
219
  const extra = [...element.classList].filter(name => name !== "k2b-page-title").join(" ") || undefined;
219
220
  const subtitle = element.querySelector(".k2b-page-title__subtitle");
220
221
  const subtitleNodes = subtitle ? [...subtitle.childNodes] : undefined;
222
+ // A link focused in the server title keeps focus in the live one: same kind, then same address.
223
+ const active = document.activeElement;
224
+ const focused = active && element.contains(active) ? {
225
+ kind: ["k2b-page-title__up", "k2b-page-title__back"].find(name => active.classList.contains(name)),
226
+ href: active.getAttribute("href")
227
+ } : undefined;
221
228
  // The live title takes the server title's place, before whatever follows it in the row.
222
229
  const place = document.createTextNode("");
223
230
  row.insertBefore(place, element);
@@ -240,7 +247,17 @@ export const followPageTrail = (root = document) => {
240
247
  }), place);
241
248
  return dispose;
242
249
  }));
250
+ const upgraded = place.previousSibling;
251
+ if (focused && upgraded instanceof HTMLElement && upgraded.classList.contains("k2b-page-title")) {
252
+ const links = focused.kind ? [...upgraded.querySelectorAll(`a.${focused.kind}`)] : [];
253
+ const link = links.find(candidate => candidate.getAttribute("href") === focused.href) ?? links[0] ?? upgraded.querySelector("a[data-k2b-page-trail-up]");
254
+ link?.focus({
255
+ preventScroll: true
256
+ });
257
+ }
243
258
  }
259
+ // A server-rendered workspace heading carries only the title.
260
+ 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;
244
261
  // The row's `data-trail` decides whether it shows; it follows the depth.
245
262
  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";
246
263
  };
@@ -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 // A link focused in the server title keeps focus in the live one: same kind, then same address.\n const active = document.activeElement;\n const focused =\n active && element.contains(active)\n ? {\n kind: [\"k2b-page-title__up\", \"k2b-page-title__back\"].find((name) => active.classList.contains(name)),\n href: active.getAttribute(\"href\"),\n }\n : 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 const upgraded = place.previousSibling;\n if (focused && upgraded instanceof HTMLElement && upgraded.classList.contains(\"k2b-page-title\")) {\n const links = focused.kind ? [...upgraded.querySelectorAll<HTMLElement>(`a.${focused.kind}`)] : [];\n const link =\n links.find((candidate) => candidate.getAttribute(\"href\") === focused.href) ??\n links[0] ??\n upgraded.querySelector<HTMLElement>(\"a[data-k2b-page-trail-up]\");\n link?.focus({ preventScroll: true });\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":[]}
@@ -0,0 +1,271 @@
1
+ import { ssr as _$ssr } from "solid-js/web";
2
+ import { createComponent as _$createComponent } from "solid-js/web";
3
+ import { ssrStyle as _$ssrStyle } from "solid-js/web";
4
+ import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
5
+ import { escape as _$escape } from "solid-js/web";
6
+ var _tmpl$ = ["<div class=\"k2b-choice-search k2b-sidebar-switcher__search\"><i class=\"ti ti-search\" aria-hidden=\"true\"></i><input type=\"search\" data-switcher-row", " aria-controls=\"", "\"></div>"],
7
+ _tmpl$2 = ["<ul class=\"k2b-sidebar-switcher__actions\">", "</ul>"],
8
+ _tmpl$3 = ["<div class=\"", "\"", "><button type=\"button\" class=\"k2b-sidebar-switcher__trigger\"", " aria-describedby=\"", "\" aria-haspopup=\"dialog\"", "><span class=\"k2b-sidebar-switcher__icon\" style=\"", "\" aria-hidden=\"true\"><i", "></i></span><span class=\"k2b-sidebar-switcher__copy\" aria-hidden=\"true\"><span>", "</span>", "</span><i class=\"ti ti-selector k2b-sidebar-switcher__chevron\" aria-hidden=\"true\"></i><span id=\"", "\" class=\"k2b-sr-only\">", "</span></button><div", " popover=\"auto\" tabindex=\"-1\" role=\"dialog\"", " class=\"k2b-sidebar-switcher__panel\">", "<ul id=\"", "\" class=\"k2b-sidebar-switcher__options\">", "</ul>", "</div></div>"],
9
+ _tmpl$4 = ["<small>", "</small>"],
10
+ _tmpl$5 = ["<li class=\"k2b-choice-status\" role=\"status\"", ">", "</li>"],
11
+ _tmpl$6 = "<i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\"></i>",
12
+ _tmpl$7 = ["<span>", "</span>"],
13
+ _tmpl$8 = ["<button type=\"button\" data-switcher-row>", "</button>"],
14
+ _tmpl$9 = ["<li class=\"k2b-choice-status\" role=\"status\">", "</li>"],
15
+ _tmpl$0 = ["<li><a", " class=\"k2b-dropdown__item k2b-sidebar-switcher__option\" data-switcher-row data-switcher-option", "><span class=\"k2b-sidebar-switcher__option-icon\" style=\"", "\" aria-hidden=\"true\"><i", "></i></span><span class=\"k2b-dropdown__copy\"><span>", "</span>", "</span><i class=\"ti ti-check k2b-dropdown__check\"", " aria-hidden=\"true\"></i></a></li>"],
16
+ _tmpl$1 = ["<li>", "</li>"],
17
+ _tmpl$10 = ["<button type=\"button\" class=\"k2b-dropdown__item\" data-switcher-row", "><i", " aria-hidden=\"true\"></i><span class=\"k2b-dropdown__copy\"><span>", "</span></span></button>"],
18
+ _tmpl$11 = ["<a", " class=\"k2b-dropdown__item\" data-switcher-row", "><i", " aria-hidden=\"true\"></i><span class=\"k2b-dropdown__copy\"><span>", "</span></span></a>"];
19
+ import { documentNavigate } from "@k2b/ssr/nav";
20
+ import { createMemo, createSignal, createUniqueId, For, onCleanup, Show } from "solid-js";
21
+ import { isServer } from "solid-js/web";
22
+ import { dropdownPosition } from "../actions/Dropdown";
23
+ import { returnFocus, ringOnReturn } from "../internal/focus-return";
24
+ import { useUiMessages } from "../intl/messages";
25
+
26
+ /** One container a person can switch to, such as a notebook, a space, or a mailbox. */
27
+
28
+ /** A row below the containers, such as "New notebook" or "All notebooks". */
29
+
30
+ /** From this many containers the panel scrolls, so it offers a search field. */
31
+ export const APP_WORKSPACE_SWITCHER_SEARCH_FROM = 8;
32
+
33
+ /** The space between trigger and panel, and the panel's distance from the viewport edges, as for every dropdown. */
34
+ const SWITCHER_GAP = 4;
35
+ const SWITCHER_INSET = 8;
36
+ const iconClass = icon => icon.split(/\s+/).includes("ti") ? icon : `ti ${icon}`;
37
+ const tint = color => color ? {
38
+ "--k2b-sidebar-switcher-color": color
39
+ } : undefined;
40
+ const plainClick = event => event.button === 0 && !(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey);
41
+
42
+ /**
43
+ * The current container at the top of a sidebar: its icon, name, and an optional fact, with a chevron that opens the
44
+ * other containers, a search field when there are many, and actions such as "New …" and "All …". The rows are links,
45
+ * so a container also opens in a new tab. In a collapsed rail it shows only the icon and opens beside the rail.
46
+ */
47
+ export function AppWorkspaceSidebarSwitcher(props) {
48
+ const messages = useUiMessages();
49
+ const id = `k2b-sidebar-switcher-${createUniqueId().replace(/[^a-zA-Z0-9_-]/g, "-")}`;
50
+ const [open, setOpen] = createSignal(false);
51
+ const [query, setQuery] = createSignal("");
52
+ let trigger;
53
+ let panel;
54
+ let search;
55
+ let keyboardOpened = true;
56
+ /** Whether the panel was open when a press on the button began; the press itself light-dismisses it first. */
57
+ let openAtPress;
58
+ const searchable = () => (props.options?.length ?? 0) >= APP_WORKSPACE_SWITCHER_SEARCH_FROM;
59
+ const visible = createMemo(() => {
60
+ const needle = query().trim().toLocaleLowerCase();
61
+ const options = props.options ?? [];
62
+ if (!needle) return options;
63
+ return options.filter(option => [option.label, option.description].some(part => part?.toLocaleLowerCase().includes(needle)));
64
+ });
65
+ const load = () => {
66
+ if (props.options === undefined) props.onLoad?.();
67
+ };
68
+
69
+ // The collapsed rail is a narrow column from 64 rem, so the panel opens beside it instead of below.
70
+ const inRail = () => Boolean(trigger?.closest('[data-sidebar-collapsed="true"] .k2b-app-workspace__sidebar[data-workspace-collapsible="true"]') && window.matchMedia("(min-width: 64rem)").matches);
71
+ /** Whether the panel opens above its trigger; chosen on opening and on a viewport resize only. */
72
+ let above = false;
73
+ /**
74
+ * Places the panel beside its trigger. `choose` picks its side again; otherwise it keeps the side, so arriving
75
+ * options, an error, or a narrowed search never flip it to the other side while it is open.
76
+ */
77
+ const place = (choose = false) => {
78
+ if (!trigger || !panel) return;
79
+ const rect = trigger.getBoundingClientRect();
80
+ const viewport = {
81
+ width: window.innerWidth,
82
+ height: window.innerHeight
83
+ };
84
+ const rail = inRail();
85
+ // As wide as the sidebar row and never narrower than a menu, so long names keep their words; fixed while open,
86
+ // so filtering never changes the panel's width.
87
+ panel.style.setProperty("--k2b-sidebar-switcher-width", `${rail ? 0 : Math.round(rect.width)}px`);
88
+ if (rail) {
89
+ panel.style.removeProperty("--k2b-sidebar-switcher-room");
90
+ panel.style.removeProperty("min-height");
91
+ panel.style.bottom = "auto";
92
+ const position = dropdownPosition(rect, panel.getBoundingClientRect(), "right-start", viewport);
93
+ panel.style.left = `${position.left}px`;
94
+ panel.style.top = `${position.top}px`;
95
+ return;
96
+ }
97
+ // Below the trigger, or above it when the panel at its tallest does not fit below and there is more room above,
98
+ // as in the lower half of the phone menu. Its tallest is its CSS max-height, min(28rem, 80dvh), so the side does
99
+ // not depend on what the panel shows at the moment. It never covers its trigger: it takes at most the room on its
100
+ // side, and its list scrolls.
101
+ const roomBelow = viewport.height - rect.bottom - SWITCHER_GAP - SWITCHER_INSET;
102
+ const roomAbove = rect.top - SWITCHER_GAP - SWITCHER_INSET;
103
+ if (choose) {
104
+ const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
105
+ above = Math.min(28 * rem, 0.8 * viewport.height) > roomBelow && roomAbove > roomBelow;
106
+ panel.style.removeProperty("min-height");
107
+ }
108
+ const room = Math.max(0, above ? roomAbove : roomBelow);
109
+ panel.style.setProperty("--k2b-sidebar-switcher-room", `${room}px`);
110
+ panel.style.left = `${dropdownPosition(rect, panel.getBoundingClientRect(), "bottom-right", viewport).left}px`;
111
+ if (above) {
112
+ // Its bottom edge stays beside the trigger, and it does not shrink while open, so a narrowed search keeps the
113
+ // search field where the person is typing.
114
+ const height = Math.min(room, Math.max(panel.getBoundingClientRect().height, Number.parseFloat(panel.style.minHeight) || 0));
115
+ panel.style.minHeight = `${height}px`;
116
+ panel.style.top = "auto";
117
+ panel.style.bottom = `${document.documentElement.clientHeight - rect.top + SWITCHER_GAP}px`;
118
+ } else {
119
+ panel.style.top = `${rect.bottom + SWITCHER_GAP}px`;
120
+ panel.style.bottom = "auto";
121
+ }
122
+ };
123
+ const reposition = () => open() && place();
124
+ const resize = () => open() && place(true);
125
+ // Arriving options, an error, or the search field grow the panel after it opened, so its size places it too.
126
+ let resizing;
127
+ const listen = on => {
128
+ const method = on ? "addEventListener" : "removeEventListener";
129
+ window[method]("resize", resize);
130
+ window[method]("scroll", reposition, true);
131
+ if (on && panel) {
132
+ resizing ??= new ResizeObserver(reposition);
133
+ resizing.observe(panel);
134
+ } else resizing?.disconnect();
135
+ };
136
+ // Server rendering cleans up too, where no window exists.
137
+ onCleanup(() => isServer || listen(false));
138
+ /** Lets the host dismiss its menu first, then opens the link or runs the action unless the host cancels. */
139
+ const choose = run => {
140
+ hide(false);
141
+ if (!props.beforeSelect) return run();
142
+ void Promise.resolve(props.beforeSelect()).then(allowed => {
143
+ if (allowed !== false) run();
144
+ });
145
+ };
146
+ const follow = event => {
147
+ if (!plainClick(event)) return;
148
+ if (!props.beforeSelect) return hide(false);
149
+ event.preventDefault();
150
+ const href = event.currentTarget.href;
151
+ choose(() => documentNavigate(href));
152
+ };
153
+ const rows = () => panel ? Array.from(panel.querySelectorAll("[data-switcher-row]:not([aria-disabled='true'])")) : [];
154
+ const focusRow = index => {
155
+ const all = rows();
156
+ if (all.length) all[(index + all.length) % all.length]?.focus();
157
+ };
158
+ const show = (focus = "start") => {
159
+ if (!panel || panel.matches(":popover-open")) return;
160
+ load();
161
+ keyboardOpened = ringOnReturn(trigger);
162
+ setQuery("");
163
+ panel.showPopover();
164
+ setOpen(true);
165
+ listen(true);
166
+ place(true);
167
+ queueMicrotask(() => {
168
+ // On touch the search field waits for a tap, so the keyboard does not cover the list on opening.
169
+ if (searchable() && focus === "start" && !window.matchMedia("(pointer: coarse)").matches) search?.focus();else if (focus === "end") focusRow(-1);
170
+ // While the list loads, the panel holds focus, so arriving rows do not move it.
171
+ else (panel?.querySelector('[aria-current="true"]') ?? (props.options ? rows()[0] : panel))?.focus();
172
+ });
173
+ };
174
+ const hide = restoreFocus => {
175
+ if (restoreFocus) returnFocus(trigger, keyboardOpened);
176
+ if (panel?.matches(":popover-open")) panel.hidePopover();
177
+ };
178
+ const onPanelKeyDown = event => {
179
+ const target = event.target;
180
+ const all = rows();
181
+ const index = all.indexOf(target);
182
+ if (event.key === "Escape") {
183
+ // The panel closes alone; an overlay sidebar around it stays open.
184
+ event.preventDefault();
185
+ event.stopPropagation();
186
+ hide(true);
187
+ } else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
188
+ event.preventDefault();
189
+ focusRow(index + (event.key === "ArrowDown" ? 1 : -1));
190
+ } else if ((event.key === "Home" || event.key === "End") && target !== search) {
191
+ event.preventDefault();
192
+ focusRow(event.key === "Home" ? 0 : -1);
193
+ } else if (event.key === "Enter" && target === search) {
194
+ event.preventDefault();
195
+ panel?.querySelector("[data-switcher-option]")?.click();
196
+ } else if (search && target !== search && event.key.length === 1 && event.key !== " " && !(event.metaKey || event.ctrlKey || event.altKey)) {
197
+ // Typing anywhere in a long list searches it.
198
+ event.preventDefault();
199
+ setQuery(query() + event.key);
200
+ search.focus();
201
+ }
202
+ };
203
+ return _$ssr(_tmpl$3, `k2b-sidebar-switcher ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("data-description", props.description ? "" : _$escape(undefined, true), false), _$ssrAttribute("aria-label", _$escape(props.label, true), false), `${_$escape(id, true)}-purpose`, _$ssrAttribute("aria-expanded", _$escape(open(), true), false) + _$ssrAttribute("aria-controls", _$escape(id, true), false) + _$ssrAttribute("title", _$escape(props.label, true), false), _$ssrStyle(tint(props.color)), _$ssrAttribute("class", _$escape(iconClass(props.icon), true), false), _$escape(props.label), _$escape(_$createComponent(Show, {
204
+ get when() {
205
+ return props.description;
206
+ },
207
+ children: description => _$ssr(_tmpl$4, _$escape(description()))
208
+ })), `${_$escape(id, true)}-purpose`, _$escape([props.description, props.switchLabel].filter(Boolean).join(", ")), _$ssrAttribute("id", _$escape(id, true), false), _$ssrAttribute("aria-label", _$escape(props.switchLabel, true), false), _$escape(_$createComponent(Show, {
209
+ get when() {
210
+ return searchable();
211
+ },
212
+ get children() {
213
+ return _$ssr(_tmpl$, _$ssrAttribute("value", _$escape(query(), true), false) + _$ssrAttribute("placeholder", _$escape(messages().search, true), false) + _$ssrAttribute("aria-label", _$escape(messages().searchLabel, true), false), `${_$escape(id, true)}-options`);
214
+ }
215
+ })), `${_$escape(id, true)}-options`, _$escape(_$createComponent(Show, {
216
+ get when() {
217
+ return props.options;
218
+ },
219
+ get fallback() {
220
+ return _$ssr(_tmpl$5, _$ssrAttribute("data-tone", props.error ? "danger" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
221
+ get when() {
222
+ return props.error;
223
+ },
224
+ get fallback() {
225
+ return [_$ssr(_tmpl$6), _$ssr(_tmpl$7, _$escape(messages().loading))];
226
+ },
227
+ children: error => [_$ssr(_tmpl$7, _$escape(error())), _$ssr(_tmpl$8, _$escape(messages().retry))]
228
+ })));
229
+ },
230
+ get children() {
231
+ return _$createComponent(For, {
232
+ get each() {
233
+ return visible();
234
+ },
235
+ get fallback() {
236
+ return _$ssr(_tmpl$9, _$escape(messages().noResults));
237
+ },
238
+ children: option => {
239
+ const current = () => option.id === props.currentId;
240
+ return _$ssr(_tmpl$0, _$ssrAttribute("href", _$escape(option.href, true), false), _$ssrAttribute("aria-current", current() ? "true" : _$escape(undefined, true), false), _$ssrStyle(tint(option.color ?? props.color)), _$ssrAttribute("class", _$escape(iconClass(option.icon ?? props.icon), true), false), _$escape(option.label), _$escape(_$createComponent(Show, {
241
+ get when() {
242
+ return option.description;
243
+ },
244
+ children: description => _$ssr(_tmpl$4, _$escape(description()))
245
+ })), _$ssrAttribute("data-hidden", current() ? _$escape(undefined, true) : "true", false));
246
+ }
247
+ });
248
+ }
249
+ })), _$escape(_$createComponent(Show, {
250
+ get when() {
251
+ return props.actions?.length;
252
+ },
253
+ get children() {
254
+ return _$ssr(_tmpl$2, _$escape(_$createComponent(For, {
255
+ get each() {
256
+ return props.actions;
257
+ },
258
+ children: item => _$ssr(_tmpl$1, _$escape(_$createComponent(Show, {
259
+ get when() {
260
+ return item.href;
261
+ },
262
+ get fallback() {
263
+ return _$ssr(_tmpl$10, _$ssrAttribute("disabled", item.disabled, true) + _$ssrAttribute("aria-disabled", item.disabled ? "true" : _$escape(undefined, true), false), _$ssrAttribute("class", _$escape(iconClass(item.icon), true), false), _$escape(item.label));
264
+ },
265
+ children: href => _$ssr(_tmpl$11, _$ssrAttribute("href", item.disabled ? _$escape(undefined, true) : _$escape(href(), true), false), _$ssrAttribute("aria-disabled", item.disabled ? "true" : _$escape(undefined, true), false), _$ssrAttribute("class", _$escape(iconClass(item.icon), true), false), _$escape(item.label))
266
+ })))
267
+ })));
268
+ }
269
+ })));
270
+ }
271
+ //# sourceMappingURL=SidebarSwitcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["layout/SidebarSwitcher.tsx"],"sourcesContent":["import { documentNavigate } from \"@k2b/ssr/nav\";\nimport { createMemo, createSignal, createUniqueId, For, type JSX, onCleanup, Show } from \"solid-js\";\nimport { isServer } from \"solid-js/web\";\nimport { dropdownPosition } from \"../actions/Dropdown\";\nimport { returnFocus, ringOnReturn } from \"../internal/focus-return\";\nimport { useUiMessages } from \"../intl/messages\";\n\n/** One container a person can switch to, such as a notebook, a space, or a mailbox. */\nexport type AppWorkspaceSwitcherOption = {\n id: string;\n label: string;\n /** One secondary fact, such as a mailbox's address. */\n description?: string;\n /** Tabler icon class; without it the row takes the switcher's icon. */\n icon?: string;\n /** Tints the icon; without it the app accent does. */\n color?: string;\n href: string;\n};\n\n/** A row below the containers, such as \"New notebook\" or \"All notebooks\". */\nexport type AppWorkspaceSwitcherAction = {\n id: string;\n label: string;\n icon: string;\n disabled?: boolean;\n} & ({ href: string; action?: never } | { action: () => void; href?: never });\n\nexport type AppWorkspaceSidebarSwitcherProps = {\n /** The current container's ID among `options`. */\n currentId: string;\n /** The current container's name. */\n label: string;\n /** One secondary fact under the name, such as a mailbox's address. */\n description?: string;\n icon: string;\n /** Tints the icon; without it the app accent does. */\n color?: string;\n /** Names the button's purpose and its panel, such as \"Switch notebook\". */\n switchLabel: string;\n /** Every container the person can switch to, the current one included; `undefined` until they are loaded. */\n options?: readonly AppWorkspaceSwitcherOption[];\n /** Shown in place of the options when loading them failed; the panel offers to try again through `onLoad`. */\n error?: string;\n /**\n * Asks for `options` while they are `undefined`: when a pointer or the focus first reaches the button, so the list\n * is usually there when the panel opens, on every opening, and from the panel's retry. Ignore repeated calls while a\n * request runs.\n */\n onLoad?: () => void;\n actions?: readonly AppWorkspaceSwitcherAction[];\n /**\n * A host menu around the switcher, such as the phone menu, finishes dismissing itself before a chosen container\n * opens or an action runs; `false` cancels the choice. Navigation passes its own.\n */\n beforeSelect?: () => boolean | void | Promise<boolean | void>;\n /** Sizes the switcher in its place, such as `flex-1` beside a button in one row. */\n class?: string;\n};\n\n/** From this many containers the panel scrolls, so it offers a search field. */\nexport const APP_WORKSPACE_SWITCHER_SEARCH_FROM = 8;\n\n/** The space between trigger and panel, and the panel's distance from the viewport edges, as for every dropdown. */\nconst SWITCHER_GAP = 4;\nconst SWITCHER_INSET = 8;\n\nconst iconClass = (icon: string) => (icon.split(/\\s+/).includes(\"ti\") ? icon : `ti ${icon}`);\nconst tint = (color: string | undefined) => (color ? { \"--k2b-sidebar-switcher-color\": color } : undefined);\nconst plainClick = (event: MouseEvent) => event.button === 0 && !(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey);\n\n/**\n * The current container at the top of a sidebar: its icon, name, and an optional fact, with a chevron that opens the\n * other containers, a search field when there are many, and actions such as \"New …\" and \"All …\". The rows are links,\n * so a container also opens in a new tab. In a collapsed rail it shows only the icon and opens beside the rail.\n */\nexport function AppWorkspaceSidebarSwitcher(props: AppWorkspaceSidebarSwitcherProps): JSX.Element {\n const messages = useUiMessages();\n const id = `k2b-sidebar-switcher-${createUniqueId().replace(/[^a-zA-Z0-9_-]/g, \"-\")}`;\n const [open, setOpen] = createSignal(false);\n const [query, setQuery] = createSignal(\"\");\n let trigger: HTMLButtonElement | undefined;\n let panel: HTMLDivElement | undefined;\n let search: HTMLInputElement | undefined;\n let keyboardOpened = true;\n /** Whether the panel was open when a press on the button began; the press itself light-dismisses it first. */\n let openAtPress: boolean | undefined;\n\n const searchable = () => (props.options?.length ?? 0) >= APP_WORKSPACE_SWITCHER_SEARCH_FROM;\n const visible = createMemo(() => {\n const needle = query().trim().toLocaleLowerCase();\n const options = props.options ?? [];\n if (!needle) return options;\n return options.filter((option) => [option.label, option.description].some((part) => part?.toLocaleLowerCase().includes(needle)));\n });\n const load = () => {\n if (props.options === undefined) props.onLoad?.();\n };\n\n // The collapsed rail is a narrow column from 64 rem, so the panel opens beside it instead of below.\n const inRail = () =>\n Boolean(\n trigger?.closest('[data-sidebar-collapsed=\"true\"] .k2b-app-workspace__sidebar[data-workspace-collapsible=\"true\"]') &&\n window.matchMedia(\"(min-width: 64rem)\").matches,\n );\n /** Whether the panel opens above its trigger; chosen on opening and on a viewport resize only. */\n let above = false;\n /**\n * Places the panel beside its trigger. `choose` picks its side again; otherwise it keeps the side, so arriving\n * options, an error, or a narrowed search never flip it to the other side while it is open.\n */\n const place = (choose = false) => {\n if (!trigger || !panel) return;\n const rect = trigger.getBoundingClientRect();\n const viewport = { width: window.innerWidth, height: window.innerHeight };\n const rail = inRail();\n // As wide as the sidebar row and never narrower than a menu, so long names keep their words; fixed while open,\n // so filtering never changes the panel's width.\n panel.style.setProperty(\"--k2b-sidebar-switcher-width\", `${rail ? 0 : Math.round(rect.width)}px`);\n if (rail) {\n panel.style.removeProperty(\"--k2b-sidebar-switcher-room\");\n panel.style.removeProperty(\"min-height\");\n panel.style.bottom = \"auto\";\n const position = dropdownPosition(rect, panel.getBoundingClientRect(), \"right-start\", viewport);\n panel.style.left = `${position.left}px`;\n panel.style.top = `${position.top}px`;\n return;\n }\n // Below the trigger, or above it when the panel at its tallest does not fit below and there is more room above,\n // as in the lower half of the phone menu. Its tallest is its CSS max-height, min(28rem, 80dvh), so the side does\n // not depend on what the panel shows at the moment. It never covers its trigger: it takes at most the room on its\n // side, and its list scrolls.\n const roomBelow = viewport.height - rect.bottom - SWITCHER_GAP - SWITCHER_INSET;\n const roomAbove = rect.top - SWITCHER_GAP - SWITCHER_INSET;\n if (choose) {\n const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;\n above = Math.min(28 * rem, 0.8 * viewport.height) > roomBelow && roomAbove > roomBelow;\n panel.style.removeProperty(\"min-height\");\n }\n const room = Math.max(0, above ? roomAbove : roomBelow);\n panel.style.setProperty(\"--k2b-sidebar-switcher-room\", `${room}px`);\n panel.style.left = `${dropdownPosition(rect, panel.getBoundingClientRect(), \"bottom-right\", viewport).left}px`;\n if (above) {\n // Its bottom edge stays beside the trigger, and it does not shrink while open, so a narrowed search keeps the\n // search field where the person is typing.\n const height = Math.min(room, Math.max(panel.getBoundingClientRect().height, Number.parseFloat(panel.style.minHeight) || 0));\n panel.style.minHeight = `${height}px`;\n panel.style.top = \"auto\";\n panel.style.bottom = `${document.documentElement.clientHeight - rect.top + SWITCHER_GAP}px`;\n } else {\n panel.style.top = `${rect.bottom + SWITCHER_GAP}px`;\n panel.style.bottom = \"auto\";\n }\n };\n const reposition = () => open() && place();\n const resize = () => open() && place(true);\n // Arriving options, an error, or the search field grow the panel after it opened, so its size places it too.\n let resizing: ResizeObserver | undefined;\n const listen = (on: boolean) => {\n const method = on ? \"addEventListener\" : \"removeEventListener\";\n window[method](\"resize\", resize);\n window[method](\"scroll\", reposition, true);\n if (on && panel) {\n resizing ??= new ResizeObserver(reposition);\n resizing.observe(panel);\n } else resizing?.disconnect();\n };\n // Server rendering cleans up too, where no window exists.\n onCleanup(() => isServer || listen(false));\n /** Lets the host dismiss its menu first, then opens the link or runs the action unless the host cancels. */\n const choose = (run: () => void) => {\n hide(false);\n if (!props.beforeSelect) return run();\n void Promise.resolve(props.beforeSelect()).then((allowed) => {\n if (allowed !== false) run();\n });\n };\n const follow = (event: MouseEvent & { currentTarget: HTMLAnchorElement }) => {\n if (!plainClick(event)) return;\n if (!props.beforeSelect) return hide(false);\n event.preventDefault();\n const href = event.currentTarget.href;\n choose(() => documentNavigate(href));\n };\n\n const rows = () => (panel ? Array.from(panel.querySelectorAll<HTMLElement>(\"[data-switcher-row]:not([aria-disabled='true'])\")) : []);\n const focusRow = (index: number) => {\n const all = rows();\n if (all.length) all[(index + all.length) % all.length]?.focus();\n };\n const show = (focus: \"start\" | \"end\" = \"start\") => {\n if (!panel || panel.matches(\":popover-open\")) return;\n load();\n keyboardOpened = ringOnReturn(trigger);\n setQuery(\"\");\n panel.showPopover();\n setOpen(true);\n listen(true);\n place(true);\n queueMicrotask(() => {\n // On touch the search field waits for a tap, so the keyboard does not cover the list on opening.\n if (searchable() && focus === \"start\" && !window.matchMedia(\"(pointer: coarse)\").matches) search?.focus();\n else if (focus === \"end\") focusRow(-1);\n // While the list loads, the panel holds focus, so arriving rows do not move it.\n else (panel?.querySelector<HTMLElement>('[aria-current=\"true\"]') ?? (props.options ? rows()[0] : panel))?.focus();\n });\n };\n const hide = (restoreFocus: boolean) => {\n if (restoreFocus) returnFocus(trigger, keyboardOpened);\n if (panel?.matches(\":popover-open\")) panel.hidePopover();\n };\n\n const onPanelKeyDown = (event: KeyboardEvent) => {\n const target = event.target as HTMLElement;\n const all = rows();\n const index = all.indexOf(target);\n if (event.key === \"Escape\") {\n // The panel closes alone; an overlay sidebar around it stays open.\n event.preventDefault();\n event.stopPropagation();\n hide(true);\n } else if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n focusRow(index + (event.key === \"ArrowDown\" ? 1 : -1));\n } else if ((event.key === \"Home\" || event.key === \"End\") && target !== search) {\n event.preventDefault();\n focusRow(event.key === \"Home\" ? 0 : -1);\n } else if (event.key === \"Enter\" && target === search) {\n event.preventDefault();\n panel?.querySelector<HTMLElement>(\"[data-switcher-option]\")?.click();\n } else if (\n search &&\n target !== search &&\n event.key.length === 1 &&\n event.key !== \" \" &&\n !(event.metaKey || event.ctrlKey || event.altKey)\n ) {\n // Typing anywhere in a long list searches it.\n event.preventDefault();\n setQuery(query() + event.key);\n search.focus();\n }\n };\n\n return (\n <div class={`k2b-sidebar-switcher ${props.class ?? \"\"}`} data-description={props.description ? \"\" : undefined}>\n <button\n ref={trigger}\n type=\"button\"\n class=\"k2b-sidebar-switcher__trigger\"\n aria-label={props.label}\n aria-describedby={`${id}-purpose`}\n aria-haspopup=\"dialog\"\n aria-expanded={open()}\n aria-controls={id}\n // The full name for a truncated title and for the icon in a collapsed rail.\n title={props.label}\n onPointerEnter={load}\n onFocus={load}\n onPointerDown={() => (openAtPress = panel?.matches(\":popover-open\"))}\n onClick={() => {\n const wasOpen = openAtPress ?? panel?.matches(\":popover-open\");\n openAtPress = undefined;\n if (wasOpen) hide(false);\n else show();\n }}\n onKeyDown={(event) => {\n if (event.key !== \"ArrowDown\" && event.key !== \"ArrowUp\") return;\n event.preventDefault();\n if (open()) focusRow(event.key === \"ArrowDown\" ? 0 : -1);\n else show(event.key === \"ArrowDown\" ? \"start\" : \"end\");\n }}\n >\n <span class=\"k2b-sidebar-switcher__icon\" style={tint(props.color)} aria-hidden=\"true\">\n <i class={iconClass(props.icon)} />\n </span>\n <span class=\"k2b-sidebar-switcher__copy\" aria-hidden=\"true\">\n <span>{props.label}</span>\n <Show when={props.description}>{(description) => <small>{description()}</small>}</Show>\n </span>\n <i class=\"ti ti-selector k2b-sidebar-switcher__chevron\" aria-hidden=\"true\" />\n <span id={`${id}-purpose`} class=\"k2b-sr-only\">\n {[props.description, props.switchLabel].filter(Boolean).join(\", \")}\n </span>\n </button>\n <div\n ref={panel}\n id={id}\n popover=\"auto\"\n tabIndex={-1}\n role=\"dialog\"\n aria-label={props.switchLabel}\n class=\"k2b-sidebar-switcher__panel\"\n onKeyDown={onPanelKeyDown}\n onFocusOut={(event) => {\n const next = event.relatedTarget as Node | null;\n // Tabbing out closes the panel; focus that leaves the window keeps it.\n if (next && !panel?.contains(next) && next !== trigger) hide(false);\n }}\n onToggle={(event) => {\n if ((event as ToggleEvent).newState === \"open\") return;\n setOpen(false);\n listen(false);\n }}\n >\n <Show when={searchable()}>\n <div class=\"k2b-choice-search k2b-sidebar-switcher__search\">\n <i class=\"ti ti-search\" aria-hidden=\"true\" />\n <input\n ref={search}\n type=\"search\"\n data-switcher-row\n value={query()}\n placeholder={messages().search}\n aria-label={messages().searchLabel}\n aria-controls={`${id}-options`}\n onInput={(event) => setQuery(event.currentTarget.value)}\n />\n </div>\n </Show>\n <ul id={`${id}-options`} class=\"k2b-sidebar-switcher__options\">\n <Show\n when={props.options}\n fallback={\n <li class=\"k2b-choice-status\" role=\"status\" data-tone={props.error ? \"danger\" : undefined}>\n <Show\n when={props.error}\n fallback={\n <>\n <i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\" />\n <span>{messages().loading}</span>\n </>\n }\n >\n {(error) => (\n <>\n <span>{error()}</span>\n <button type=\"button\" data-switcher-row onClick={() => props.onLoad?.()}>\n {messages().retry}\n </button>\n </>\n )}\n </Show>\n </li>\n }\n >\n <For\n each={visible()}\n fallback={\n <li class=\"k2b-choice-status\" role=\"status\">\n {messages().noResults}\n </li>\n }\n >\n {(option) => {\n const current = () => option.id === props.currentId;\n return (\n <li>\n <a\n href={option.href}\n class=\"k2b-dropdown__item k2b-sidebar-switcher__option\"\n data-switcher-row\n data-switcher-option\n aria-current={current() ? \"true\" : undefined}\n onClick={(event) => {\n // The open container is already here: choosing it only closes the panel.\n if (current() && plainClick(event)) {\n event.preventDefault();\n hide(true);\n } else follow(event);\n }}\n >\n <span class=\"k2b-sidebar-switcher__option-icon\" style={tint(option.color ?? props.color)} aria-hidden=\"true\">\n <i class={iconClass(option.icon ?? props.icon)} />\n </span>\n <span class=\"k2b-dropdown__copy\">\n <span>{option.label}</span>\n <Show when={option.description}>{(description) => <small>{description()}</small>}</Show>\n </span>\n <i class=\"ti ti-check k2b-dropdown__check\" data-hidden={current() ? undefined : \"true\"} aria-hidden=\"true\" />\n </a>\n </li>\n );\n }}\n </For>\n </Show>\n </ul>\n <Show when={props.actions?.length}>\n <ul class=\"k2b-sidebar-switcher__actions\">\n <For each={props.actions}>\n {(item) => (\n <li>\n <Show\n when={item.href}\n fallback={\n <button\n type=\"button\"\n class=\"k2b-dropdown__item\"\n data-switcher-row\n disabled={item.disabled}\n aria-disabled={item.disabled ? \"true\" : undefined}\n onClick={() => choose(() => item.action?.())}\n >\n <i class={iconClass(item.icon)} aria-hidden=\"true\" />\n <span class=\"k2b-dropdown__copy\">\n <span>{item.label}</span>\n </span>\n </button>\n }\n >\n {(href) => (\n <a\n href={item.disabled ? undefined : href()}\n class=\"k2b-dropdown__item\"\n data-switcher-row\n aria-disabled={item.disabled ? \"true\" : undefined}\n onClick={(event) => item.disabled || follow(event)}\n >\n <i class={iconClass(item.icon)} aria-hidden=\"true\" />\n <span class=\"k2b-dropdown__copy\">\n <span>{item.label}</span>\n </span>\n </a>\n )}\n </Show>\n </li>\n )}\n </For>\n </ul>\n </Show>\n </div>\n </div>\n );\n}\n"],"mappings":"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","ignoreList":[]}