@k2b/ui 0.31.0 → 0.32.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 (116) hide show
  1. package/dist/base.css +2 -0
  2. package/dist/browser/actions/ViewTabs.js +8 -0
  3. package/dist/browser/actions/ViewTabs.js.map +1 -1
  4. package/dist/browser/content/Chart.js +104 -55
  5. package/dist/browser/content/Chart.js.map +1 -1
  6. package/dist/browser/content/ChartExplorer.js +5 -3
  7. package/dist/browser/content/ChartExplorer.js.map +1 -1
  8. package/dist/browser/content/MapView.js +4 -4
  9. package/dist/browser/content/MapView.js.map +1 -1
  10. package/dist/browser/content/chart-inspection-core.js +4 -1
  11. package/dist/browser/content/chart-inspection-core.js.map +1 -1
  12. package/dist/browser/content/code-highlight.js +66 -0
  13. package/dist/browser/content/code-highlight.js.map +1 -1
  14. package/dist/browser/content/index.js +1 -0
  15. package/dist/browser/content/index.js.map +1 -1
  16. package/dist/browser/content/map-view-live.js +47 -10
  17. package/dist/browser/content/map-view-live.js.map +1 -1
  18. package/dist/browser/content/map-view-types.js.map +1 -1
  19. package/dist/browser/inputs/AutocompleteEditor.js +167 -104
  20. package/dist/browser/inputs/AutocompleteEditor.js.map +1 -1
  21. package/dist/browser/inputs/RichSelect.js +1474 -0
  22. package/dist/browser/inputs/RichSelect.js.map +1 -0
  23. package/dist/browser/inputs/TextInput.js +95 -60
  24. package/dist/browser/inputs/TextInput.js.map +1 -1
  25. package/dist/browser/inputs/choice.js +19 -8
  26. package/dist/browser/inputs/choice.js.map +1 -1
  27. package/dist/browser/inputs/index.js +1 -0
  28. package/dist/browser/inputs/index.js.map +1 -1
  29. package/dist/browser/inputs/rich-select-messages.js +46 -0
  30. package/dist/browser/inputs/rich-select-messages.js.map +1 -0
  31. package/dist/browser/layout/AppWorkspace.js +177 -135
  32. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  33. package/dist/browser/layout/DetailPanel.js +10 -1
  34. package/dist/browser/layout/DetailPanel.js.map +1 -1
  35. package/dist/browser/layout/Navigation.js +14 -5
  36. package/dist/browser/layout/Navigation.js.map +1 -1
  37. package/dist/browser/layout/PageTitle.js +9 -3
  38. package/dist/browser/layout/PageTitle.js.map +1 -1
  39. package/dist/browser/layout/ResizeSeparator.js +8 -2
  40. package/dist/browser/layout/ResizeSeparator.js.map +1 -1
  41. package/dist/browser/layout/app-workspace-controller.js +192 -31
  42. package/dist/browser/layout/app-workspace-controller.js.map +1 -1
  43. package/dist/browser/layout/navigation-model.js.map +1 -1
  44. package/dist/browser/layout/resize-separator.js +25 -1
  45. package/dist/browser/layout/resize-separator.js.map +1 -1
  46. package/dist/browser/surfaces/Skeleton.js +14 -0
  47. package/dist/browser/surfaces/Skeleton.js.map +1 -0
  48. package/dist/browser/surfaces/index.js +1 -0
  49. package/dist/browser/surfaces/index.js.map +1 -1
  50. package/dist/ssr/actions/ViewTabs.js +8 -0
  51. package/dist/ssr/actions/ViewTabs.js.map +1 -1
  52. package/dist/ssr/content/Chart.js +27 -7
  53. package/dist/ssr/content/Chart.js.map +1 -1
  54. package/dist/ssr/content/ChartExplorer.js +5 -3
  55. package/dist/ssr/content/ChartExplorer.js.map +1 -1
  56. package/dist/ssr/content/MapView.js +4 -4
  57. package/dist/ssr/content/MapView.js.map +1 -1
  58. package/dist/ssr/content/chart-inspection-core.js +4 -1
  59. package/dist/ssr/content/chart-inspection-core.js.map +1 -1
  60. package/dist/ssr/content/code-highlight.js +66 -0
  61. package/dist/ssr/content/code-highlight.js.map +1 -1
  62. package/dist/ssr/content/index.js +1 -0
  63. package/dist/ssr/content/index.js.map +1 -1
  64. package/dist/ssr/content/map-view-types.js.map +1 -1
  65. package/dist/ssr/inputs/AutocompleteEditor.js +64 -24
  66. package/dist/ssr/inputs/AutocompleteEditor.js.map +1 -1
  67. package/dist/ssr/inputs/RichSelect.js +950 -0
  68. package/dist/ssr/inputs/RichSelect.js.map +1 -0
  69. package/dist/ssr/inputs/TextInput.js +40 -16
  70. package/dist/ssr/inputs/TextInput.js.map +1 -1
  71. package/dist/ssr/inputs/choice.js +19 -8
  72. package/dist/ssr/inputs/choice.js.map +1 -1
  73. package/dist/ssr/inputs/index.js +1 -0
  74. package/dist/ssr/inputs/index.js.map +1 -1
  75. package/dist/ssr/inputs/rich-select-messages.js +46 -0
  76. package/dist/ssr/inputs/rich-select-messages.js.map +1 -0
  77. package/dist/ssr/layout/AppWorkspace.js +36 -7
  78. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  79. package/dist/ssr/layout/DetailPanel.js +1 -1
  80. package/dist/ssr/layout/DetailPanel.js.map +1 -1
  81. package/dist/ssr/layout/Navigation.js +1 -1
  82. package/dist/ssr/layout/Navigation.js.map +1 -1
  83. package/dist/ssr/layout/PageTitle.js +9 -3
  84. package/dist/ssr/layout/PageTitle.js.map +1 -1
  85. package/dist/ssr/layout/ResizeSeparator.js +4 -1
  86. package/dist/ssr/layout/ResizeSeparator.js.map +1 -1
  87. package/dist/ssr/layout/app-workspace-controller.js +192 -31
  88. package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
  89. package/dist/ssr/layout/navigation-model.js.map +1 -1
  90. package/dist/ssr/layout/resize-separator.js +25 -1
  91. package/dist/ssr/layout/resize-separator.js.map +1 -1
  92. package/dist/ssr/surfaces/Skeleton.js +8 -0
  93. package/dist/ssr/surfaces/Skeleton.js.map +1 -0
  94. package/dist/ssr/surfaces/index.js +1 -0
  95. package/dist/ssr/surfaces/index.js.map +1 -1
  96. package/dist/styles.css +1 -1
  97. package/dist/types/content/Chart.d.ts +4 -2
  98. package/dist/types/content/ChartExplorer.d.ts +0 -1
  99. package/dist/types/content/code-highlight.d.ts +3 -1
  100. package/dist/types/content/index.d.ts +1 -0
  101. package/dist/types/content/map-view-live.d.ts +1 -1
  102. package/dist/types/content/map-view-types.d.ts +2 -0
  103. package/dist/types/inputs/AutocompleteEditor.d.ts +6 -1
  104. package/dist/types/inputs/RichSelect.d.ts +93 -0
  105. package/dist/types/inputs/TextInput.d.ts +5 -2
  106. package/dist/types/inputs/choice.d.ts +24 -4
  107. package/dist/types/inputs/index.d.ts +2 -0
  108. package/dist/types/inputs/rich-select-messages.d.ts +21 -0
  109. package/dist/types/layout/AppWorkspace.d.ts +7 -0
  110. package/dist/types/layout/DetailPanel.d.ts +2 -0
  111. package/dist/types/layout/app-workspace-controller.d.ts +2 -0
  112. package/dist/types/layout/navigation-model.d.ts +2 -0
  113. package/dist/types/layout/resize-separator.d.ts +1 -0
  114. package/dist/types/surfaces/Skeleton.d.ts +6 -0
  115. package/dist/types/surfaces/index.d.ts +1 -0
  116. package/package.json +4 -4
