@revenexx/studio-shared 0.3.3 → 0.4.1

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 (205) hide show
  1. package/dist/module.d.mts +7 -3
  2. package/dist/module.json +2 -2
  3. package/dist/module.mjs +10 -4
  4. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.d.vue.ts +2 -1
  5. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue +1 -1
  6. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue.d.ts +2 -1
  7. package/dist/runtime/components/Dashboard/DashboardControls.d.vue.ts +8 -7
  8. package/dist/runtime/components/Dashboard/DashboardControls.vue.d.ts +8 -7
  9. package/dist/runtime/components/Dashboard/DashboardGrid.d.vue.ts +2 -1
  10. package/dist/runtime/components/Dashboard/DashboardGrid.vue +45 -1
  11. package/dist/runtime/components/Dashboard/DashboardGrid.vue.d.ts +2 -1
  12. package/dist/runtime/components/Dashboard/DashboardNameDialog.d.vue.ts +2 -1
  13. package/dist/runtime/components/Dashboard/DashboardNameDialog.vue +1 -1
  14. package/dist/runtime/components/Dashboard/DashboardNameDialog.vue.d.ts +2 -1
  15. package/dist/runtime/components/Dashboard/DashboardPickerDialog.d.vue.ts +2 -1
  16. package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue +1 -1
  17. package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue.d.ts +2 -1
  18. package/dist/runtime/components/Dashboard/DashboardShareDialog.d.vue.ts +2 -1
  19. package/dist/runtime/components/Dashboard/DashboardShareDialog.vue +1 -1
  20. package/dist/runtime/components/Dashboard/DashboardShareDialog.vue.d.ts +2 -1
  21. package/dist/runtime/components/Dashboard/DashboardTopBar.d.vue.ts +9 -4
  22. package/dist/runtime/components/Dashboard/DashboardTopBar.vue.d.ts +9 -4
  23. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.d.vue.ts +8 -6
  24. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue +1 -1
  25. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue.d.ts +8 -6
  26. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +5 -4
  27. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue +14 -3
  28. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +5 -4
  29. package/dist/runtime/components/Dashboard/DashboardWidgetHost.d.vue.ts +2 -1
  30. package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue.d.ts +2 -1
  31. package/dist/runtime/components/Dashboard/DashboardWidgetIcon.d.vue.ts +2 -1
  32. package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue.d.ts +2 -1
  33. package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.d.vue.ts +2 -1
  34. package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue.d.ts +2 -1
  35. package/dist/runtime/components/Dashboard/DashboardWidgetPreview.d.vue.ts +2 -1
  36. package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue.d.ts +2 -1
  37. package/dist/runtime/components/DataTable/ColumnsMenu.d.vue.ts +2 -1
  38. package/dist/runtime/components/DataTable/ColumnsMenu.vue.d.ts +2 -1
  39. package/dist/runtime/components/DataTable/DataTable.d.vue.ts +120 -37
  40. package/dist/runtime/components/DataTable/DataTable.vue +493 -53
  41. package/dist/runtime/components/DataTable/DataTable.vue.d.ts +120 -37
  42. package/dist/runtime/components/DataTable/FacetChips.d.vue.ts +2 -1
  43. package/dist/runtime/components/DataTable/FacetChips.vue.d.ts +2 -1
  44. package/dist/runtime/components/DataTable/FacetPanel.d.vue.ts +6 -5
  45. package/dist/runtime/components/DataTable/FacetPanel.vue +57 -8
  46. package/dist/runtime/components/DataTable/FacetPanel.vue.d.ts +6 -5
  47. package/dist/runtime/components/DataTable/FilterMenu.d.vue.ts +6 -5
  48. package/dist/runtime/components/DataTable/FilterMenu.vue +5 -5
  49. package/dist/runtime/components/DataTable/FilterMenu.vue.d.ts +6 -5
  50. package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +2 -1
  51. package/dist/runtime/components/DataTable/SavedViewsMenu.vue +61 -12
  52. package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +2 -1
  53. package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +4 -2
  54. package/dist/runtime/components/DataTable/SortMenu.vue +4 -1
  55. package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +4 -2
  56. package/dist/runtime/components/DataTable/ToolbarLabelTrigger.d.vue.ts +23 -0
  57. package/dist/runtime/components/DataTable/ToolbarLabelTrigger.vue +29 -0
  58. package/dist/runtime/components/DataTable/ToolbarLabelTrigger.vue.d.ts +23 -0
  59. package/dist/runtime/components/DataTable/ViewModeSwitch.d.vue.ts +4 -2
  60. package/dist/runtime/components/DataTable/ViewModeSwitch.vue.d.ts +4 -2
  61. package/dist/runtime/components/DataTable/types.d.ts +17 -0
  62. package/dist/runtime/components/Form/FieldCell.d.vue.ts +32 -0
  63. package/dist/runtime/components/Form/FieldCell.vue +46 -0
  64. package/dist/runtime/components/Form/FieldCell.vue.d.ts +32 -0
  65. package/dist/runtime/components/Form/FieldGrid.d.vue.ts +13 -0
  66. package/dist/runtime/components/Form/FieldGrid.vue +11 -0
  67. package/dist/runtime/components/Form/FieldGrid.vue.d.ts +13 -0
  68. package/dist/runtime/components/Form/FormContextCard.d.vue.ts +33 -0
  69. package/dist/runtime/components/Form/FormContextCard.vue +56 -0
  70. package/dist/runtime/components/Form/FormContextCard.vue.d.ts +33 -0
  71. package/dist/runtime/components/Form/FormLayout.d.vue.ts +47 -0
  72. package/dist/runtime/components/Form/FormLayout.vue +169 -0
  73. package/dist/runtime/components/Form/FormLayout.vue.d.ts +47 -0
  74. package/dist/runtime/components/Form/FormSection.d.vue.ts +25 -0
  75. package/dist/runtime/components/Form/FormSection.vue +48 -0
  76. package/dist/runtime/components/Form/FormSection.vue.d.ts +25 -0
  77. package/dist/runtime/components/Form/SaveBar.d.vue.ts +44 -0
  78. package/dist/runtime/components/Form/SaveBar.vue +71 -0
  79. package/dist/runtime/components/Form/SaveBar.vue.d.ts +44 -0
  80. package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +6 -5
  81. package/dist/runtime/components/Media/MediaFolderTree.vue +1 -2
  82. package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +6 -5
  83. package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +13 -1
  84. package/dist/runtime/components/Media/MediaPickerDialog.vue +206 -54
  85. package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +13 -1
  86. package/dist/runtime/components/Media/MediaUploader.d.vue.ts +2 -1
  87. package/dist/runtime/components/Media/MediaUploader.vue +1 -1
  88. package/dist/runtime/components/Media/MediaUploader.vue.d.ts +2 -1
  89. package/dist/runtime/components/Shared/AppIcon.d.vue.ts +21 -0
  90. package/dist/runtime/components/Shared/AppIcon.vue +47 -0
  91. package/dist/runtime/components/Shared/AppIcon.vue.d.ts +21 -0
  92. package/dist/runtime/components/Shared/BuildLogsFlyout.d.vue.ts +2 -1
  93. package/dist/runtime/components/Shared/BuildLogsFlyout.vue.d.ts +2 -1
  94. package/dist/runtime/components/Shared/CardSizeToggle.d.vue.ts +11 -0
  95. package/dist/runtime/components/Shared/CardSizeToggle.vue +19 -0
  96. package/dist/runtime/components/Shared/CardSizeToggle.vue.d.ts +11 -0
  97. package/dist/runtime/components/Shared/DetailSheet.d.vue.ts +60 -0
  98. package/dist/runtime/components/Shared/DetailSheet.vue +211 -0
  99. package/dist/runtime/components/Shared/DetailSheet.vue.d.ts +60 -0
  100. package/dist/runtime/components/Shared/EmptyNote.d.vue.ts +19 -0
  101. package/dist/runtime/components/Shared/EmptyNote.vue +25 -0
  102. package/dist/runtime/components/Shared/EmptyNote.vue.d.ts +19 -0
  103. package/dist/runtime/components/Shared/FloatingPanel.d.vue.ts +7 -6
  104. package/dist/runtime/components/Shared/FloatingPanel.vue.d.ts +7 -6
  105. package/dist/runtime/components/Shared/GitDeploymentSettings.d.vue.ts +2 -1
  106. package/dist/runtime/components/Shared/GitDeploymentSettings.vue.d.ts +2 -1
  107. package/dist/runtime/components/Shared/KpiTile.d.vue.ts +8 -1
  108. package/dist/runtime/components/Shared/KpiTile.vue +10 -9
  109. package/dist/runtime/components/Shared/KpiTile.vue.d.ts +8 -1
  110. package/dist/runtime/components/Shared/ListSurface.d.vue.ts +3 -2
  111. package/dist/runtime/components/Shared/ListSurface.vue.d.ts +3 -2
  112. package/dist/runtime/components/Shared/PageHeader.d.vue.ts +7 -6
  113. package/dist/runtime/components/Shared/PageHeader.vue +15 -4
  114. package/dist/runtime/components/Shared/PageHeader.vue.d.ts +7 -6
  115. package/dist/runtime/components/Shared/RecordLayout.d.vue.ts +8 -7
  116. package/dist/runtime/components/Shared/RecordLayout.vue +1 -2
  117. package/dist/runtime/components/Shared/RecordLayout.vue.d.ts +8 -7
  118. package/dist/runtime/components/Shared/RecordRailCard.d.vue.ts +3 -2
  119. package/dist/runtime/components/Shared/RecordRailCard.vue +3 -3
  120. package/dist/runtime/components/Shared/RecordRailCard.vue.d.ts +3 -2
  121. package/dist/runtime/components/Shared/SelectionBar.d.vue.ts +22 -0
  122. package/dist/runtime/components/Shared/SelectionBar.vue +40 -0
  123. package/dist/runtime/components/Shared/SelectionBar.vue.d.ts +22 -0
  124. package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +39 -4
  125. package/dist/runtime/components/Shared/SidebarNav.vue +71 -25
  126. package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +39 -4
  127. package/dist/runtime/components/Shared/SidebarNavAreas.d.vue.ts +7 -0
  128. package/dist/runtime/components/Shared/SidebarNavAreas.vue +291 -0
  129. package/dist/runtime/components/Shared/SidebarNavAreas.vue.d.ts +7 -0
  130. package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +3 -10
  131. package/dist/runtime/components/Shared/SidebarNavLink.vue +31 -5
  132. package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +3 -10
  133. package/dist/runtime/components/Shared/SortHeader.d.vue.ts +3 -2
  134. package/dist/runtime/components/Shared/SortHeader.vue +6 -2
  135. package/dist/runtime/components/Shared/SortHeader.vue.d.ts +3 -2
  136. package/dist/runtime/components/Shared/StateCard.d.vue.ts +6 -5
  137. package/dist/runtime/components/Shared/StateCard.vue +1 -1
  138. package/dist/runtime/components/Shared/StateCard.vue.d.ts +6 -5
  139. package/dist/runtime/components/Shared/StudioCard.d.vue.ts +5 -4
  140. package/dist/runtime/components/Shared/StudioCard.vue.d.ts +5 -4
  141. package/dist/runtime/components/Shared/TabBar.d.vue.ts +4 -21
  142. package/dist/runtime/components/Shared/TabBar.vue +30 -13
  143. package/dist/runtime/components/Shared/TabBar.vue.d.ts +4 -21
  144. package/dist/runtime/components/Shared/ViewTab.d.vue.ts +2 -1
  145. package/dist/runtime/components/Shared/ViewTab.vue.d.ts +2 -1
  146. package/dist/runtime/components/Shared/Wizard.d.vue.ts +20 -7
  147. package/dist/runtime/components/Shared/Wizard.vue +9 -5
  148. package/dist/runtime/components/Shared/Wizard.vue.d.ts +20 -7
  149. package/dist/runtime/composables/useDetailSheet.d.ts +20 -0
  150. package/dist/runtime/composables/useDetailSheet.js +50 -0
  151. package/dist/runtime/composables/useMediaLibrary.d.ts +12 -0
  152. package/dist/runtime/composables/usePlatformTenant.d.ts +6 -0
  153. package/dist/runtime/composables/useSavedViews.d.ts +11 -1
  154. package/dist/runtime/composables/useSavedViews.js +4 -4
  155. package/dist/runtime/composables/useWidgetFigures.d.ts +12 -0
  156. package/dist/runtime/composables/useWidgetFigures.js +24 -0
  157. package/dist/runtime/composables/useWidgetHeader.d.ts +15 -0
  158. package/dist/runtime/composables/useWidgetHeader.js +15 -0
  159. package/dist/runtime/lib/activeTenant.d.ts +58 -0
  160. package/dist/runtime/lib/activeTenant.js +45 -0
  161. package/dist/runtime/lib/appIcon.d.ts +48 -0
  162. package/dist/runtime/lib/appIcon.js +27 -0
  163. package/dist/runtime/lib/auditEnvelope.d.ts +43 -0
  164. package/dist/runtime/lib/auditEnvelope.js +55 -0
  165. package/dist/runtime/lib/cardSize.d.ts +13 -0
  166. package/dist/runtime/lib/cardSize.js +20 -0
  167. package/dist/runtime/lib/dashboardIcons.js +2 -0
  168. package/dist/runtime/lib/dataTableFilters.d.ts +22 -0
  169. package/dist/runtime/lib/dataTableFilters.js +5 -0
  170. package/dist/runtime/lib/detailSheet.d.ts +43 -0
  171. package/dist/runtime/lib/detailSheet.js +34 -0
  172. package/dist/runtime/lib/facets.d.ts +13 -0
  173. package/dist/runtime/lib/formLayout.d.ts +164 -0
  174. package/dist/runtime/lib/formLayout.js +117 -0
  175. package/dist/runtime/lib/formLayoutContext.d.ts +26 -0
  176. package/dist/runtime/lib/formLayoutContext.js +1 -0
  177. package/dist/runtime/lib/gridTracks.d.ts +14 -1
  178. package/dist/runtime/lib/gridTracks.js +13 -2
  179. package/dist/runtime/lib/isoLists.d.ts +37 -2
  180. package/dist/runtime/lib/isoLists.js +54 -1
  181. package/dist/runtime/lib/notice.d.ts +14 -0
  182. package/dist/runtime/lib/notice.js +19 -0
  183. package/dist/runtime/lib/pageSize.d.ts +15 -0
  184. package/dist/runtime/lib/pageSize.js +26 -0
  185. package/dist/runtime/lib/rowMove.d.ts +38 -0
  186. package/dist/runtime/lib/rowMove.js +20 -0
  187. package/dist/runtime/lib/sidebarAreas.d.ts +46 -0
  188. package/dist/runtime/lib/sidebarAreas.js +25 -0
  189. package/dist/runtime/lib/sidebarNav.d.ts +21 -2
  190. package/dist/runtime/lib/sidebarNav.js +23 -4
  191. package/dist/runtime/lib/solarIconMap.d.ts +16 -0
  192. package/dist/runtime/lib/solarIconMap.js +326 -0
  193. package/dist/runtime/lib/solarIcons.d.ts +37 -0
  194. package/dist/runtime/lib/solarIcons.js +25 -0
  195. package/dist/runtime/lib/swipeToClose.d.ts +25 -0
  196. package/dist/runtime/lib/swipeToClose.js +118 -0
  197. package/dist/runtime/lib/tableExport.d.ts +31 -0
  198. package/dist/runtime/lib/tableExport.js +143 -0
  199. package/dist/runtime/lib/talkback.d.ts +5 -0
  200. package/dist/runtime/lib/talkback.js +8 -0
  201. package/dist/runtime/lib/talkbackTokenBatch.d.ts +35 -0
  202. package/dist/runtime/lib/talkbackTokenBatch.js +101 -0
  203. package/dist/runtime/types/nav.d.ts +9 -0
  204. package/dist/types.d.mts +8 -0
  205. package/package.json +4 -3
