@happyvertical/smrt-svelte 0.51.39 → 0.52.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 (114) hide show
  1. package/AGENTS.md +3 -0
  2. package/README.md +167 -1
  3. package/agents/workspace.md +24 -0
  4. package/dist/browser-ai/adapters/stt/browser-speech.d.ts +11 -0
  5. package/dist/browser-ai/adapters/stt/browser-speech.d.ts.map +1 -1
  6. package/dist/browser-ai/adapters/stt/browser-speech.js +20 -4
  7. package/dist/browser-ai/adapters/stt/browser-speech.test.js +113 -0
  8. package/dist/browser-ai/adapters/stt/dictation-source.d.ts +3 -0
  9. package/dist/browser-ai/adapters/stt/dictation-source.d.ts.map +1 -0
  10. package/dist/browser-ai/adapters/stt/dictation-source.js +38 -0
  11. package/dist/browser-ai/adapters/stt/dictation-source.test.js +52 -0
  12. package/dist/browser-ai/adapters/stt/index.d.ts +2 -1
  13. package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -1
  14. package/dist/browser-ai/adapters/stt/index.js +2 -1
  15. package/dist/components/forms/Form.svelte +118 -8
  16. package/dist/components/forms/Form.svelte.d.ts +18 -2
  17. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  18. package/dist/components/forms/FormScope.svelte +202 -0
  19. package/dist/components/forms/FormScope.svelte.d.ts +22 -0
  20. package/dist/components/forms/FormScope.svelte.d.ts.map +1 -0
  21. package/dist/components/forms/__tests__/Form.ui-controls.test.js +119 -0
  22. package/dist/components/forms/__tests__/Form.webmcp.test.js +5 -3
  23. package/dist/components/forms/__tests__/FormScope.test.js +39 -0
  24. package/dist/components/forms/__tests__/form-scope.fixture.svelte +30 -0
  25. package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts +10 -0
  26. package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts.map +1 -0
  27. package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte +84 -0
  28. package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts +14 -0
  29. package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts.map +1 -0
  30. package/dist/components/forms/index.d.ts +1 -0
  31. package/dist/components/forms/index.d.ts.map +1 -1
  32. package/dist/components/forms/index.js +1 -0
  33. package/dist/components/workspace/README.md +11 -0
  34. package/dist/components/workspace/__tests__/AdminShell.responsive.test.js +586 -0
  35. package/dist/components/workspace/__tests__/AdminShell.test.js +85 -0
  36. package/dist/components/workspace/__tests__/TenantNav.test.js +49 -0
  37. package/dist/components/workspace/__tests__/admin-shell-header-overflow.test.js +25 -0
  38. package/dist/components/workspace/__tests__/admin-shell-mobile.test.js +217 -0
  39. package/dist/components/workspace/__tests__/admin-shell-page-contracts.test.js +30 -0
  40. package/dist/components/workspace/__tests__/admin-shell-responsive-state.test.js +184 -0
  41. package/dist/components/workspace/__tests__/admin-shell-theme-hooks.test.js +36 -0
  42. package/dist/components/workspace/__tests__/shell-chrome-components.test.js +108 -0
  43. package/dist/components/workspace/admin-shell/AdminShell.svelte +998 -40
  44. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +42 -1
  45. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
  46. package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte +170 -0
  47. package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts +11 -0
  48. package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts.map +1 -0
  49. package/dist/components/workspace/admin-shell/PhoneTopBar.svelte +93 -0
  50. package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts +11 -0
  51. package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts.map +1 -0
  52. package/dist/components/workspace/admin-shell/ShellNavToggle.svelte +88 -0
  53. package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts +14 -0
  54. package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts.map +1 -0
  55. package/dist/components/workspace/admin-shell/ShellTitle.svelte +42 -0
  56. package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts +11 -0
  57. package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts.map +1 -0
  58. package/dist/components/workspace/admin-shell/TenantNav.svelte +46 -1
  59. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
  60. package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts +37 -0
  61. package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts.map +1 -0
  62. package/dist/components/workspace/admin-shell/mobile-shell-dom.js +150 -0
  63. package/dist/components/workspace/admin-shell/mobile-shell.d.ts +191 -0
  64. package/dist/components/workspace/admin-shell/mobile-shell.d.ts.map +1 -0
  65. package/dist/components/workspace/admin-shell/mobile-shell.js +165 -0
  66. package/dist/components/workspace/admin-shell/settings.d.ts +27 -2
  67. package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
  68. package/dist/components/workspace/admin-shell/settings.js +84 -2
  69. package/dist/components/workspace/admin-shell/state.svelte.d.ts +55 -1
  70. package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
  71. package/dist/components/workspace/admin-shell/state.svelte.js +149 -6
  72. package/dist/components/workspace/admin-shell/types.d.ts +97 -0
  73. package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
  74. package/dist/components/workspace/admin-shell/types.js +13 -0
  75. package/dist/components/workspace/index.d.ts +11 -4
  76. package/dist/components/workspace/index.d.ts.map +1 -1
  77. package/dist/components/workspace/index.js +12 -3
  78. package/dist/i18n/strings.workspace.d.ts +10 -0
  79. package/dist/i18n/strings.workspace.d.ts.map +1 -1
  80. package/dist/i18n/strings.workspace.js +10 -0
  81. package/dist/index.d.ts +1 -1
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1 -1
  84. package/dist/web/__tests__/assistant-dock.integration.svelte.test.js +2 -2
  85. package/dist/web/__tests__/list-sort-surface.fixture.svelte +41 -0
  86. package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts +15 -0
  87. package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts.map +1 -0
  88. package/dist/web/__tests__/list-sort-surface.svelte.test.js +59 -0
  89. package/dist/web/__tests__/surfaces.child.fixture.svelte +69 -0
  90. package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts +16 -0
  91. package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts.map +1 -0
  92. package/dist/web/__tests__/surfaces.fixture.svelte +26 -0
  93. package/dist/web/__tests__/surfaces.fixture.svelte.d.ts +18 -0
  94. package/dist/web/__tests__/surfaces.fixture.svelte.d.ts.map +1 -0
  95. package/dist/web/__tests__/surfaces.svelte.test.js +140 -0
  96. package/dist/web/__tests__/webmcp-ui.test.js +18 -0
  97. package/dist/web/index.d.ts +4 -1
  98. package/dist/web/index.d.ts.map +1 -1
  99. package/dist/web/index.js +4 -1
  100. package/dist/web/link-surface.svelte.d.ts +14 -0
  101. package/dist/web/link-surface.svelte.d.ts.map +1 -0
  102. package/dist/web/link-surface.svelte.js +49 -0
  103. package/dist/web/list-surface.svelte.d.ts +80 -0
  104. package/dist/web/list-surface.svelte.d.ts.map +1 -0
  105. package/dist/web/list-surface.svelte.js +309 -0
  106. package/dist/web/step-surface.svelte.d.ts +17 -0
  107. package/dist/web/step-surface.svelte.d.ts.map +1 -0
  108. package/dist/web/step-surface.svelte.js +56 -0
  109. package/dist/web/webmcp-ui-context.d.ts +7 -0
  110. package/dist/web/webmcp-ui-context.d.ts.map +1 -1
  111. package/dist/web/webmcp-ui-context.js +10 -0
  112. package/dist/web/webmcp-ui.d.ts.map +1 -1
  113. package/dist/web/webmcp-ui.js +16 -3
  114. package/package.json +16 -16