@@ -0,0 +1,46 @@
1
+ import { i18n } from "@k2b/stdlib";
2
+ import { useLocale } from "../intl/locale";
3
+ const richSelectMessages = i18n.define({
4
+ baseLocale: "en",
5
+ messages: {
6
+ en: {
7
+ back: "Back",
8
+ choose: "Choose",
9
+ chooseOption: "Choose option",
10
+ close: "Close",
11
+ couldNotLoad: "Could not be loaded.",
12
+ create: "Create",
13
+ detailsFor: ({
14
+ name
15
+ }) => `Details: ${name}`,
16
+ loading: "Loading options",
17
+ noOptions: "No options available",
18
+ noResults: "No results found",
19
+ retry: "Retry",
20
+ search: "Search"
21
+ },
22
+ de: {
23
+ back: "Zurück",
24
+ choose: "Auswählen",
25
+ chooseOption: "Option auswählen",
26
+ close: "Schließen",
27
+ couldNotLoad: "Konnte nicht geladen werden.",
28
+ create: "Anlegen",
29
+ detailsFor: ({
30
+ name
31
+ }) => `Details: ${name}`,
32
+ loading: "Optionen werden geladen",
33
+ noOptions: "Keine Optionen verfügbar",
34
+ noResults: "Keine Ergebnisse gefunden",
35
+ retry: "Erneut versuchen",
36
+ search: "Suchen"
37
+ }
38
+ }
39
+ });
40
+ /** Resolved from the inherited render locale. */
41
+ export const useRichSelectMessages = () => {
42
+ const locale = useLocale();
43
+ return () => richSelectMessages.resolve([locale()]).t;
44
+ };
45
+ export const checkRichSelectMessages = () => richSelectMessages.check();
46
+ //# sourceMappingURL=rich-select-messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["inputs/rich-select-messages.ts"],"sourcesContent":["import { i18n } from \"@k2b/stdlib\";\nimport { useLocale } from \"../intl/locale\";\n\nconst richSelectMessages = i18n.define({\n baseLocale: \"en\",\n messages: {\n en: {\n back: \"Back\",\n choose: \"Choose\",\n chooseOption: \"Choose option\",\n close: \"Close\",\n couldNotLoad: \"Could not be loaded.\",\n create: \"Create\",\n detailsFor: ({ name }: { name: string }) => `Details: ${name}`,\n loading: \"Loading options\",\n noOptions: \"No options available\",\n noResults: \"No results found\",\n retry: \"Retry\",\n search: \"Search\",\n },\n de: {\n back: \"Zurück\",\n choose: \"Auswählen\",\n chooseOption: \"Option auswählen\",\n close: \"Schließen\",\n couldNotLoad: \"Konnte nicht geladen werden.\",\n create: \"Anlegen\",\n detailsFor: ({ name }) => `Details: ${name}`,\n loading: \"Optionen werden geladen\",\n noOptions: \"Keine Optionen verfügbar\",\n noResults: \"Keine Ergebnisse gefunden\",\n retry: \"Erneut versuchen\",\n search: \"Suchen\",\n },\n },\n});\n\nexport type RichSelectMessages = ReturnType<(typeof richSelectMessages)[\"resolve\"]>[\"t\"];\n\n/** Resolved from the inherited render locale. */\nexport const useRichSelectMessages = (): (() => RichSelectMessages) => {\n const locale = useLocale();\n return () => richSelectMessages.resolve([locale()]).t;\n};\n\nexport const checkRichSelectMessages = () => richSelectMessages.check();\n"],"mappings":"AAAA,SAAS,IAAI,QAAQ,aAAa;AAClC,SAAS,SAAS,QAAQ,gBAAgB;AAE1C,MAAM,kBAAkB,GAAG,IAAI,CAAC,MAAM,CAAC;EACrC,UAAU,EAAE,IAAI;EAChB,QAAQ,EAAE;IACR,EAAE,EAAE;MACF,IAAI,EAAE,MAAM;MACZ,MAAM,EAAE,QAAQ;MAChB,YAAY,EAAE,eAAe;MAC7B,KAAK,EAAE,OAAO;MACd,YAAY,EAAE,sBAAsB;MACpC,MAAM,EAAE,QAAQ;MAChB,UAAU,EAAE,CAAC;QAAE;MAAuB,CAAC,KAAK,YAAY,IAAI,EAAE;MAC9D,OAAO,EAAE,iBAAiB;MAC1B,SAAS,EAAE,sBAAsB;MACjC,SAAS,EAAE,kBAAkB;MAC7B,KAAK,EAAE,OAAO;MACd,MAAM,EAAE;IACV,CAAC;IACD,EAAE,EAAE;MACF,IAAI,EAAE,QAAQ;MACd,MAAM,EAAE,WAAW;MACnB,YAAY,EAAE,kBAAkB;MAChC,KAAK,EAAE,WAAW;MAClB,YAAY,EAAE,8BAA8B;MAC5C,MAAM,EAAE,SAAS;MACjB,UAAU,EAAE,CAAC;QAAE;MAAK,CAAC,KAAK,YAAY,IAAI,EAAE;MAC5C,OAAO,EAAE,yBAAyB;MAClC,SAAS,EAAE,0BAA0B;MACrC,SAAS,EAAE,2BAA2B;MACtC,KAAK,EAAE,kBAAkB;MACzB,MAAM,EAAE;IACV;EACF;AACF,CAAC,CAAC;AAIF;AACA,OAAO,MAAM,qBAAqB,GAAG,MAAkC;EACrE,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC;EAC1B,OAAO,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,OAAO,MAAM,uBAAuB,GAAG,MAAM,kBAAkB,CAAC,KAAK,CAAC,CAAC","ignoreList":[]}
@@ -62,7 +62,7 @@ var _tmpl$ = ["<button type=\"button\" class=\"k2b-app-workspace__resize\" role=
62
62
  _tmpl$51 = ["<div class=\"k2b-app-workspace__head-filters\" role=\"group\"", ">", "", "</div>"],
63
63
  _tmpl$52 = ["<span class=\"k2b-app-workspace__head-count\"><span>", "</span></span>"],
64
64
  _tmpl$53 = ["<div class=\"k2b-app-workspace__head-actions\">", "", "</div>"],
65
- _tmpl$54 = ["<div class=\"k2b-app-workspace__head\"", "><div class=\"k2b-app-workspace__head-lead\">", "", "", "", "", "</div>", "</div>"],
65
+ _tmpl$54 = ["<div class=\"k2b-app-workspace__head\"", ">", "<div class=\"k2b-app-workspace__head-lead\">", "", "", "", "", "</div>", "</div>"],
66
66
  _tmpl$55 = ["<h1 class=\"k2b-app-workspace__head-heading\" data-k2b-page-trail-title", ">", "</h1>"],
67
67
  _tmpl$56 = ["<div class=\"", "\" data-k2b-app-workspace", " style=\"", "\">", "</div>"],
68
68
  _tmpl$57 = ["<div class=\"", "\">", "</div>"];
@@ -73,7 +73,7 @@ import { Button, ButtonLink, IconButton, IconButtonLink } from "../actions/Butto
73
73
  import { useFilterMessages } from "../actions/filter-messages";
74
74
  import { dialogCore } from "../feedback/dialog-core";
75
75
  import { RenderErrorBoundary } from "../internal/render-error";
76
- import { APP_WORKSPACE_DETAIL_CLOSE_EVENT, APP_WORKSPACE_DETAIL_STEP_EVENT, APP_WORKSPACE_SIDEBAR_HIDE_EVENT, installAppWorkspaceController } from "./app-workspace-controller";
76
+ import { APP_WORKSPACE_DETAIL_CLOSE_EVENT, APP_WORKSPACE_DETAIL_OPEN_EVENT, APP_WORKSPACE_DETAIL_STEP_EVENT, APP_WORKSPACE_SIDEBAR_HIDE_EVENT, installAppWorkspaceController } from "./app-workspace-controller";
77
77
  import { APP_WORKSPACE_DETAIL_DEFAULT, APP_WORKSPACE_DETAIL_MAX, APP_WORKSPACE_DETAIL_MIN, APP_WORKSPACE_DRAWER_DEFAULT, APP_WORKSPACE_DRAWER_MAX, APP_WORKSPACE_DRAWER_MIN, APP_WORKSPACE_MAIN_MIN, APP_WORKSPACE_PANE_DEFAULT, APP_WORKSPACE_PANE_MAX, APP_WORKSPACE_PANE_MIN, APP_WORKSPACE_SIDEBAR_COLLAPSED, APP_WORKSPACE_SIDEBAR_DEFAULT, APP_WORKSPACE_SIDEBAR_MAX, APP_WORKSPACE_SIDEBAR_MIN, appWorkspaceLayoutStyle, appWorkspacePanelVariable } from "./app-workspace-state";
78
78
  import { appWorkspaceInverseWidthCss, appWorkspaceWidthCss, appWorkspaceWidthToken, isAppWorkspaceWidthShare } from "./app-workspace-width";
79
79
  import BottomSheet, { bottomSheetAdaptiveOptions } from "./BottomSheet";
@@ -486,15 +486,20 @@ function AppWorkspaceDetail(props) {
486
486
  let aside;
487
487
  onMount(() => {
488
488
  const close = () => props.onClose?.();
489
+ const open = () => {
490
+ if (props.available !== false) props.onOpen?.();
491
+ };
489
492
  const isStep = detail => typeof detail === "object" && detail !== null && "direction" in detail && (detail.direction === 1 || detail.direction === -1);
490
493
  const step = event => {
491
494
  if (!(event instanceof CustomEvent) || !isStep(event.detail)) return;
492
495
  const item = props.onStep?.(event.detail.direction);
493
496
  if (item instanceof HTMLElement) event.detail.item = item;
494
497
  };
498
+ aside?.addEventListener(APP_WORKSPACE_DETAIL_OPEN_EVENT, open);
495
499
  aside?.addEventListener(APP_WORKSPACE_DETAIL_CLOSE_EVENT, close);
496
500
  aside?.addEventListener(APP_WORKSPACE_DETAIL_STEP_EVENT, step);
497
501
  onCleanup(() => {
502
+ aside?.removeEventListener(APP_WORKSPACE_DETAIL_OPEN_EVENT, open);
498
503
  aside?.removeEventListener(APP_WORKSPACE_DETAIL_CLOSE_EVENT, close);
499
504
  aside?.removeEventListener(APP_WORKSPACE_DETAIL_STEP_EVENT, step);
500
505
  });
@@ -507,6 +512,12 @@ function AppWorkspaceDetail(props) {
507
512
  },
508
513
  "aria-expanded": "false",
509
514
  "data-app-workspace-detail-toggle": "",
515
+ get ["data-workspace-detail-reopen"]() {
516
+ return props.onOpen ? "true" : undefined;
517
+ },
518
+ get disabled() {
519
+ return props.available === false;
520
+ },
510
521
  "class": "k2b-app-workspace__detail-toggle",
511
522
  get children() {
512
523
  return [_$ssr(_tmpl$0), workspaceMessages().details];
@@ -534,12 +545,13 @@ function AppWorkspaceDetail(props) {
534
545
  get maxSize() {
535
546
  return maxWidth();
536
547
  },
548
+ shadow: true,
537
549
  get style() {
538
550
  return sizeStyle();
539
551
  }
540
552
  });
541
553
  }
542
- }), _$ssr(_tmpl$1, _$ssrAttribute("id", _$escape(domId(), true), false), `k2b-app-workspace__detail ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("data-width", _$escape(props.width ?? "md", true), false) + _$ssrAttribute("data-workspace-detail-phone", props.phone === "page" ? "page" : _$escape(undefined, true), false) + _$ssrAttribute("data-workspace-detail-choice", props.openOnChoice === false ? "request" : _$escape(undefined, true), false) + _$ssrAttribute("data-workspace-panel-id", _$escape(panelId(), true), false) + _$ssrAttribute("data-workspace-resizable", resizable() ? "true" : "false", false) + _$ssrAttribute("data-workspace-detail-closable", props.onClose ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-workspace-detail-steppable", props.onStep ? "true" : _$escape(undefined, true), false), _$ssrAttribute("hidden", !props.open, true), _$ssrStyle({
554
+ }), _$ssr(_tmpl$1, _$ssrAttribute("id", _$escape(domId(), true), false), `k2b-app-workspace__detail ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("data-width", _$escape(props.width ?? "md", true), false) + _$ssrAttribute("data-workspace-detail-phone", props.phone === "page" ? "page" : _$escape(undefined, true), false) + _$ssrAttribute("data-workspace-detail-choice", props.openOnChoice === false ? "request" : _$escape(undefined, true), false) + _$ssrAttribute("data-workspace-panel-id", _$escape(panelId(), true), false) + _$ssrAttribute("data-workspace-resizable", resizable() ? "true" : "false", false) + _$ssrAttribute("data-workspace-detail-reopen", props.onOpen ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-workspace-detail-closable", props.onClose ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-workspace-detail-steppable", props.onStep ? "true" : _$escape(undefined, true), false), _$ssrAttribute("hidden", !props.open || props.available === false, true), _$ssrStyle({
543
555
  ...sizeStyle(),
544
556
  "view-transition-name": props.viewTransitionName
545
557
  }), _$ssrAttribute("aria-label", _$escape(workspaceMessages().detailFullHeight, true), false), _$escape(props.children))];