@@ -0,0 +1,48 @@
1
+ /**
2
+ * What an app's icon is, read from the one field the manifest documents (PE-560).
3
+ *
4
+ * `manifest.icon` is either a FILE or a SOLAR ICON NAME, told apart by shape:
5
+ *
6
+ * - a file contains a `/` or ends in `.svg`, `.png` or `.webp` — a URL, a
7
+ * `data:` URI, or a path the registry has resolved to one;
8
+ * - a Solar name is kebab-case (`money-bag`), the same token a `cockpit.json`
9
+ * navigation entry carries.
10
+ *
11
+ * Anything else is neither and is skipped. The Cockpit used to read the field
12
+ * three ways — a Solar name in the sidebar, an `<img src>` on the installed
13
+ * pages, not at all in the Marketplace — so no value rendered everywhere, and
14
+ * no app shipped one.
15
+ *
16
+ * Pure on purpose: the Solar registry lookup lives in `solarIcons.ts`, so this
17
+ * module has no imports and is testable without a Nuxt alias.
18
+ */
19
+ export type AppIconSource = {
20
+ kind: 'file';
21
+ src: string;
22
+ } | {
23
+ kind: 'solar';
24
+ name: string;
25
+ };
26
+ /**
27
+ * `money-bag` → `MoneyBag`, the export name in the Solar registry. Snake case
28
+ * (`card_2`) and the export name itself (`Layers`) read the same way.
29
+ * `scripts/icons.mjs` carries a copy (`exportName`) and must agree with it.
30
+ */
31
+ export declare function solarExportName(name: string): string;
32
+ /** How one `manifest.icon` value reads, or null when it is neither shape. */
33
+ export declare function readAppIcon(value: string | null | undefined): AppIconSource | null;
34
+ /**
35
+ * The Solar name of the first `cockpit.json` navigation entry that has one —
36
+ * the glyph the app already shows in its host studio's sidebar, and the
37
+ * fallback for the apps that set no `manifest.icon`.
38
+ */
39
+ export declare function firstNavIcon(navigation: ReadonlyArray<{
40
+ icon?: string | null;
41
+ }> | null | undefined): string | null;
42
+ /**
43
+ * Every source to try, in order: `manifest.icon` (file or Solar name), then the
44
+ * navigation glyph. The renderer walks the list and draws the first that
45
+ * resolves — an image that fails to load, or a name the registry lacks, falls
46
+ * through to the next, and past the end to the generic glyph.
47
+ */
48
+ export declare function appIconCandidates(icon: string | null | undefined, navIcon?: string | null): AppIconSource[];
@@ -0,0 +1,27 @@
1
+ const FILE_EXTENSION = /\.(?:svg|png|webp)$/i;
2
+ const SOLAR_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3
+ export function solarExportName(name) {
4
+ return name.replace(/(^|[-_])([a-z0-9])/gi, (_m, _sep, ch) => ch.toUpperCase());
5
+ }
6
+ export function readAppIcon(value) {
7
+ const v = value?.trim();
8
+ if (!v) return null;
9
+ if (v.includes("/") || FILE_EXTENSION.test(v)) return { kind: "file", src: v };
10
+ if (SOLAR_NAME.test(v)) return { kind: "solar", name: v };
11
+ return null;
12
+ }
13
+ export function firstNavIcon(navigation) {
14
+ for (const item of navigation ?? []) {
15
+ const icon = item.icon?.trim();
16
+ if (icon) return icon;
17
+ }
18
+ return null;
19
+ }
20
+ export function appIconCandidates(icon, navIcon) {
21
+ const out = [];
22
+ const own = readAppIcon(icon);
23
+ if (own) out.push(own);
24
+ const nav = navIcon?.trim();
25
+ if (nav && !(own?.kind === "solar" && own.name === nav)) out.push({ kind: "solar", name: nav });
26
+ return out;
27
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The impersonation audit envelope (PE-1040, ADR-0114 / PE-1012).
3
+ *
4
+ * While an operator impersonates a tenant, Console's `/operator/impersonate`
5
+ * answers with a signed `audit` value next to the brokered token. Sent as
6
+ * `X-Revenexx-Audit`, it tells Baseline's history which human acted behind the
7
+ * operator subject. Baseline checks the signature, the tenant, the token's
8
+ * `sub` and the expiry, and ignores anything that does not hold, so a missing,
9
+ * stale or foreign envelope only costs the name in the history, never a write.
10
+ *
11
+ * Two switches keep it harmless:
12
+ * - nothing is held outside impersonation: `useOperatorSession` stores the
13
+ * value on install/renew and clears it on exit, and a Console that sends no
14
+ * `audit` field stores nothing;
15
+ * - nothing is sent until the runtime config names the target
16
+ * (`NUXT_PUBLIC_AUDIT_ENVELOPE_TARGETS=postgrest,gateway`). A browser can
17
+ * only send a header the server's CORS preflight allows, and a header it
18
+ * does not allow fails the whole request, so each target is switched on once
19
+ * its edge lists the header.
20
+ *
21
+ * Pure (no Nuxt imports), so the cockpit, studio-commerce and tests share it.
22
+ */
23
+ /** The header Baseline reads the envelope from. */
24
+ export declare const AUDIT_HEADER = "X-Revenexx-Audit";
25
+ /** Where the envelope can go. Each one is switched on in the runtime config. */
26
+ export type AuditEnvelopeTarget = 'postgrest' | 'gateway';
27
+ /** localStorage key: the envelope survives the reload a tenant switch does. */
28
+ export declare const AUDIT_ENVELOPE_STORAGE_KEY = "console.auditEnvelope";
29
+ /** The value Console sent, if it is one we may send on. */
30
+ export declare function parseAuditEnvelope(value: unknown): string | null;
31
+ /** Store (or, with null, drop) the envelope of the current operator session. */
32
+ export declare function setAuditEnvelope(value: unknown): void;
33
+ /** The envelope of the current operator session, or null. */
34
+ export declare function currentAuditEnvelope(): string | null;
35
+ /** `"postgrest, gateway"` (or an array) → the targets it names. */
36
+ export declare function auditEnvelopeTargets(configured: unknown): Set<AuditEnvelopeTarget>;
37
+ /**
38
+ * The header to add to a request for `target`: `{}` unless the target is
39
+ * switched on and an operator session holds an envelope.
40
+ */
41
+ export declare function auditHeaders(target: AuditEnvelopeTarget, configured: unknown): Record<string, string>;
42
+ /** For tests: forget what was read from storage. */
43
+ export declare function resetAuditEnvelopeCache(): void;
@@ -0,0 +1,55 @@
1
+ export const AUDIT_HEADER = "X-Revenexx-Audit";
2
+ export const AUDIT_ENVELOPE_STORAGE_KEY = "console.auditEnvelope";
3
+ const ENVELOPE = /^v1\.[\w-]{1,64}\.[\w-]{1,4096}\.[\w-]{16,256}$/;
4
+ let held;
5
+ function storage() {
6
+ try {
7
+ return typeof window !== "undefined" && window.localStorage ? window.localStorage : null;
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+ export function parseAuditEnvelope(value) {
13
+ if (typeof value !== "string") return null;
14
+ const trimmed = value.trim();
15
+ return ENVELOPE.test(trimmed) ? trimmed : null;
16
+ }
17
+ export function setAuditEnvelope(value) {
18
+ held = parseAuditEnvelope(value);
19
+ const store = storage();
20
+ if (!store) return;
21
+ try {
22
+ if (held) store.setItem(AUDIT_ENVELOPE_STORAGE_KEY, held);
23
+ else store.removeItem(AUDIT_ENVELOPE_STORAGE_KEY);
24
+ } catch {
25
+ }
26
+ }
27
+ export function currentAuditEnvelope() {
28
+ if (held === void 0) {
29
+ let stored = null;
30
+ try {
31
+ stored = storage()?.getItem(AUDIT_ENVELOPE_STORAGE_KEY) ?? null;
32
+ } catch {
33
+ stored = null;
34
+ }
35
+ held = parseAuditEnvelope(stored);
36
+ }
37
+ return held;
38
+ }
39
+ export function auditEnvelopeTargets(configured) {
40
+ const raw = Array.isArray(configured) ? configured.join(",") : typeof configured === "string" ? configured : "";
41
+ const out = /* @__PURE__ */ new Set();
42
+ for (const part of raw.split(",")) {
43
+ const name = part.trim().toLowerCase();
44
+ if (name === "postgrest" || name === "gateway") out.add(name);
45
+ }
46
+ return out;
47
+ }
48
+ export function auditHeaders(target, configured) {
49
+ if (!auditEnvelopeTargets(configured).has(target)) return {};
50
+ const envelope = currentAuditEnvelope();
51
+ return envelope ? { [AUDIT_HEADER]: envelope } : {};
52
+ }
53
+ export function resetAuditEnvelopeCache() {
54
+ held = void 0;
55
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Small or large cards in a gallery (Mediathek, PIM assets) — the choice the
3
+ * `CardSizeToggle` makes, remembered per viewer on this browser.
4
+ *
5
+ * Browser storage is a convenience here: a private window or blocked storage
6
+ * simply starts at the large cards again.
7
+ */
8
+ export type CardSize = 'comfortable' | 'compact';
9
+ export declare function parseCardSize(value: unknown): CardSize;
10
+ export declare function storedCardSize(key: string): CardSize | null;
11
+ export declare function storeCardSize(key: string, size: CardSize): void;
12
+ /** The gallery grid's columns per size: more, smaller cards when compact. */
13
+ export declare function cardGridClass(size: CardSize): string;
@@ -0,0 +1,20 @@
1
+ export function parseCardSize(value) {
2
+ return value === "compact" ? "compact" : "comfortable";
3
+ }
4
+ export function storedCardSize(key) {
5
+ try {
6
+ const v = typeof localStorage === "undefined" ? null : localStorage.getItem(key);
7
+ return v === "compact" || v === "comfortable" ? v : null;
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+ export function storeCardSize(key, size) {
13
+ try {
14
+ localStorage.setItem(key, size);
15
+ } catch {
16
+ }
17
+ }
18
+ export function cardGridClass(size) {
19
+ return size === "compact" ? "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6" : "grid-cols-1 min-[480px]:grid-cols-2 md:grid-cols-3 2xl:grid-cols-4";
20
+ }
@@ -1,4 +1,5 @@
1
1
  import {
2
+ Atom,
2
3
  Bag,
3
4
  BillList,
4
5
  Bolt,
@@ -22,6 +23,7 @@ import {
22
23
  Widget
23
24
  } from "#solar-icons";
24
25
  export const DASHBOARD_ICONS = {
26
+ Atom,
25
27
  Bag,
26
28
  BillList,
27
29
  Bolt,
@@ -81,3 +81,25 @@ export declare function countByValue<T>(rows: readonly T[], getter: FilterGetter
81
81
  * whose value happens to be `''` must not be the only thing that survives it.
82
82
  */
83
83
  export declare function matchesFilter<T>(getter: FilterGetter<T>, row: T, active: string | readonly string[] | null | undefined): boolean;
84
+ /** One dimension as the counting below needs it: its key and how a row reads under it. */
85
+ export interface FilterDimension<T> {
86
+ key: string;
87
+ getter: FilterGetter<T>;
88
+ }
89
+ /**
90
+ * How many rows each option of one dimension would leave, given what is ticked.
91
+ * `rows` is what the search already leaves; a caller without a search passes
92
+ * the whole list.
93
+ *
94
+ * Counted over the rows that survive every OTHER dimension's selection, and not
95
+ * this dimension's own. With "Status: Active" ticked, "Channel: Email 3" means
96
+ * three active email rows — the number a click on Email will actually show —
97
+ * where counting the whole list offered rows the Active choice had already
98
+ * removed ("Ready 3" that yielded nothing, PO-313).
99
+ *
100
+ * Not this dimension's own selection, because inside one dimension the choices
101
+ * are ANY (see `matchesFilter`): ticking Draft beside Active ADDS the drafts, so
102
+ * "Draft" has to keep counting the drafts rather than drop to zero the moment
103
+ * Active is ticked.
104
+ */
105
+ export declare function facetCounts<T>(rows: readonly T[], dimensions: readonly FilterDimension<T>[], active: Readonly<Record<string, readonly string[] | undefined>>, dimension: FilterDimension<T>): Record<string, number>;
@@ -21,3 +21,8 @@ export function matchesFilter(getter, row, active) {
21
21
  const values = filterValues(getter, row);
22
22
  return wanted.some((v) => values.includes(v));
23
23
  }
24
+ export function facetCounts(rows, dimensions, active, dimension) {
25
+ const others = dimensions.filter((d) => d !== dimension && active[d.key]?.length);
26
+ const narrowed = others.length ? rows.filter((row) => others.every((d) => matchesFilter(d.getter, row, active[d.key]))) : rows;
27
+ return countByValue(narrowed, dimension.getter);
28
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The parts of `DetailSheet` that are arithmetic rather than UI (PE-691).
3
+ *
4
+ * Kept out of the component and the composable so they can be tested without a
5
+ * router or a DOM: which record is next, what the query looks like with the
6
+ * panel open or shut, and whether a keypress belongs to the panel or to the
7
+ * control it landed on.
8
+ */
9
+ export type QueryValue = string | null | (string | null)[] | undefined;
10
+ export type Query = Record<string, QueryValue>;
11
+ /** The open record id from a query value — the first one if a link repeats it. */
12
+ export declare function idFromQuery(value: QueryValue): string | null;
13
+ /** `query` with `key` set to `id`, or without `key` when `id` is null. */
14
+ export declare function queryWith(query: Query, key: string, id: string | null): Query;
15
+ /**
16
+ * The id `step` places away from `current` in `ids`, or null at either end.
17
+ *
18
+ * No wrap-around: arrowing past the last row and landing on the first reads as
19
+ * the list having jumped, and in a paginated list the first row is not even on
20
+ * the page. An id that is no longer in the list (deleted, filtered out) has no
21
+ * neighbours either — guessing one would open a record nobody pointed at.
22
+ */
23
+ export declare function neighbourId(ids: readonly string[], current: string | null, step: -1 | 1): string | null;
24
+ interface KeyEvent {
25
+ key: string;
26
+ altKey?: boolean;
27
+ ctrlKey?: boolean;
28
+ metaKey?: boolean;
29
+ shiftKey?: boolean;
30
+ defaultPrevented?: boolean;
31
+ target?: unknown;
32
+ }
33
+ /**
34
+ * Whether the focused element handles arrows and Escape itself: a text field
35
+ * (caret, clearing), a select, a menu, a listbox, a slider. The panel's keys
36
+ * give way there — taking them would break the control to save one click.
37
+ */
38
+ export declare function targetOwnsKeys(target: unknown): boolean;
39
+ /** Whether ArrowUp/ArrowDown should move to the neighbouring record, and which way. */
40
+ export declare function isRecordStepKey(ev: KeyEvent): -1 | 1 | null;
41
+ /** Whether Escape should close the panel rather than the control it landed on. */
42
+ export declare function isCloseKey(ev: KeyEvent): boolean;
43
+ export {};
@@ -0,0 +1,34 @@
1
+ export function idFromQuery(value) {
2
+ const v = Array.isArray(value) ? value[0] : value;
3
+ return typeof v === "string" && v !== "" ? v : null;
4
+ }
5
+ export function queryWith(query, key, id) {
6
+ const next = { ...query };
7
+ if (id === null) delete next[key];
8
+ else next[key] = id;
9
+ return next;
10
+ }
11
+ export function neighbourId(ids, current, step) {
12
+ if (current === null) return null;
13
+ const i = ids.indexOf(current);
14
+ if (i < 0) return null;
15
+ return ids[i + step] ?? null;
16
+ }
17
+ export function targetOwnsKeys(target) {
18
+ const t = target;
19
+ if (!t) return false;
20
+ const tag = (t.tagName ?? "").toUpperCase();
21
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
22
+ if (t.isContentEditable) return true;
23
+ return !!t.closest?.('[role="menu"],[role="listbox"],[role="combobox"],[role="slider"],[role="spinbutton"],[role="radiogroup"],[role="tablist"]');
24
+ }
25
+ export function isRecordStepKey(ev) {
26
+ if (ev.defaultPrevented) return null;
27
+ if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey) return null;
28
+ const step = ev.key === "ArrowDown" ? 1 : ev.key === "ArrowUp" ? -1 : null;
29
+ if (step === null) return null;
30
+ return targetOwnsKeys(ev.target) ? null : step;
31
+ }
32
+ export function isCloseKey(ev) {
33
+ return ev.key === "Escape" && !ev.defaultPrevented && !targetOwnsKeys(ev.target);
34
+ }
@@ -54,6 +54,11 @@ export interface FacetOption {
54
54
  count?: number;
55
55
  /** Greyed and unclickable, kept visible so the dimension still reads whole. */
56
56
  disabled?: boolean;
57
+ /**
58
+ * A second, smaller line — the customer number or e-mail that tells two
59
+ * options of the same name apart (a searched facet's hits, Michael 19.10.).
60
+ */
61
+ description?: string;
57
62
  }
58
63
  /** One node of a `tree` facet. */
59
64
  export interface FacetNode {
@@ -89,6 +94,14 @@ export interface Facet {
89
94
  * nothing, so offering it as a pair of checkboxes offers a dead end.
90
95
  */
91
96
  single?: boolean;
97
+ /**
98
+ * `check` over a set that grows with the tenant — organisations, contacts,
99
+ * price lists: the pane asks the server for what matches the panel's search
100
+ * box (an empty term browses the first page) instead of listing `options`.
101
+ * `options` then carries only the values already picked, with their names,
102
+ * so a ticked value stays on screen and the chips can name it.
103
+ */
104
+ search?: (term: string) => Promise<FacetOption[]>;
92
105
  }
93
106
  /** `{ from, to }`, either side optional. */
94
107
  export interface FacetRange {
@@ -0,0 +1,164 @@
1
+ /**
2
+ * The decisions behind the shared form layout (PE-976), as pure functions.
3
+ *
4
+ * `FormLayout`, `FormSection`, `FieldGrid`, `FieldCell`, `FormContextCard` and
5
+ * `SaveBar` draw a form; this file decides the parts that have to agree across
6
+ * every surface that draws one — how wide a field is when nobody said, when the
7
+ * section navigation appears, which section the navigation marks while the
8
+ * page scrolls, and which grid tracks the layout gets. The Commerce renderer
9
+ * (mapping a manifest), hand-written cockpit forms and the manifest lint all
10
+ * read the same answers from here, so a phone number cannot be half width in
11
+ * one place and full width in another.
12
+ *
13
+ * The class strings live here as whole literals on purpose: Tailwind finds
14
+ * classes by scanning source text, so a class assembled at runtime from parts
15
+ * would never be generated. This directory is under the host's `@source`.
16
+ */
17
+ /** How much of a two-column field grid a field takes. */
18
+ export type FieldSpan = 'half' | 'full';
19
+ /** The width a field of `fieldType` gets when nothing says otherwise. */
20
+ export declare function defaultSpan(fieldType: string | null | undefined): FieldSpan;
21
+ /** What `resolveSpan` reads — a manifest field fits, so does a hand-written one. */
22
+ export interface SpanSource {
23
+ type?: string | null;
24
+ /** `auto` (or absent) means "the type decides". */
25
+ span?: 'auto' | FieldSpan | string | null;
26
+ }
27
+ /** An explicit `half` / `full` wins; `auto`, absent or unknown falls back to the type. */
28
+ export declare function resolveSpan(field: SpanSource): FieldSpan;
29
+ /**
30
+ * The grid-item class for a span inside `FieldGrid`.
31
+ *
32
+ * The grid is one column until its OWN width reaches the `@lg` container size
33
+ * (32rem) and two above it — a container query, not a viewport breakpoint, so
34
+ * the same grid is right in a 512 px dialog, beside a section nav, and in a
35
+ * full-width page. A full field spans both tracks once there are two.
36
+ */
37
+ export declare function spanClass(span: FieldSpan): string;
38
+ /**
39
+ * The label/value grid of a record card (Michael 19.10.: „ich will es gerne
40
+ * zweispaltig haben“). The read card and the same card opened with
41
+ * „Bearbeiten“ both draw their rows into this grid, so nothing moves when the
42
+ * values turn into inputs.
43
+ *
44
+ * One column of label/value rows until the grid itself is 832 px wide, two
45
+ * above — row-major, so the fields read left to right, top to bottom. A
46
+ * container query on the card body, not the viewport: a rail card, a dialog
47
+ * and a record page on a 1440 px window stay one column. The label keeps its
48
+ * 160 px column in each cell, so 832 px is where a half cell still leaves the
49
+ * value 228 px — a country with its flag and code, a currency, a date. Any
50
+ * narrower and the second column only squeezes inputs into stubs.
51
+ */
52
+ export declare const PAIR_GRID_CLASS = "grid grid-cols-1 gap-x-8 gap-y-2.5 @min-[832px]:grid-cols-2 [clip-path:inset(-24px_-24px_1px_-24px)]";
53
+ /** The grid-item class for a span inside {@link PAIR_GRID_CLASS}: a full field takes both columns. */
54
+ export declare function pairSpanClass(span: FieldSpan): string;
55
+ /** Every cell carries a faint rule underneath, so the eye finds each row fast (Michael 19.10.). */
56
+ export declare const PAIR_CELL_CLASS = "min-w-0 border-b border-black/[0.06] pb-2.5";
57
+ /**
58
+ * The compact control size on every form (Michael 19.10.: „stelle die
59
+ * Formularfelder auf die kleinen um … wir verlieren viel Höhe“): inputs,
60
+ * selects, comboboxes, lookups and number fields as tall as the card's own
61
+ * buttons (32 px, the design system's `FIELD_HEIGHT_SM`). Joined controls —
62
+ * phone (code + number), an input with a unit or currency addon — are an
63
+ * input group and follow too, unless the addon stacks above or below. Set on the field's
64
+ * wrapper so every control inside — studio's and ours — follows without a
65
+ * size prop threaded through each one. Textareas and the per-language stack
66
+ * keep their own height.
67
+ */
68
+ export declare const COMPACT_CONTROLS_CLASS = "[&_[data-slot=input]]:h-8 [&_[data-slot=select-trigger]]:h-8 [&_[data-slot=combobox-select-trigger]]:h-8 [&_[data-slot=combobox-anchor]]:min-h-8 [&_[data-slot=number-field-input]]:h-8 [&_[data-slot=input-group]:not(:has(>[data-align^=block]))]:h-8";
69
+ /**
70
+ * The scroll body of every modal that scrolls its fields between a fixed head
71
+ * and foot: the create wizard, the edit lightbox, an action's fields. A scroll
72
+ * container clips everything at its padding edge, focus rings included, so a
73
+ * field at the top or the side lost a stripe of its ring (Michael 19.10.:
74
+ * „betrifft das alle Modals?“). The body bleeds into the dialog's own gutter
75
+ * (24 px, 16 px on the phone sheet) and 4 px above and below, then pads the
76
+ * same amount back: content stays where it was, the ring gets room, and the
77
+ * scrollbar sits at the dialog edge. Add a height (`flex-1` in a flex column
78
+ * or a `max-h-*`) at the call site.
79
+ */
80
+ export declare const DIALOG_SCROLL_CLASS = "overflow-y-auto overscroll-contain -mx-6 px-6 -my-1 py-1 max-md:-mx-4 max-md:px-4";
81
+ /** {@link DIALOG_SCROLL_CLASS} filling the rest of a flex-column dialog. */
82
+ export declare const DIALOG_BODY_CLASS = "min-h-0 flex-1 overflow-y-auto overscroll-contain -mx-6 px-6 -my-1 py-1 max-md:-mx-4 max-md:px-4";
83
+ /**
84
+ * How wide a field's CONTROL is inside its cell (Charge 2: full-width pages,
85
+ * single fields sized by their content). The cell keeps its grid track; only
86
+ * the input shrinks — a postal code is five characters, not half a 1,400 px
87
+ * row. Label and hint keep the cell width.
88
+ *
89
+ * `null` = as wide as the cell (names, free text, references, anything long).
90
+ */
91
+ export type FieldFit = 'xs' | 'sm' | 'md' | null;
92
+ export interface FitSource {
93
+ type?: string | null;
94
+ name?: string | null;
95
+ format?: string | null;
96
+ /** An explicit fit wins; `auto`/absent lets the field decide. */
97
+ fit?: FieldFit | 'auto' | 'full' | string | null;
98
+ }
99
+ /** The control width a field gets when nothing says otherwise. */
100
+ export declare function fieldFit(field: FitSource): FieldFit;
101
+ /**
102
+ * The class that caps the control (everything in the field except its label
103
+ * and its hint/error paragraphs) at the fit's width. Whole literals for the
104
+ * Tailwind scanner.
105
+ */
106
+ export declare function fitClass(fit: FieldFit): string;
107
+ /** Below this many sections a navigation is noise — the form fits in a glance. */
108
+ export declare const SECTION_NAV_MIN = 3;
109
+ /**
110
+ * Whether the left section navigation is drawn.
111
+ * `auto` = from {@link SECTION_NAV_MIN} sections on; a boolean forces it.
112
+ */
113
+ export declare function showSectionNav(mode: boolean | 'auto', sectionCount: number): boolean;
114
+ /** One section as the scroll spy sees it: its top edge, relative to the viewport of the scroller. */
115
+ export interface SectionOffset {
116
+ id: string;
117
+ top: number;
118
+ }
119
+ /**
120
+ * Which section the navigation marks.
121
+ *
122
+ * The last section whose top edge has scrolled up to `threshold` (the line just
123
+ * under the sticky header) is the one being read. Two edges need a rule of
124
+ * their own: before anything has scrolled the first section is current even if
125
+ * it starts lower than the line, and once the scroller cannot move further the
126
+ * LAST section is current — a short final section can never reach the line,
127
+ * and a navigation that can never mark it reads as broken.
128
+ *
129
+ * `sections` must be in document order. Returns `null` for no sections.
130
+ */
131
+ export declare function activeSectionId(sections: readonly SectionOffset[], options: {
132
+ threshold: number;
133
+ atEnd?: boolean;
134
+ }): string | null;
135
+ /** A scroller has reached its end, within a pixel or two of subpixel rounding. */
136
+ export declare function isScrolledToEnd(scrollTop: number, clientHeight: number, scrollHeight: number): boolean;
137
+ /**
138
+ * The tracks of the layout grid, by which side columns are present.
139
+ *
140
+ * The form column takes the full width (S19, full-width pages: no centred
141
+ * 48rem column — single fields size themselves by content); the navigation
142
+ * is 10rem, the context column 16rem. Each side column
143
+ * only exists from the container width it fits at — the nav from `@3xl`
144
+ * (48rem), the context column from `@5xl` (64rem) — and below that the layout
145
+ * is the bare form column. Container queries again, because the space a form
146
+ * gets is the content pane beside a 256 px studio sidebar, not the window.
147
+ */
148
+ export declare function layoutGridClass(nav: boolean, context: boolean): string;
149
+ /** Visibility of the side columns, matching the breakpoints in {@link layoutGridClass}. */
150
+ export declare const NAV_COLUMN_CLASS = "hidden @3xl:block";
151
+ export declare const CONTEXT_COLUMN_CLASS = "hidden @5xl:block";
152
+ /** One fact in a `FormContextCard`. Empty values render as an em dash. */
153
+ export interface FormContextRow {
154
+ /** Stable key; the `value` slot receives the whole row. */
155
+ key: string;
156
+ label: string;
157
+ value?: string | number | null;
158
+ }
159
+ /**
160
+ * Up to two initials for an avatar: first letters of the first and last word (split on spaces, `-`, `_`),
161
+ * or the first two letters of a single word. Letters only — `"ACME GmbH (Test)"`
162
+ * reads as `AT`, not `A(`. Empty for nothing usable.
163
+ */
164
+ export declare function initialsOf(name: string | null | undefined): string;
@@ -0,0 +1,117 @@
1
+ const FULL_WIDTH_TYPES = /* @__PURE__ */ new Set([
2
+ "textarea",
3
+ "json",
4
+ "key-value",
5
+ "multi-select",
6
+ "market-assignment",
7
+ "repeater",
8
+ "record-list",
9
+ "string-list",
10
+ "fieldset",
11
+ "rich-text",
12
+ "richtext",
13
+ "markdown",
14
+ "media",
15
+ "media-collection",
16
+ "image",
17
+ "localized-text",
18
+ "rules",
19
+ "record-view",
20
+ "address",
21
+ "tree-assign",
22
+ "line-selection",
23
+ // Read-side spellings of the same values on a record card.
24
+ "markets",
25
+ "gallery"
26
+ ]);
27
+ export function defaultSpan(fieldType) {
28
+ if (!fieldType) return "half";
29
+ return FULL_WIDTH_TYPES.has(fieldType.trim().toLowerCase()) ? "full" : "half";
30
+ }
31
+ export function resolveSpan(field) {
32
+ if (field.span === "half" || field.span === "full") return field.span;
33
+ return defaultSpan(field.type);
34
+ }
35
+ export function spanClass(span) {
36
+ return span === "full" ? "col-span-1 @lg:col-span-2" : "col-span-1";
37
+ }
38
+ export const PAIR_GRID_CLASS = "grid grid-cols-1 gap-x-8 gap-y-2.5 @min-[832px]:grid-cols-2 [clip-path:inset(-24px_-24px_1px_-24px)]";
39
+ export function pairSpanClass(span) {
40
+ return span === "full" ? `${PAIR_CELL_CLASS} @min-[832px]:col-span-2` : PAIR_CELL_CLASS;
41
+ }
42
+ export const PAIR_CELL_CLASS = "min-w-0 border-b border-black/[0.06] pb-2.5";
43
+ export const COMPACT_CONTROLS_CLASS = "[&_[data-slot=input]]:h-8 [&_[data-slot=select-trigger]]:h-8 [&_[data-slot=combobox-select-trigger]]:h-8 [&_[data-slot=combobox-anchor]]:min-h-8 [&_[data-slot=number-field-input]]:h-8 [&_[data-slot=input-group]:not(:has(>[data-align^=block]))]:h-8";
44
+ export const DIALOG_SCROLL_CLASS = "overflow-y-auto overscroll-contain -mx-6 px-6 -my-1 py-1 max-md:-mx-4 max-md:px-4";
45
+ export const DIALOG_BODY_CLASS = `min-h-0 flex-1 ${DIALOG_SCROLL_CLASS}`;
46
+ const XS_NAME = /(^|_)(zip|postal_code|postcode|plz|position|sort|sort_order|priority|rank|index|moq|min_order(_qty|_quantity)?|order_interval|qty|quantity|days|day_count|hours|percent|pct|rate|discount|decimals|step)($|_)/i;
47
+ const SM_NAME = /(^|_)(code|sku|gtin|ean|iso|country|country_code|currency|vat_rate|tax_rate|customs_tariff|tariff|hs_code|unit|uom|packaging_unit|weight|width|height|depth|length|color|threshold|limit|amount|price|total|budget)($|_)/i;
48
+ const MD_NAME = /(^|_)(phone|mobile|fax|tel|email|vat_id|customer_number|number|iban|bic|username|slug|key)($|_)/i;
49
+ export function fieldFit(field) {
50
+ if (field.fit === "xs" || field.fit === "sm" || field.fit === "md") return field.fit;
51
+ if (field.fit === "full") return null;
52
+ const type = String(field.type ?? "text").trim().toLowerCase();
53
+ const name = String(field.name ?? "");
54
+ if (defaultSpan(type) === "full") return null;
55
+ if (type === "boolean" || type === "reference" || type === "relation") return null;
56
+ if (type === "date" || type === "time") return "sm";
57
+ if (type === "datetime") return "md";
58
+ if (type === "money" || type === "measure" || field.format === "money") return "sm";
59
+ if (field.format === "percent") return "xs";
60
+ if (type === "number") return XS_NAME.test(name) ? "xs" : "sm";
61
+ if (type === "tel" || type === "email" || type === "url") return "md";
62
+ if (type === "locale") return null;
63
+ if (type === "select" && /(^|_)(country|currency|language)($|_)/i.test(name)) return "md";
64
+ if (type === "select") return SM_NAME.test(name) ? "sm" : "md";
65
+ if (XS_NAME.test(name)) return "xs";
66
+ if (SM_NAME.test(name)) return "sm";
67
+ if (MD_NAME.test(name)) return "md";
68
+ return null;
69
+ }
70
+ export function fitClass(fit) {
71
+ switch (fit) {
72
+ case "xs":
73
+ return "[&>*:not(label):not(p)]:max-w-32";
74
+ case "sm":
75
+ return "[&>*:not(label):not(p)]:max-w-52";
76
+ case "md":
77
+ return "[&>*:not(label):not(p)]:max-w-80";
78
+ default:
79
+ return "";
80
+ }
81
+ }
82
+ export const SECTION_NAV_MIN = 3;
83
+ export function showSectionNav(mode, sectionCount) {
84
+ if (mode === "auto") return sectionCount >= SECTION_NAV_MIN;
85
+ return mode && sectionCount > 0;
86
+ }
87
+ export function activeSectionId(sections, options) {
88
+ if (!sections.length) return null;
89
+ if (options.atEnd) return sections[sections.length - 1].id;
90
+ let active = sections[0].id;
91
+ for (const section of sections) {
92
+ if (section.top <= options.threshold) active = section.id;
93
+ else break;
94
+ }
95
+ return active;
96
+ }
97
+ export function isScrolledToEnd(scrollTop, clientHeight, scrollHeight) {
98
+ if (scrollHeight <= clientHeight + 2) return false;
99
+ return scrollTop + clientHeight >= scrollHeight - 2;
100
+ }
101
+ export function layoutGridClass(nav, context) {
102
+ if (nav && context)
103
+ return "grid grid-cols-[minmax(0,1fr)] gap-6 @3xl:grid-cols-[10rem_minmax(0,1fr)] @5xl:grid-cols-[10rem_minmax(0,1fr)_16rem]";
104
+ if (nav)
105
+ return "grid grid-cols-[minmax(0,1fr)] gap-6 @3xl:grid-cols-[10rem_minmax(0,1fr)]";
106
+ if (context)
107
+ return "grid grid-cols-[minmax(0,1fr)] gap-6 @5xl:grid-cols-[minmax(0,1fr)_16rem]";
108
+ return "grid grid-cols-[minmax(0,1fr)] gap-6";
109
+ }
110
+ export const NAV_COLUMN_CLASS = "hidden @3xl:block";
111
+ export const CONTEXT_COLUMN_CLASS = "hidden @5xl:block";
112
+ export function initialsOf(name) {
113
+ const words = (name ?? "").split(/[\s_-]+/).map((w) => w.replace(/[^\p{L}\p{N}]/gu, "")).filter(Boolean);
114
+ if (!words.length) return "";
115
+ if (words.length === 1) return words[0].slice(0, 2).toUpperCase();
116
+ return (words[0][0] + words[words.length - 1][0]).toUpperCase();
117
+ }