@@ -1,6 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
+ import { type BottomBarMode } from './mobile-shell.js';
2
3
  import { type ShellState } from './state.svelte.js';
3
- import type { AdminShellProps, ShellFocusTool } from './types.js';
4
+ import type { AdminShellPhoneOptions, AdminShellProps, ShellFocusTool, ShellViewport } from './types.js';
4
5
  interface Props extends AdminShellProps {
5
6
  /** Pre-configured shell state. Creates one from config if omitted. */
6
7
  state?: ShellState;
@@ -34,6 +35,46 @@ interface Props extends AdminShellProps {
34
35
  bottomRightCorner?: Snippet;
35
36
  /** Content for the keyboard shortcuts overlay. */
36
37
  shortcutsOverlay?: Snippet;
38
+ /**
39
+ * A full-width header row above every edge (`#smrt-admin-shell-header`,
40
+ * 3.5rem; override `--smrt-admin-shell-header-size`). Shown at every
41
+ * width except on phones when `phoneTopBar` is given.
42
+ */
43
+ header?: Snippet<[{
44
+ viewport: ShellViewport;
45
+ }]>;
46
+ /**
47
+ * Phone-only context bar over the top of the main region. Hides while
48
+ * scrolling down and returns on the first scroll up (see
49
+ * `phone.hideOnScroll`); pinned while `pinChrome` or a drawer is open.
50
+ */
51
+ phoneTopBar?: Snippet<[{
52
+ hidden: boolean;
53
+ }]>;
54
+ /**
55
+ * Phone-only bottom bar row. Replaced by a form's action bar
56
+ * (`[data-form-action-bar]` inside main) and hidden while the on-screen
57
+ * keyboard is open.
58
+ */
59
+ phoneBottomBar?: Snippet;
60
+ /**
61
+ * Host surfaces laid over the shell above the phone bottom bar (phone
62
+ * sheets, status strips). The layer ignores pointer events; its direct
63
+ * children receive them.
64
+ */
65
+ overlays?: Snippet<[{
66
+ viewport: ShellViewport;
67
+ bottomBar: BottomBarMode;
68
+ }]>;
69
+ /** Phone behavior switches (scrim, swipe to close, hide on scroll). */
70
+ phone?: AdminShellPhoneOptions;
71
+ /** Keep the phone top bar shown (e.g. while a banner is visible). */
72
+ pinChrome?: boolean;
73
+ /**
74
+ * Current location. When it changes on a phone, open side drawers and
75
+ * sheets close and the phone top bar comes back.
76
+ */
77
+ path?: string;
37
78
  children: Snippet;
38
79
  }
39
80
  declare const AdminShell: import("svelte").Component<Props, {}, "">;
@@ -1 +1 @@
1
- {"version":3,"file":"AdminShell.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/AdminShell.svelte.ts"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAStC,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EACR,eAAe,EAEf,cAAc,EACf,MAAM,YAAY,CAAC;AAGpB,UAAU,KAAM,SAAQ,eAAe;IACrC,sEAAsE;IACtE,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,mCAAmC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wCAAwC;IACxC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mEAAmE;IACnE,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC,CAAC;IACxD,kCAAkC;IAClC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oCAAoC;IACpC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wCAAwC;IACxC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,2CAA2C;IAC3C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,EAAE,OAAO,CAAC;CACnB;AAmYH,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"AdminShell.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/AdminShell.svelte.ts"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAQtC,OAAO,EAAE,KAAK,aAAa,EAAiB,MAAM,mBAAmB,CAAC;AAStE,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EACR,sBAAsB,EACtB,eAAe,EAEf,cAAc,EACd,aAAa,EACd,MAAM,YAAY,CAAC;AAGpB,UAAU,KAAM,SAAQ,eAAe;IACrC,sEAAsE;IACtE,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,mCAAmC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wCAAwC;IACxC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mEAAmE;IACnE,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC,CAAC;IACxD,kCAAkC;IAClC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oCAAoC;IACpC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wCAAwC;IACxC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,2CAA2C;IAC3C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,QAAQ,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC,CAAC;IAChD;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,CAAC;IAC7C;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,QAAQ,EAAE,aAAa,CAAC;QAAC,SAAS,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC,CAAC;IAC5E,uEAAuE;IACvE,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAgxBH,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,170 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Phone bottom bar: 56px plus the safe-area inset, an icon and a short
4
+ * label per item. Items are data: a link (`href`) or a button (`onclick`),
5
+ * with an active state, an optional count badge, and an optional dot.
6
+ * Render it in AdminShell's `phoneBottomBar` snippet: the shell hides it
7
+ * while a form's action bar is on screen or the keyboard is open.
8
+ */
9
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
10
+ import { M } from '../../../i18n/strings.workspace.js';
11
+ import type { PhoneBottomBarItem } from './mobile-shell.js';
12
+
13
+ interface Props {
14
+ /** Bar entries, left to right. */
15
+ items: PhoneBottomBarItem[];
16
+ /** Accessible name of the nav landmark (default "Main"). */
17
+ label?: string;
18
+ }
19
+
20
+ let { items, label }: Props = $props();
21
+
22
+ const { t } = useI18n();
23
+
24
+ function accessibleName(item: PhoneBottomBarItem): string {
25
+ const parts = [item.label];
26
+ if (item.badge) {
27
+ parts.push(
28
+ t(M['ui.phone_bottom_bar.unread'], { count: String(item.badge) }),
29
+ );
30
+ }
31
+ if (item.dot) parts.push(item.dotLabel ?? t(M['ui.phone_bottom_bar.new']));
32
+ return parts.join(', ');
33
+ }
34
+ </script>
35
+
36
+ {#snippet content(item: PhoneBottomBarItem)}
37
+ <span class="smrt-phone-bottom-bar__icon" aria-hidden="true">
38
+ <item.icon size={22} />
39
+ {#if item.badge}
40
+ <span class="smrt-phone-bottom-bar__badge">
41
+ {item.badge > 9 ? '9+' : item.badge}
42
+ </span>
43
+ {:else if item.dot}
44
+ <span class="smrt-phone-bottom-bar__dot"></span>
45
+ {/if}
46
+ </span>
47
+ <span class="smrt-phone-bottom-bar__label" aria-hidden="true">{item.label}</span>
48
+ {/snippet}
49
+
50
+ <nav
51
+ class="smrt-phone-bottom-bar"
52
+ aria-label={label ?? t(M['ui.phone_bottom_bar.label'])}
53
+ data-testid="phone-bottom-bar"
54
+ >
55
+ {#each items as item (item.id)}
56
+ {#if item.href}
57
+ <a
58
+ class="smrt-phone-bottom-bar__item"
59
+ class:active={item.active}
60
+ href={item.href}
61
+ aria-label={accessibleName(item)}
62
+ aria-current={item.active ? 'page' : undefined}
63
+ data-item={item.id}
64
+ onclick={item.onclick}
65
+ >
66
+ {@render content(item)}
67
+ </a>
68
+ {:else}
69
+ <!-- raw-primitive-allow: shell chrome toggle, not a content button -->
70
+ <button
71
+ type="button"
72
+ class="smrt-phone-bottom-bar__item"
73
+ class:active={item.active}
74
+ aria-label={accessibleName(item)}
75
+ aria-expanded={item.expanded}
76
+ aria-controls={item.controls}
77
+ data-item={item.id}
78
+ onclick={item.onclick}
79
+ >
80
+ {@render content(item)}
81
+ </button>
82
+ {/if}
83
+ {/each}
84
+ </nav>
85
+
86
+ <style>
87
+ .smrt-phone-bottom-bar {
88
+ display: grid;
89
+ grid-auto-columns: minmax(0, 1fr);
90
+ grid-auto-flow: column;
91
+ gap: var(--smrt-spacing-2);
92
+ box-sizing: content-box;
93
+ block-size: 3.5rem;
94
+ padding: 0 var(--smrt-spacing-2) env(safe-area-inset-bottom);
95
+ border-block-start: 1px solid var(--smrt-color-outline-variant);
96
+ background: var(--smrt-color-surface);
97
+ }
98
+
99
+ .smrt-phone-bottom-bar__item {
100
+ display: grid;
101
+ place-items: center;
102
+ align-content: center;
103
+ gap: var(--smrt-spacing-1);
104
+ min-inline-size: 2.75rem;
105
+ min-block-size: 2.75rem;
106
+ padding: var(--smrt-spacing-1) 0;
107
+ border: 0;
108
+ border-radius: var(--smrt-radius-lg);
109
+ background: transparent;
110
+ color: var(--smrt-color-on-surface-variant);
111
+ font: inherit;
112
+ text-decoration: none;
113
+ cursor: pointer;
114
+ -webkit-tap-highlight-color: transparent;
115
+ }
116
+
117
+ .smrt-phone-bottom-bar__item.active {
118
+ color: var(--smrt-color-primary);
119
+ }
120
+
121
+ .smrt-phone-bottom-bar__item.active .smrt-phone-bottom-bar__icon {
122
+ background: var(--smrt-color-primary-container);
123
+ color: var(--smrt-color-on-primary-container);
124
+ }
125
+
126
+ .smrt-phone-bottom-bar__item:focus-visible {
127
+ outline: 2px solid var(--smrt-color-primary);
128
+ outline-offset: -2px;
129
+ }
130
+
131
+ .smrt-phone-bottom-bar__icon {
132
+ position: relative;
133
+ display: grid;
134
+ place-items: center;
135
+ inline-size: 3.5rem;
136
+ block-size: 1.75rem;
137
+ border-radius: var(--smrt-radius-full);
138
+ }
139
+
140
+ .smrt-phone-bottom-bar__label {
141
+ font: var(--smrt-typography-label-small-font);
142
+ }
143
+
144
+ .smrt-phone-bottom-bar__badge {
145
+ position: absolute;
146
+ inset-block-start: -0.2rem;
147
+ inset-inline-end: 0.6rem;
148
+ box-sizing: border-box;
149
+ min-inline-size: 1.1rem;
150
+ block-size: 1.1rem;
151
+ padding: 0 var(--smrt-spacing-1);
152
+ border-radius: var(--smrt-radius-full);
153
+ background: var(--smrt-color-error);
154
+ color: var(--smrt-color-on-error);
155
+ font: var(--smrt-typography-label-small-font);
156
+ line-height: 1.1rem;
157
+ text-align: center;
158
+ }
159
+
160
+ .smrt-phone-bottom-bar__dot {
161
+ position: absolute;
162
+ inset-block-start: 0.1rem;
163
+ inset-inline-end: 1rem;
164
+ inline-size: 0.6rem;
165
+ block-size: 0.6rem;
166
+ border: 2px solid var(--smrt-color-surface);
167
+ border-radius: var(--smrt-radius-full);
168
+ background: var(--smrt-color-primary);
169
+ }
170
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { PhoneBottomBarItem } from './mobile-shell.js';
2
+ interface Props {
3
+ /** Bar entries, left to right. */
4
+ items: PhoneBottomBarItem[];
5
+ /** Accessible name of the nav landmark (default "Main"). */
6
+ label?: string;
7
+ }
8
+ declare const PhoneBottomBar: import("svelte").Component<Props, {}, "">;
9
+ type PhoneBottomBar = ReturnType<typeof PhoneBottomBar>;
10
+ export default PhoneBottomBar;
11
+ //# sourceMappingURL=PhoneBottomBar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PhoneBottomBar.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/PhoneBottomBar.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAG5D,UAAU,KAAK;IACb,kCAAkC;IAClC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyDD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,93 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Phone top bar, context only: a back arrow and the page title on detail
4
+ * pages, the workspace name (a link home) on section homes. No action
5
+ * icons: those live in the phone bottom bar. Build the model with
6
+ * `phoneTopBarFor()` and render this in AdminShell's `phoneTopBar`
7
+ * snippet, which hides it on scroll.
8
+ */
9
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
10
+ import { M } from '../../../i18n/strings.workspace.js';
11
+ import type { PhoneTopBarModel } from './mobile-shell.js';
12
+ import ShellTitle from './ShellTitle.svelte';
13
+
14
+ interface Props {
15
+ /** What to show (see `phoneTopBarFor`). */
16
+ model: PhoneTopBarModel;
17
+ /** The workspace home page, linked from the name on section homes. */
18
+ homeHref: string;
19
+ }
20
+
21
+ let { model, homeHref }: Props = $props();
22
+
23
+ const { t } = useI18n();
24
+ </script>
25
+
26
+ <div class="smrt-phone-top-bar" data-testid="phone-top-bar" data-kind={model.kind}>
27
+ {#if model.kind === 'detail'}
28
+ <a
29
+ class="smrt-phone-top-bar__back"
30
+ href={model.backHref}
31
+ aria-label={t(M['ui.phone_top_bar.back'], { label: model.backLabel })}
32
+ >
33
+ <svg
34
+ viewBox="0 0 24 24"
35
+ width="22"
36
+ height="22"
37
+ aria-hidden="true"
38
+ fill="none"
39
+ stroke="currentColor"
40
+ stroke-width="2"
41
+ stroke-linecap="round"
42
+ stroke-linejoin="round"
43
+ >
44
+ <path d="m12 19-7-7 7-7M19 12H5" />
45
+ </svg>
46
+ </a>
47
+ <span class="smrt-phone-top-bar__title">{model.title}</span>
48
+ {:else}
49
+ <ShellTitle title={model.title} href={homeHref} />
50
+ {/if}
51
+ </div>
52
+
53
+ <style>
54
+ .smrt-phone-top-bar {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: var(--smrt-spacing-1);
58
+ box-sizing: border-box;
59
+ block-size: var(--smrt-admin-shell-phone-top-size, 3.5rem);
60
+ padding: 0 var(--smrt-spacing-2);
61
+ border-block-end: 1px solid var(--smrt-color-outline-variant);
62
+ background: var(--smrt-color-surface);
63
+ color: var(--smrt-color-on-surface);
64
+ }
65
+
66
+ .smrt-phone-top-bar__back {
67
+ display: inline-grid;
68
+ place-items: center;
69
+ flex: none;
70
+ inline-size: 2.75rem;
71
+ block-size: 2.75rem;
72
+ border-radius: var(--smrt-radius-full);
73
+ color: inherit;
74
+ }
75
+
76
+ .smrt-phone-top-bar__back:hover,
77
+ .smrt-phone-top-bar__back:focus-visible {
78
+ background: var(--smrt-color-surface-container-high);
79
+ }
80
+
81
+ .smrt-phone-top-bar__back:focus-visible {
82
+ outline: 2px solid var(--smrt-color-primary);
83
+ outline-offset: -2px;
84
+ }
85
+
86
+ .smrt-phone-top-bar__title {
87
+ min-inline-size: 0;
88
+ overflow: hidden;
89
+ font: var(--smrt-typography-title-medium-font);
90
+ text-overflow: ellipsis;
91
+ white-space: nowrap;
92
+ }
93
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { PhoneTopBarModel } from './mobile-shell.js';
2
+ interface Props {
3
+ /** What to show (see `phoneTopBarFor`). */
4
+ model: PhoneTopBarModel;
5
+ /** The workspace home page, linked from the name on section homes. */
6
+ homeHref: string;
7
+ }
8
+ declare const PhoneTopBar: import("svelte").Component<Props, {}, "">;
9
+ type PhoneTopBar = ReturnType<typeof PhoneTopBar>;
10
+ export default PhoneTopBar;
11
+ //# sourceMappingURL=PhoneTopBar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PhoneTopBar.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/PhoneTopBar.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAI1D,UAAU,KAAK;IACb,2CAA2C;IAC3C,KAAK,EAAE,gBAAgB,CAAC;IACxB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;CAClB;AA+BD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Header nav toggle for tablet/desktop: a chevron that points left while
4
+ * the nav is open (collapse) and right while it is collapsed (expand),
5
+ * with an accessible label and `aria-expanded`. Pair it with
6
+ * `ShellTitle` in AdminShell's `header` snippet; phones use the bottom
7
+ * bar's Menu item instead.
8
+ */
9
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
10
+ import { M } from '../../../i18n/strings.workspace.js';
11
+
12
+ interface Props {
13
+ /** Whether the nav is open. */
14
+ expanded: boolean;
15
+ /** Toggle the nav (e.g. `() => shell.togglePanel('left')`). */
16
+ onclick: () => void;
17
+ /** id of the nav panel (default AdminShell's left panel). */
18
+ controls?: string;
19
+ /** What is toggled, for the accessible name ("Collapse site navigation"). */
20
+ label?: string;
21
+ }
22
+
23
+ let {
24
+ expanded,
25
+ onclick,
26
+ controls = 'smrt-admin-shell-left-panel',
27
+ label,
28
+ }: Props = $props();
29
+
30
+ const { t } = useI18n();
31
+ const name = $derived(label ?? t(M['ui.shell_nav_toggle.navigation']));
32
+ </script>
33
+
34
+ <!-- raw-primitive-allow: shell chrome toggle, not a content button -->
35
+ <button
36
+ type="button"
37
+ class="smrt-shell-nav-toggle"
38
+ aria-label={expanded
39
+ ? t(M['ui.shell_nav_toggle.collapse'], { label: name })
40
+ : t(M['ui.shell_nav_toggle.expand'], { label: name })}
41
+ aria-expanded={expanded}
42
+ aria-controls={controls}
43
+ data-testid="shell-nav-toggle"
44
+ {onclick}
45
+ >
46
+ <svg
47
+ viewBox="0 0 24 24"
48
+ width="20"
49
+ height="20"
50
+ aria-hidden="true"
51
+ fill="none"
52
+ stroke="currentColor"
53
+ stroke-width="2"
54
+ stroke-linecap="round"
55
+ stroke-linejoin="round"
56
+ >
57
+ {#if expanded}
58
+ <path d="m15 18-6-6 6-6" />
59
+ {:else}
60
+ <path d="m9 18 6-6-6-6" />
61
+ {/if}
62
+ </svg>
63
+ </button>
64
+
65
+ <style>
66
+ .smrt-shell-nav-toggle {
67
+ display: inline-grid;
68
+ place-items: center;
69
+ flex: none;
70
+ inline-size: 2.75rem;
71
+ block-size: 2.75rem;
72
+ border: 0;
73
+ border-radius: var(--smrt-radius-md);
74
+ background: transparent;
75
+ color: var(--smrt-color-on-surface-variant);
76
+ cursor: pointer;
77
+ }
78
+
79
+ .smrt-shell-nav-toggle:hover {
80
+ background: var(--smrt-color-surface-container-high);
81
+ color: var(--smrt-color-on-surface);
82
+ }
83
+
84
+ .smrt-shell-nav-toggle:focus-visible {
85
+ outline: 2px solid var(--smrt-color-primary);
86
+ outline-offset: 2px;
87
+ }
88
+ </style>
@@ -0,0 +1,14 @@
1
+ interface Props {
2
+ /** Whether the nav is open. */
3
+ expanded: boolean;
4
+ /** Toggle the nav (e.g. `() => shell.togglePanel('left')`). */
5
+ onclick: () => void;
6
+ /** id of the nav panel (default AdminShell's left panel). */
7
+ controls?: string;
8
+ /** What is toggled, for the accessible name ("Collapse site navigation"). */
9
+ label?: string;
10
+ }
11
+ declare const ShellNavToggle: import("svelte").Component<Props, {}, "">;
12
+ type ShellNavToggle = ReturnType<typeof ShellNavToggle>;
13
+ export default ShellNavToggle;
14
+ //# sourceMappingURL=ShellNavToggle.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShellNavToggle.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/ShellNavToggle.svelte.ts"],"names":[],"mappings":"AAcA,UAAU,KAAK;IACb,+BAA+B;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmCD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ /** The workspace name in a shell header: a plain link to its home page. */
3
+ interface Props {
4
+ /** Workspace name. */
5
+ title: string;
6
+ /** Its home page. */
7
+ href: string;
8
+ }
9
+
10
+ let { title, href }: Props = $props();
11
+ </script>
12
+
13
+ <a class="smrt-shell-title" {href} data-testid="shell-title">{title}</a>
14
+
15
+ <style>
16
+ .smrt-shell-title {
17
+ display: inline-flex;
18
+ align-items: center;
19
+ min-inline-size: 0;
20
+ max-inline-size: 100%;
21
+ min-block-size: 2.75rem;
22
+ padding: 0 var(--smrt-spacing-2);
23
+ overflow: hidden;
24
+ border-radius: var(--smrt-radius-md);
25
+ color: var(--smrt-color-on-surface);
26
+ font: var(--smrt-typography-title-medium-font);
27
+ /* Themeable: --smrt-shell-title-font-family (a serif masthead, say). */
28
+ font-family: var(--smrt-shell-title-font-family, var(--smrt-typography-title-medium-font-family, inherit));
29
+ text-decoration: none;
30
+ text-overflow: ellipsis;
31
+ white-space: nowrap;
32
+ }
33
+
34
+ .smrt-shell-title:hover {
35
+ background: var(--smrt-color-surface-container-high);
36
+ }
37
+
38
+ .smrt-shell-title:focus-visible {
39
+ outline: 2px solid var(--smrt-color-primary);
40
+ outline-offset: 2px;
41
+ }
42
+ </style>
@@ -0,0 +1,11 @@
1
+ /** The workspace name in a shell header: a plain link to its home page. */
2
+ interface Props {
3
+ /** Workspace name. */
4
+ title: string;
5
+ /** Its home page. */
6
+ href: string;
7
+ }
8
+ declare const ShellTitle: import("svelte").Component<Props, {}, "">;
9
+ type ShellTitle = ReturnType<typeof ShellTitle>;
10
+ export default ShellTitle;
11
+ //# sourceMappingURL=ShellTitle.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShellTitle.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/ShellTitle.svelte.ts"],"names":[],"mappings":"AAGA,2EAA2E;AAC3E,UAAU,KAAK;IACb,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;CACd;AAaD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -35,11 +35,31 @@ function isVisibleActive(item: ShellNavItem): boolean {
35
35
  return collapsed && !!item.children?.some((child) => isActive(child));
36
36
  }
37
37
 
38
+ /** Accessible label of an item's attention dot, or null when it has none. */
39
+ function attentionLabel(item: ShellNavItem): string | null {
40
+ if (!item.attention) return null;
41
+ return typeof item.attention === 'string' && item.attention.trim()
42
+ ? item.attention.trim()
43
+ : t(M['ui.tenant_nav.needs_attention']);
44
+ }
45
+
38
46
  function fallbackIcon(label: string): string {
39
47
  return label.trim().charAt(0).toLocaleUpperCase() || '?';
40
48
  }
41
49
  </script>
42
50
 
51
+ {#snippet attention(item: ShellNavItem)}
52
+ {@const label = attentionLabel(item)}
53
+ {#if label}
54
+ <span
55
+ class="smrt-tenant-nav__attention"
56
+ data-attention
57
+ aria-hidden="true"
58
+ ></span>
59
+ <span class="smrt-tenant-nav__sr-only">({label})</span>
60
+ {/if}
61
+ {/snippet}
62
+
43
63
  <nav
44
64
  class="smrt-tenant-nav"
45
65
  class:smrt-tenant-nav--collapsed={collapsed}
@@ -78,6 +98,7 @@ function fallbackIcon(label: string): string {
78
98
  <small>{item.badge}</small>
79
99
  {/if}
80
100
  {/if}
101
+ {@render attention(item)}
81
102
  </a>
82
103
  {#if item.children?.length && !collapsed}
83
104
  <div class="smrt-tenant-nav__children">
@@ -100,6 +121,7 @@ function fallbackIcon(label: string): string {
100
121
  {#if child.badge !== null && child.badge !== undefined}
101
122
  <small>{child.badge}</small>
102
123
  {/if}
124
+ {@render attention(child)}
103
125
  </a>
104
126
  {/each}
105
127
  </div>
@@ -118,7 +140,10 @@ function fallbackIcon(label: string): string {
118
140
 
119
141
  .smrt-tenant-nav a {
120
142
  display: grid;
121
- grid-template-columns: auto minmax(0, 1fr) auto;
143
+ /* Icon, label, then one auto column per trailing mark (badge, dot). */
144
+ grid-template-columns: auto minmax(0, 1fr);
145
+ grid-auto-flow: column;
146
+ grid-auto-columns: auto;
122
147
  align-items: center;
123
148
  gap: var(--smrt-spacing-2);
124
149
  min-inline-size: 0;
@@ -178,6 +203,26 @@ function fallbackIcon(label: string): string {
178
203
  padding-inline-start: var(--smrt-spacing-4);
179
204
  }
180
205
 
206
+ .smrt-tenant-nav a {
207
+ position: relative;
208
+ }
209
+
210
+ .smrt-tenant-nav__attention {
211
+ inline-size: 0.5rem;
212
+ block-size: 0.5rem;
213
+ border-radius: var(--smrt-radius-full);
214
+ background: var(--smrt-color-error);
215
+ justify-self: end;
216
+ }
217
+
218
+ /* Collapsed: the dot sits on the icon's corner. */
219
+ .smrt-tenant-nav--collapsed .smrt-tenant-nav__attention {
220
+ position: absolute;
221
+ inset-block-start: 0.125rem;
222
+ inset-inline-end: 0.125rem;
223
+ box-shadow: 0 0 0 2px var(--smrt-color-surface);
224
+ }
225
+
181
226
  .smrt-tenant-nav__sr-only {
182
227
  position: absolute;
183
228
  inline-size: 1px;
@@ -1 +1 @@
1
- {"version":3,"file":"TenantNav.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/TenantNav.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,UAAU,KAAK;IACb,sEAAsE;IACtE,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,aAAa,CAAC,EAAE,SAAS,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,2DAA2D;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAsFD,QAAA,MAAM,SAAS,0BAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"TenantNav.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/TenantNav.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,UAAU,KAAK;IACb,sEAAsE;IACtE,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,aAAa,CAAC,EAAE,SAAS,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,2DAA2D;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAwGD,QAAA,MAAM,SAAS,0BAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { type ScrollChromeOptions } from './mobile-shell.js';
2
+ import { type ShellState } from './state.svelte.js';
3
+ /**
4
+ * Keep `shell.viewport` in step with the phone/desktop media queries (which
5
+ * applies each edge's `viewportDefaults` when the class changes).
6
+ */
7
+ export declare function installShellViewport(shell: ShellState): () => void;
8
+ /**
9
+ * Keep `shell.overlayMatches` in step with each side edge's `overlayMedia`
10
+ * query (edges without one are skipped).
11
+ */
12
+ export declare function installShellOverlayMedia(shell: ShellState): () => void;
13
+ /**
14
+ * Hide-on-scroll for one scroller (AdminShell's main region). Samples once
15
+ * per animation frame and reports changes of the hidden state.
16
+ */
17
+ export declare function installScrollChrome(scroller: HTMLElement, options: {
18
+ onChange: (hidden: boolean) => void;
19
+ isPinned: () => boolean;
20
+ tuning?: ScrollChromeOptions;
21
+ }): {
22
+ recheck: () => void;
23
+ reset: () => void;
24
+ destroy: () => void;
25
+ };
26
+ /**
27
+ * Watch for the on-screen keyboard. Sets `data-keyboard-open` on `<html>`
28
+ * (phone bars and `FormActionBar` hide on it) and keeps the focused field in
29
+ * view when the keyboard opens.
30
+ */
31
+ export declare function installKeyboardWatcher(onChange: (open: boolean) => void): () => void;
32
+ /**
33
+ * Report whether `container` holds a form action bar
34
+ * (`[data-form-action-bar]`), now and whenever its subtree changes.
35
+ */
36
+ export declare function watchFormActionBar(container: HTMLElement, onChange: (present: boolean) => void): () => void;
37
+ //# sourceMappingURL=mobile-shell-dom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile-shell-dom.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/mobile-shell-dom.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,mBAAmB,EAEzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,mBAAmB,CAAC;AAE3B;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,IAAI,CAoBlE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,IAAI,CAoBtE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE;IACP,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACpC,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,mBAAmB,CAAC;CAC9B,GACA;IAAE,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAqCjE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,GAChC,MAAM,IAAI,CA6BZ;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,WAAW,EACtB,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GACnC,MAAM,IAAI,CAYZ"}