@@ -1708,9 +1720,23 @@ function AppWorkspaceHead(props) {
1708
1720
  const resolvedCount = children(() => props.head?.count);
1709
1721
  // A nested workspace is part of a page that already shows its title.
1710
1722
  const trail = props.nested ? () => undefined : usePageTrail();
1723
+ // Main already publishes the active region in SSR and on each selection.
1724
+ // Scope both sides to this workspace, including transparent island hosts;
1725
+ // a nested Main must never decide whether its parent's head is visible.
1726
+ const mobilePane = () => props.head?.mobilePane === undefined ? undefined : assertStableUiId(props.head.mobilePane, "AppWorkspace.Head mobilePane");
1727
+ const mobilePaneRule = pane => {
1728
+ const main = `.k2b-app-workspace__main.has-panes:not([data-workspace-levels]) > [data-workspace-mobile-active="true"]:not([data-workspace-main-region="${pane}"])`;
1729
+ const host = `.k2b-ui .k2b-app-workspace:has(> .k2b-app-workspace__head[data-workspace-head-pane="${pane}"]):has(> .k2b-app-workspace__content > ${main}, > .k2b-app-workspace__content > :is(solid-island, solid-client) > ${main})`;
1730
+ return `@media not all and (min-width:48rem){${host} > .k2b-app-workspace__head,${host} > .k2b-app-workspace__content > .k2b-app-workspace__detail-toggle,${host} > .k2b-app-workspace__content > :is(solid-island, solid-client) > .k2b-app-workspace__detail-toggle{display:none}}`;
1731
+ };
1711
1732
  const resetContent = () => filters().reset;
1712
1733
  const primaryContent = () => [_$ssr(_tmpl$47, _$ssrAttribute("class", _$escape(props.head?.primary?.icon, true), false)), _$ssr(_tmpl$48, _$escape(props.head?.primary?.label))];
1713
- return _$ssr(_tmpl$54, _$ssrAttribute("data-trail", trail()?.ancestors.length ? "up" : trail() ? "title" : _$escape(undefined, true), false) + _$ssrAttribute("data-row", props.head ? "" : _$escape(undefined, true), false), _$escape(_$createComponent(IconButton, {
1734
+ return _$ssr(_tmpl$54, _$ssrAttribute("data-trail", trail()?.ancestors.length ? "up" : trail() ? "title" : _$escape(undefined, true), false) + _$ssrAttribute("data-row", props.head ? "" : _$escape(undefined, true), false) + _$ssrAttribute("data-workspace-head-pane", _$escape(mobilePane(), true), false), _$escape(_$createComponent(Show, {
1735
+ get when() {
1736
+ return mobilePane();
1737
+ },
1738
+ children: pane => _$ssr(_tmpl$5, mobilePaneRule(pane()))
1739
+ })), _$escape(_$createComponent(IconButton, {
1714
1740
  "class": "k2b-app-workspace__head-toggle",
1715
1741
  get label() {
1716
1742
  return messages().showNavigation;
@@ -1872,14 +1898,17 @@ const AppWorkspace = props => {
1872
1898
  const slots = createMemo(() => flatten(resolved()));
1873
1899
  // Same props object, same memo value: a sibling change must not rebuild the head's slots.
1874
1900
  const head = createMemo(() => slots().find(headSlot)?.props);
1875
- const content = () => slots().filter(value => !headSlot(value));
1876
- return [_$createComponent(AppWorkspaceHead, {
1901
+ // Keep one head instance at the explicit slot's position. Object links
1902
+ // before it must also precede its view controls in SSR and keyboard order.
1903
+ // Callers without a slot retain the automatic leading head.
1904
+ const renderedHead = _$createComponent(AppWorkspaceHead, {
1877
1905
  sidebarId: sidebarId,
1878
1906
  nested: nested,
1879
1907
  get head() {
1880
1908
  return head();
1881
1909
  }
1882
- }), content()];
1910
+ });
1911
+ return head() ? slots().map(value => headSlot(value) ? value.props === head() ? renderedHead : null : value) : [renderedHead, ...slots()];
1883
1912
  };
1884
1913
  return _$createComponent(ResizeContext.Provider, {
1885
1914
  get value() {