@wildwinter/app-shell 0.55.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tokens.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /* src/tokens.css */
2
2
  :root {
3
- color-scheme: light dark;
3
+ color-scheme: var(--color-scheme, light dark);
4
4
  --ok: light-dark(#3f7d49, #79bd80);
5
5
  --on-accent: light-dark(#ffffff, #1b1916);
6
6
  --chrome-size: 14px;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tokens.css"],"sourcesContent":["/* ============================================================================\n Design-token GRAMMAR - the app-agnostic layer (design-language.md section 4).\n\n The shared half of \"two palettes, one grammar\": the scales that are IDENTICAL\n across the editor family - elevation, motion, radius, scrollbar chrome. Each\n app supplies its own colour + font VALUES (its palette) over this grammar in\n its own theme.css. Do not add app-specific colours here.\n\n SCALE. Chrome is sized in px off a 14px base; the reading surface alone\n scales with the root (\"Chrome has its own scale\", design-language.md\n section 4, 2026-09-16). Every size in the shell's stylesheets (font-size,\n padding, gap, margin, width, height) is a pixel value: the navigator, the\n inspector, the bars, the dialogs and the controls in them read the same in\n every app of the family whatever the reading size is. `em` survives only\n where a rule deliberately scales with its own font; line-heights stay\n unitless. The values are what Storyletter rendered at 0.39.0 (rem x 16, to\n the nearest half pixel), so the conversion moved nothing there and brought\n Patterpad, whose chrome had grown 7 to 10 percent with its reading root,\n back to the same size. No `rem` in a chrome rule; the test suite greps.\n ============================================================================ */\n\n:root {\n color-scheme: light dark;\n\n /* The semantic set's third member, and the only COLOUR in this file.\n\n It is here because it kept not being anywhere. An app's palette owns\n --danger and --warn; neither owned \"all is well\", so a real need - the live\n debug link, an in-sync shard, a saved document - was met with a raw green\n literal that could not theme-shift (surface-audit-shell.md F2). A default\n in the grammar layer means the shell's own widgets may say it, and an app\n that has not thought about green yet still gets a green that works on both\n grounds. Every palette should override it in its theme.css, as it does the\n other two: this is a floor, not a decision. */\n --ok: light-dark(#3f7d49, #79bd80);\n\n /* The ink that sits ON the accent: a primary button's label, a selected\n row's text. Every palette overrides it (a light tan accent on a dark\n palette wants dark ink, not white); this floor only stops the shell's own\n primary buttons from falling back to a raw #fff literal, which is what\n they did until 0.39.0 and which read white-on-tan on Night and Slate. */\n --on-accent: light-dark(#ffffff, #1b1916);\n\n /* The chrome base. `.shell-dialog`, `.pane-shell` and `.swin-head` set it as\n their font-size, so unstyled text inside them lands at 14px regardless of\n the app's root; a rule that wants the base by name reads it here. Not a\n scale factor: the sizes below it are literal px, on purpose. */\n --chrome-size: 14px;\n\n /* The one scrim behind every modal in the family (confirm, settings,\n identity, about, notes). Four recipes had grown across five files. */\n --scrim: color-mix(in oklab, var(--ink) 28%, transparent);\n\n /* Elevation: three tiers by how far a surface floats (resting cards,\n floating menus/popovers, full overlays). Theme-agnostic black so it\n deepens naturally on the dark palettes. */\n --shadow-1: 0 1px 3px rgba(0, 0, 0, 0.05);\n --shadow-2: 0 6px 20px rgba(0, 0, 0, 0.14);\n --shadow-3: 0 16px 48px rgba(0, 0, 0, 0.28);\n\n /* Motion: one easing curve + a small duration scale, plus the shared\n panel/backdrop/screen enter-exit vocabulary. Nothing pops. */\n --ease-standard: cubic-bezier(0.2, 0, 0, 1);\n --dur-quick: 120ms;\n --dur-base: 150ms;\n --dur-settle: 200ms;\n --dur-slow: 1s;\n --dur-panel: 260ms;\n --dur-exit: 170ms;\n --anim-panel-in: panel-in var(--dur-panel) var(--ease-standard);\n --anim-backdrop-in: backdrop-in var(--dur-settle) var(--ease-standard);\n --anim-screen-in: screen-in var(--dur-panel) var(--ease-standard);\n --anim-panel-out: panel-out var(--dur-exit) var(--ease-standard) forwards;\n --anim-backdrop-out: backdrop-out var(--dur-exit) var(--ease-standard) forwards;\n /* The long-job strip's indeterminate band (job.css draws the keyframes). */\n --anim-job-band: job-band 1.4s var(--ease-standard) infinite;\n\n /* Corner radius: four steps cover every surface. */\n --radius-sm: 6px;\n --radius-md: 10px;\n --radius-lg: 14px;\n --radius-pill: 999px;\n\n /* Scrollbar thumb: a translucent ink, so it adapts per palette. */\n --scroll-thumb: color-mix(in oklab, var(--ink) 20%, transparent);\n --scroll-thumb-hover: color-mix(in oklab, var(--ink) 40%, transparent);\n}\n\n/* A drawn icon (`iconNode` / `iconHtml` in icons.ts stamp `data-icon`). A\n block, so it never sits on a text baseline with descender space under it;\n no flex shrink, so a crowded row squeezes the text and not the picture; and\n no pointer events, so a click lands on the button and never on the drawing\n (a handler comparing `event.target` to its button would otherwise miss).\n Colour is inherited: every drawing is currentColor. */\n[data-icon] { display: block; flex: none; pointer-events: none; }\n\n/* The family's keyboard focus ring: one rule, zero specificity (:where), so a\n component may draw its own and win without !important. Until 0.39.0\n Patterpad's main window had no ring at all and Storyletter carried five\n copies of this line. Mouse focus stays quiet; only :focus-visible draws. */\n:where(button, [role=\"button\"], input, select, textarea, summary, a[href], [tabindex]):focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 1px;\n}\n\n/* The tracked uppercase caption, kept for exactly two jobs: table column heads\n and screenplay character cues, where uppercase is the domain's own\n convention (design-language.md, \"Captions are words, not overlines\"). A\n section caption anywhere else is sentence-case text at label size, weight\n 600, in the UI face, which is what the shell's own captions now are. */\n.overline { font: 600 10.5px/1 var(--font-ui); text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); }\n\n* { scrollbar-width: thin; scrollbar-color: var(--scroll-thumb) transparent; }\n::-webkit-scrollbar { width: 11px; height: 11px; }\n::-webkit-scrollbar-track { background: transparent; }\n::-webkit-scrollbar-corner { background: transparent; }\n::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: var(--radius-sm); border: 3px solid transparent; background-clip: padding-box; }\n::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); background-clip: padding-box; }\n\n@keyframes panel-in { from { opacity: 0; transform: translateY(-9px) scale(0.965); } to { opacity: 1; transform: none; } }\n@keyframes backdrop-in { from { opacity: 0; } to { opacity: 1; } }\n@keyframes screen-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }\n@keyframes panel-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(-6px) scale(0.97); } }\n@keyframes backdrop-out { from { opacity: 1; } to { opacity: 0; } }\n\n@media (prefers-reduced-motion: reduce) {\n :root {\n --anim-panel-in: none; --anim-backdrop-in: none; --anim-screen-in: none;\n --anim-panel-out: none; --anim-backdrop-out: none; --anim-job-band: none;\n }\n}\n\n/* A floating layer from createFloating (floating.ts) carries this attribute, because its class is\n the caller's. Never a window-drag region, even over one: Electron gives a click in a drag area to\n the OS before the page sees it (dialog.css has the whole story, 2026-09-28). */\n[data-shell-layer], [data-shell-layer] * { -webkit-app-region: no-drag; }\n"],"mappings":";AAqBA;AACE,gBAAc,MAAM;AAYpB,QAAM,WAAW,OAAO,EAAE;AAO1B,eAAa,WAAW,OAAO,EAAE;AAMjC,iBAAe;AAIf,WAAS,UAAU,GAAG,KAAK,EAAE,IAAI,OAAO,GAAG,EAAE;AAK7C,cAAY,EAAE,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACpC,cAAY,EAAE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACrC,cAAY,EAAE,KAAK,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAItC,mBAAiB,aAAa,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE;AACzC,eAAa;AACb,cAAY;AACZ,gBAAc;AACd,cAAY;AACZ,eAAa;AACb,cAAY;AACZ,mBAAiB,SAAS,IAAI,aAAa,IAAI;AAC/C,sBAAoB,YAAY,IAAI,cAAc,IAAI;AACtD,oBAAkB,UAAU,IAAI,aAAa,IAAI;AACjD,oBAAkB,UAAU,IAAI,YAAY,IAAI,iBAAiB;AACjE,uBAAqB,aAAa,IAAI,YAAY,IAAI,iBAAiB;AAEvE,mBAAiB,SAAS,KAAK,IAAI,iBAAiB;AAGpD,eAAa;AACb,eAAa;AACb,eAAa;AACb,iBAAe;AAGf,kBAAgB,UAAU,GAAG,KAAK,EAAE,IAAI,OAAO,GAAG,EAAE;AACpD,wBAAsB,UAAU,GAAG,KAAK,EAAE,IAAI,OAAO,GAAG,EAAE;AAC5D;AAQA,CAAC;AAAa,WAAS;AAAO,QAAM;AAAM,kBAAgB;AAAM;AAMhE,OAAO,QAAQ,CAAC,cAAgB,OAAO,QAAQ,UAAU,SAAS,CAAC,CAAC,OAAO,CAAC,UAAU;AACpF,WAAS,IAAI,MAAM,IAAI;AACvB,kBAAgB;AAClB;AAOA,CAAC;AAAW,QAAM,IAAI,MAAM,CAAC,EAAE,IAAI;AAAY,kBAAgB;AAAW,kBAAgB;AAAQ,SAAO,IAAI;AAAU;AAEvH;AAAI,mBAAiB;AAAM,mBAAiB,IAAI,gBAAgB;AAAa;AAC7E;AAAsB,SAAO;AAAM,UAAQ;AAAM;AACjD;AAA4B,cAAY;AAAa;AACrD;AAA6B,cAAY;AAAa;AACtD;AAA4B,cAAY,IAAI;AAAiB,iBAAe,IAAI;AAAc,UAAQ,IAAI,MAAM;AAAa,mBAAiB;AAAa;AAC3J,yBAAyB;AAAS,cAAY,IAAI;AAAuB,mBAAiB;AAAa;AAEvG,WAAW;AAAW;AAAO,aAAS;AAAG,eAAW,WAAW,MAAM,MAAM;AAAQ;AAAE;AAAK,aAAS;AAAG,eAAW;AAAM;AAAE;AACzH,WAAW;AAAc;AAAO,aAAS;AAAG;AAAE;AAAK,aAAS;AAAG;AAAE;AACjE,WAAW;AAAY;AAAO,aAAS;AAAG,eAAW,WAAW;AAAM;AAAE;AAAK,aAAS;AAAG,eAAW;AAAM;AAAE;AAC5G,WAAW;AAAY;AAAO,aAAS;AAAG,eAAW;AAAM;AAAE;AAAK,aAAS;AAAG,eAAW,WAAW,MAAM,MAAM;AAAO;AAAE;AACzH,WAAW;AAAe;AAAO,aAAS;AAAG;AAAE;AAAK,aAAS;AAAG;AAAE;AAElE,QAAO,wBAAyB;AAC9B;AACE,qBAAiB;AAAM,wBAAoB;AAAM,sBAAkB;AACnE,sBAAkB;AAAM,yBAAqB;AAAM,qBAAiB;AACtE;AACF;AAKA,CAAC;AAAmB,CAAC,kBAAkB;AAAI,sBAAoB;AAAS;","names":[]}
1
+ {"version":3,"sources":["../src/tokens.css"],"sourcesContent":["/* ============================================================================\n Design-token GRAMMAR - the app-agnostic layer (design-language.md section 4).\n\n The shared half of \"two palettes, one grammar\": the scales that are IDENTICAL\n across the editor family - elevation, motion, radius, scrollbar chrome. Each\n app supplies its own colour + font VALUES (its palette) over this grammar in\n its own theme.css. Do not add app-specific colours here.\n\n SCALE. Chrome is sized in px off a 14px base; the reading surface alone\n scales with the root (\"Chrome has its own scale\", design-language.md\n section 4, 2026-09-16). Every size in the shell's stylesheets (font-size,\n padding, gap, margin, width, height) is a pixel value: the navigator, the\n inspector, the bars, the dialogs and the controls in them read the same in\n every app of the family whatever the reading size is. `em` survives only\n where a rule deliberately scales with its own font; line-heights stay\n unitless. The values are what Storyletter rendered at 0.39.0 (rem x 16, to\n the nearest half pixel), so the conversion moved nothing there and brought\n Patterpad, whose chrome had grown 7 to 10 percent with its reading root,\n back to the same size. No `rem` in a chrome rule; the test suite greps.\n ============================================================================ */\n\n:root {\n /* Native controls (an unticked checkbox, a radio, a scrollbar, a select's\n pop-up, a date field) and every light-dark() below follow this, so it must\n follow the palette the author chose, not the OS: a light palette under a\n dark OS drew every unticked checkbox as a dark filled square. Every palette\n block in an app's theme.css declares `--color-scheme: light` or `dark`;\n with no palette chosen the fallback follows the OS, as before. */\n color-scheme: var(--color-scheme, light dark);\n\n /* The semantic set's third member, and the only COLOUR in this file.\n\n It is here because it kept not being anywhere. An app's palette owns\n --danger and --warn; neither owned \"all is well\", so a real need - the live\n debug link, an in-sync shard, a saved document - was met with a raw green\n literal that could not theme-shift (surface-audit-shell.md F2). A default\n in the grammar layer means the shell's own widgets may say it, and an app\n that has not thought about green yet still gets a green that works on both\n grounds. Every palette should override it in its theme.css, as it does the\n other two: this is a floor, not a decision. */\n --ok: light-dark(#3f7d49, #79bd80);\n\n /* The ink that sits ON the accent: a primary button's label, a selected\n row's text. Every palette overrides it (a light tan accent on a dark\n palette wants dark ink, not white); this floor only stops the shell's own\n primary buttons from falling back to a raw #fff literal, which is what\n they did until 0.39.0 and which read white-on-tan on Night and Slate. */\n --on-accent: light-dark(#ffffff, #1b1916);\n\n /* The chrome base. `.shell-dialog`, `.pane-shell` and `.swin-head` set it as\n their font-size, so unstyled text inside them lands at 14px regardless of\n the app's root; a rule that wants the base by name reads it here. Not a\n scale factor: the sizes below it are literal px, on purpose. */\n --chrome-size: 14px;\n\n /* The one scrim behind every modal in the family (confirm, settings,\n identity, about, notes). Four recipes had grown across five files. */\n --scrim: color-mix(in oklab, var(--ink) 28%, transparent);\n\n /* Elevation: three tiers by how far a surface floats (resting cards,\n floating menus/popovers, full overlays). Theme-agnostic black so it\n deepens naturally on the dark palettes. */\n --shadow-1: 0 1px 3px rgba(0, 0, 0, 0.05);\n --shadow-2: 0 6px 20px rgba(0, 0, 0, 0.14);\n --shadow-3: 0 16px 48px rgba(0, 0, 0, 0.28);\n\n /* Motion: one easing curve + a small duration scale, plus the shared\n panel/backdrop/screen enter-exit vocabulary. Nothing pops. */\n --ease-standard: cubic-bezier(0.2, 0, 0, 1);\n --dur-quick: 120ms;\n --dur-base: 150ms;\n --dur-settle: 200ms;\n --dur-slow: 1s;\n --dur-panel: 260ms;\n --dur-exit: 170ms;\n --anim-panel-in: panel-in var(--dur-panel) var(--ease-standard);\n --anim-backdrop-in: backdrop-in var(--dur-settle) var(--ease-standard);\n --anim-screen-in: screen-in var(--dur-panel) var(--ease-standard);\n --anim-panel-out: panel-out var(--dur-exit) var(--ease-standard) forwards;\n --anim-backdrop-out: backdrop-out var(--dur-exit) var(--ease-standard) forwards;\n /* The long-job strip's indeterminate band (job.css draws the keyframes). */\n --anim-job-band: job-band 1.4s var(--ease-standard) infinite;\n\n /* Corner radius: four steps cover every surface. */\n --radius-sm: 6px;\n --radius-md: 10px;\n --radius-lg: 14px;\n --radius-pill: 999px;\n\n /* Scrollbar thumb: a translucent ink, so it adapts per palette. */\n --scroll-thumb: color-mix(in oklab, var(--ink) 20%, transparent);\n --scroll-thumb-hover: color-mix(in oklab, var(--ink) 40%, transparent);\n}\n\n/* A drawn icon (`iconNode` / `iconHtml` in icons.ts stamp `data-icon`). A\n block, so it never sits on a text baseline with descender space under it;\n no flex shrink, so a crowded row squeezes the text and not the picture; and\n no pointer events, so a click lands on the button and never on the drawing\n (a handler comparing `event.target` to its button would otherwise miss).\n Colour is inherited: every drawing is currentColor. */\n[data-icon] { display: block; flex: none; pointer-events: none; }\n\n/* The family's keyboard focus ring: one rule, zero specificity (:where), so a\n component may draw its own and win without !important. Until 0.39.0\n Patterpad's main window had no ring at all and Storyletter carried five\n copies of this line. Mouse focus stays quiet; only :focus-visible draws. */\n:where(button, [role=\"button\"], input, select, textarea, summary, a[href], [tabindex]):focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 1px;\n}\n\n/* The tracked uppercase caption, kept for exactly two jobs: table column heads\n and screenplay character cues, where uppercase is the domain's own\n convention (design-language.md, \"Captions are words, not overlines\"). A\n section caption anywhere else is sentence-case text at label size, weight\n 600, in the UI face, which is what the shell's own captions now are. */\n.overline { font: 600 10.5px/1 var(--font-ui); text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); }\n\n* { scrollbar-width: thin; scrollbar-color: var(--scroll-thumb) transparent; }\n::-webkit-scrollbar { width: 11px; height: 11px; }\n::-webkit-scrollbar-track { background: transparent; }\n::-webkit-scrollbar-corner { background: transparent; }\n::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: var(--radius-sm); border: 3px solid transparent; background-clip: padding-box; }\n::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); background-clip: padding-box; }\n\n@keyframes panel-in { from { opacity: 0; transform: translateY(-9px) scale(0.965); } to { opacity: 1; transform: none; } }\n@keyframes backdrop-in { from { opacity: 0; } to { opacity: 1; } }\n@keyframes screen-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }\n@keyframes panel-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(-6px) scale(0.97); } }\n@keyframes backdrop-out { from { opacity: 1; } to { opacity: 0; } }\n\n@media (prefers-reduced-motion: reduce) {\n :root {\n --anim-panel-in: none; --anim-backdrop-in: none; --anim-screen-in: none;\n --anim-panel-out: none; --anim-backdrop-out: none; --anim-job-band: none;\n }\n}\n\n/* A floating layer from createFloating (floating.ts) carries this attribute, because its class is\n the caller's. Never a window-drag region, even over one: Electron gives a click in a drag area to\n the OS before the page sees it (dialog.css has the whole story, 2026-09-28). */\n[data-shell-layer], [data-shell-layer] * { -webkit-app-region: no-drag; }\n"],"mappings":";AAqBA;AAOE,gBAAc,IAAI,cAAc,EAAE,MAAM;AAYxC,QAAM,WAAW,OAAO,EAAE;AAO1B,eAAa,WAAW,OAAO,EAAE;AAMjC,iBAAe;AAIf,WAAS,UAAU,GAAG,KAAK,EAAE,IAAI,OAAO,GAAG,EAAE;AAK7C,cAAY,EAAE,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACpC,cAAY,EAAE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACrC,cAAY,EAAE,KAAK,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAItC,mBAAiB,aAAa,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE;AACzC,eAAa;AACb,cAAY;AACZ,gBAAc;AACd,cAAY;AACZ,eAAa;AACb,cAAY;AACZ,mBAAiB,SAAS,IAAI,aAAa,IAAI;AAC/C,sBAAoB,YAAY,IAAI,cAAc,IAAI;AACtD,oBAAkB,UAAU,IAAI,aAAa,IAAI;AACjD,oBAAkB,UAAU,IAAI,YAAY,IAAI,iBAAiB;AACjE,uBAAqB,aAAa,IAAI,YAAY,IAAI,iBAAiB;AAEvE,mBAAiB,SAAS,KAAK,IAAI,iBAAiB;AAGpD,eAAa;AACb,eAAa;AACb,eAAa;AACb,iBAAe;AAGf,kBAAgB,UAAU,GAAG,KAAK,EAAE,IAAI,OAAO,GAAG,EAAE;AACpD,wBAAsB,UAAU,GAAG,KAAK,EAAE,IAAI,OAAO,GAAG,EAAE;AAC5D;AAQA,CAAC;AAAa,WAAS;AAAO,QAAM;AAAM,kBAAgB;AAAM;AAMhE,OAAO,QAAQ,CAAC,cAAgB,OAAO,QAAQ,UAAU,SAAS,CAAC,CAAC,OAAO,CAAC,UAAU;AACpF,WAAS,IAAI,MAAM,IAAI;AACvB,kBAAgB;AAClB;AAOA,CAAC;AAAW,QAAM,IAAI,MAAM,CAAC,EAAE,IAAI;AAAY,kBAAgB;AAAW,kBAAgB;AAAQ,SAAO,IAAI;AAAU;AAEvH;AAAI,mBAAiB;AAAM,mBAAiB,IAAI,gBAAgB;AAAa;AAC7E;AAAsB,SAAO;AAAM,UAAQ;AAAM;AACjD;AAA4B,cAAY;AAAa;AACrD;AAA6B,cAAY;AAAa;AACtD;AAA4B,cAAY,IAAI;AAAiB,iBAAe,IAAI;AAAc,UAAQ,IAAI,MAAM;AAAa,mBAAiB;AAAa;AAC3J,yBAAyB;AAAS,cAAY,IAAI;AAAuB,mBAAiB;AAAa;AAEvG,WAAW;AAAW;AAAO,aAAS;AAAG,eAAW,WAAW,MAAM,MAAM;AAAQ;AAAE;AAAK,aAAS;AAAG,eAAW;AAAM;AAAE;AACzH,WAAW;AAAc;AAAO,aAAS;AAAG;AAAE;AAAK,aAAS;AAAG;AAAE;AACjE,WAAW;AAAY;AAAO,aAAS;AAAG,eAAW,WAAW;AAAM;AAAE;AAAK,aAAS;AAAG,eAAW;AAAM;AAAE;AAC5G,WAAW;AAAY;AAAO,aAAS;AAAG,eAAW;AAAM;AAAE;AAAK,aAAS;AAAG,eAAW,WAAW,MAAM,MAAM;AAAO;AAAE;AACzH,WAAW;AAAe;AAAO,aAAS;AAAG;AAAE;AAAK,aAAS;AAAG;AAAE;AAElE,QAAO,wBAAyB;AAC9B;AACE,qBAAiB;AAAM,wBAAoB;AAAM,sBAAkB;AACnE,sBAAkB;AAAM,yBAAqB;AAAM,qBAAiB;AACtE;AACF;AAKA,CAAC;AAAmB,CAAC,kBAAkB;AAAI,sBAAoB;AAAS;","names":[]}
@@ -826,13 +826,274 @@ var fieldRowFocus = {
826
826
  settle: (row) => keeper.settle(row),
827
827
  restore: (row) => keeper.restore(row)
828
828
  };
829
+ var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
830
+ function isSet(decl, value) {
831
+ if (value === void 0) return false;
832
+ if (decl.type === "record" && decl.default === void 0 && isObject(value)) {
833
+ const fields = decl.fields ?? [];
834
+ const declared = new Set(fields.map((f) => f.name));
835
+ if (Object.entries(value).some(([k, v]) => !declared.has(k) && v !== void 0)) return true;
836
+ return fields.some((f) => isSet(f, value[f.name]));
837
+ }
838
+ return extensions.stableStringify(value) !== extensions.stableStringify(extensions.defaultOf(decl));
839
+ }
840
+ function foldCaption(name, set) {
841
+ return set > 0 ? `${name} (${set} set)` : name;
842
+ }
843
+ function extensionFold() {
844
+ const open = /* @__PURE__ */ new Set();
845
+ const shown = /* @__PURE__ */ new Map();
846
+ const keyOf = (o) => `${o.extension}\0${o.node}`;
847
+ const visible = (o, setNames) => {
848
+ const was = shown.get(keyOf(o));
849
+ const fields = was ? /* @__PURE__ */ new Set([...was.fields, ...setNames]) : new Set(setNames);
850
+ shown.set(keyOf(o), { fields, used: true });
851
+ return fields;
852
+ };
853
+ const fold = {
854
+ isOpen: (extension) => open.has(extension),
855
+ setOpen(extension, on) {
856
+ if (on) open.add(extension);
857
+ else open.delete(extension);
858
+ for (const key of shown.keys()) if (key.startsWith(`${extension}\0`)) shown.delete(key);
859
+ },
860
+ sweep() {
861
+ for (const [key, entry] of shown) {
862
+ if (!entry.used) shown.delete(key);
863
+ else entry.used = false;
864
+ }
865
+ },
866
+ section(o) {
867
+ const sec = el("div", `shell-fold${o.className ? ` ${o.className}` : ""}`);
868
+ sec.dataset["extension"] = o.extension;
869
+ const cap = el("button", `shell-fold-cap${o.captionClassName ? ` ${o.captionClassName}` : ""}`);
870
+ cap.type = "button";
871
+ const body = el("div", "shell-fold-body");
872
+ sec.append(cap, body);
873
+ const draw = () => {
874
+ const isOpen = open.has(o.extension);
875
+ const setNames = new Set(o.fields.filter((f) => isSet(f, o.entry?.[f.name])).map((f) => f.name));
876
+ const show2 = isOpen ? void 0 : visible(o, setNames);
877
+ cap.replaceChildren(iconNode(isOpen ? "expanded" : "collapsed"), el("span", "shell-fold-name", foldCaption(o.caption, setNames.size)));
878
+ cap.setAttribute("aria-expanded", String(isOpen));
879
+ cap.dataset["tip"] = isOpen ? `Show only the fields set here` : `Show every ${o.caption} field`;
880
+ sec.classList.toggle("shell-fold-open", isOpen);
881
+ const rows = [...o.lead ?? []];
882
+ let group;
883
+ for (const decl of o.fields) {
884
+ if (show2 && !show2.has(decl.name)) continue;
885
+ const g = decl.group;
886
+ if (g !== void 0 && g !== group) {
887
+ group = g;
888
+ rows.push(o.group(g));
889
+ }
890
+ rows.push(o.row(decl));
891
+ }
892
+ rows.push(...o.tail ?? []);
893
+ body.replaceChildren(...rows);
894
+ };
895
+ cap.addEventListener("click", () => {
896
+ fold.setOpen(o.extension, !open.has(o.extension));
897
+ draw();
898
+ });
899
+ draw();
900
+ return sec;
901
+ }
902
+ };
903
+ return fold;
904
+ }
905
+ var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
906
+ function isRecordMap(decl) {
907
+ return decl.type === "map" && decl.of?.type === "record";
908
+ }
909
+ function recordMapKeyLabel(decl, key, adapter) {
910
+ const kind = decl.key?.type === "reference" ? decl.key.to ?? "" : void 0;
911
+ if (kind === void 0 || !adapter) return key;
912
+ return adapter.nodes(kind).find((n) => n.id === key)?.label ?? adapter.label?.(kind, key) ?? key;
913
+ }
914
+ function valueText(decl, value, adapter) {
915
+ if (!isSet(decl, value)) return "";
916
+ if (decl.type === "pick" && typeof value === "string") {
917
+ const choice = extensions.pickChoices(decl, {
918
+ ...adapter ? { nodes: (kind) => adapter.nodes(kind) } : {},
919
+ ...adapter?.list ? { list: (name) => adapter.list(name) } : {}
920
+ }).find((c) => c.value === value);
921
+ return choice?.label ?? value;
922
+ }
923
+ if (decl.type === "reference" && typeof value === "string" && decl.to !== void 0 && adapter) {
924
+ return adapter.nodes(decl.to).find((n) => n.id === value)?.label ?? adapter.label?.(decl.to, value) ?? value;
925
+ }
926
+ if (decl.type === "boolean") return value === true ? decl.label ?? decl.name : "";
927
+ if (Array.isArray(value)) return value.map(String).join(", ");
928
+ if (isObject2(value)) return JSON.stringify(value);
929
+ return String(value);
930
+ }
931
+ function recordMapSummary(decl, value, adapter) {
932
+ if (!isObject2(value)) return [];
933
+ const fields = decl.of?.fields ?? [];
934
+ return Object.entries(value).map(([key, record]) => {
935
+ const name = recordMapKeyLabel(decl, key, adapter);
936
+ const words = isObject2(record) ? fields.map((f) => valueText(f, record[f.name], adapter)).filter((w) => w !== "") : [];
937
+ return words.length > 0 ? `${name}: ${words.join(", ")}` : name;
938
+ });
939
+ }
940
+ function recordMapGrid(initial) {
941
+ let opts = initial;
942
+ const root = el("div", "shell-recordgrid");
943
+ const table = el("table", "shell-recordgrid-table");
944
+ const head = el("thead");
945
+ const body = el("tbody");
946
+ table.append(head, body);
947
+ const adder = el("div", "shell-recordgrid-add");
948
+ const empty = el("p", "shell-recordgrid-empty");
949
+ root.append(table, empty, adder);
950
+ const cells = /* @__PURE__ */ new Map();
951
+ let addKey = "";
952
+ const editable = () => opts.onCommit !== void 0 && opts.disabled !== true;
953
+ const held = () => isObject2(opts.value) ? opts.value : {};
954
+ const commit = (next) => {
955
+ const v = Object.keys(next).length > 0 ? next : void 0;
956
+ opts = { ...opts, value: v };
957
+ draw();
958
+ opts.onCommit?.(v);
959
+ };
960
+ const cell = (key, field, record) => {
961
+ const id = `${key}\0${field.name}`;
962
+ const o = {
963
+ decl: field,
964
+ value: record[field.name],
965
+ ...opts.adapter ? { adapter: opts.adapter } : {},
966
+ ...opts.disabled !== void 0 ? { disabled: opts.disabled } : {},
967
+ ...opts.controlClassName !== void 0 ? { controlClassName: opts.controlClassName } : {},
968
+ ...editable() ? {
969
+ onCommit: (v) => {
970
+ const map = held();
971
+ const rec = { ...isObject2(map[key]) ? map[key] : {} };
972
+ if (v === void 0) delete rec[field.name];
973
+ else rec[field.name] = v;
974
+ commit({ ...map, [key]: rec });
975
+ }
976
+ } : {},
977
+ className: "shell-recordgrid-cell"
978
+ };
979
+ const had = cells.get(id);
980
+ if (had && had.el.dataset["type"] === field.type) {
981
+ fieldRowFocus.settle(had.el);
982
+ had.update(o);
983
+ fieldRowFocus.restore(had.el);
984
+ return had.el;
985
+ }
986
+ const handle = valueRow(o);
987
+ handle.el.dataset["type"] = field.type;
988
+ cells.set(id, handle);
989
+ return handle.el;
990
+ };
991
+ const draw = () => {
992
+ const decl = opts.decl;
993
+ const fields = decl.of?.fields ?? [];
994
+ const map = held();
995
+ const keys = Object.keys(map);
996
+ const adapter = opts.adapter;
997
+ const keyKind = decl.key?.type === "reference" ? decl.key.to ?? "" : void 0;
998
+ const keyNodes = keyKind !== void 0 && adapter ? adapter.nodes(keyKind) : void 0;
999
+ const hr = el("tr");
1000
+ hr.append(el("th", "shell-recordgrid-keyhead", recordMapKeyHeading(decl)));
1001
+ for (const f of fields) {
1002
+ const th = el("th", void 0, f.label ?? f.name);
1003
+ const help = f.help ?? f.purpose;
1004
+ if (help) th.dataset["tip"] = help;
1005
+ hr.append(th);
1006
+ }
1007
+ hr.append(el("th", "shell-recordgrid-tools"));
1008
+ head.replaceChildren(hr);
1009
+ const used = /* @__PURE__ */ new Set();
1010
+ const rows = [];
1011
+ for (const key of keys) {
1012
+ const record = isObject2(map[key]) ? map[key] : {};
1013
+ const tr = el("tr");
1014
+ tr.dataset["key"] = key;
1015
+ const name = recordMapKeyLabel(decl, key, adapter);
1016
+ const keyCell = el("th", "shell-recordgrid-key", name);
1017
+ keyCell.setAttribute("scope", "row");
1018
+ if (keyNodes !== void 0 && adapter && keyNodes.every((n) => n.id !== key) && adapter.label?.(keyKind, key) === void 0) {
1019
+ keyCell.append(el("span", "shell-recordgrid-gone", " (not here any more)"));
1020
+ }
1021
+ tr.append(keyCell);
1022
+ for (const f of fields) {
1023
+ used.add(`${key}\0${f.name}`);
1024
+ const td = el("td");
1025
+ td.append(cell(key, f, record));
1026
+ tr.append(td);
1027
+ }
1028
+ const tools = el("td", "shell-recordgrid-tools");
1029
+ if (editable()) tools.append(iconBtn("close", `Remove ${name}`, () => {
1030
+ const next = { ...held() };
1031
+ delete next[key];
1032
+ commit(next);
1033
+ }));
1034
+ tr.append(tools);
1035
+ rows.push(tr);
1036
+ }
1037
+ for (const id of [...cells.keys()]) if (!used.has(id)) cells.delete(id);
1038
+ body.replaceChildren(...rows);
1039
+ table.hidden = keys.length === 0;
1040
+ empty.textContent = keys.length === 0 ? "None yet." : "";
1041
+ empty.hidden = keys.length !== 0;
1042
+ adder.replaceChildren();
1043
+ if (!editable()) return;
1044
+ const add = (k) => {
1045
+ if (k === "" || Object.prototype.hasOwnProperty.call(held(), k)) return;
1046
+ commit({ ...held(), [k]: extensions.defaultOf(decl.of) });
1047
+ };
1048
+ if (keyNodes !== void 0) {
1049
+ const pick = el("select", `field shell-valuerow-key${opts.controlClassName ? ` ${opts.controlClassName}` : ""}`);
1050
+ const first = el("option", void 0, "Add...");
1051
+ first.value = "";
1052
+ pick.append(first);
1053
+ for (const n of keyNodes.filter((n2) => !Object.prototype.hasOwnProperty.call(map, n2.id))) {
1054
+ const opt = el("option", void 0, n.label);
1055
+ opt.value = n.id;
1056
+ pick.append(opt);
1057
+ }
1058
+ pick.disabled = pick.options.length === 1;
1059
+ pick.addEventListener("change", () => add(pick.value));
1060
+ adder.append(pick);
1061
+ } else {
1062
+ const input = el("input", "field shell-valuerow-key");
1063
+ input.type = "text";
1064
+ input.placeholder = "New key";
1065
+ input.value = addKey;
1066
+ input.addEventListener("input", () => {
1067
+ addKey = input.value;
1068
+ });
1069
+ const go = () => {
1070
+ const k = input.value.trim();
1071
+ addKey = "";
1072
+ add(k);
1073
+ };
1074
+ input.addEventListener("keydown", (e) => {
1075
+ if (e.key === "Enter") go();
1076
+ });
1077
+ adder.append(input, iconBtn("add", "Add", go));
1078
+ }
1079
+ };
1080
+ draw();
1081
+ return { el: root, update: (o) => {
1082
+ opts = o;
1083
+ draw();
1084
+ } };
1085
+ }
1086
+ function recordMapKeyHeading(decl) {
1087
+ const to = decl.key?.type === "reference" ? decl.key.to : void 0;
1088
+ return to ? to.charAt(0).toUpperCase() + to.slice(1) : "Key";
1089
+ }
829
1090
 
830
1091
  // src/value-rows.ts
831
1092
  function nodeLabel(adapter, kind, id) {
832
1093
  return adapter.nodes(kind).find((n) => n.id === id)?.label ?? adapter.label?.(kind, id);
833
1094
  }
834
1095
  var SCALARS = /* @__PURE__ */ new Set(["text", "multiline", "number", "boolean", "enum", "flags", "quality", "string"]);
835
- var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1096
+ var isObject3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
836
1097
  var nameOf = (decl) => decl.label ?? decl.name;
837
1098
  var asDecl = (d, name) => ({ ...d, name: d.name ?? name });
838
1099
  function ownProblems(decl, value, adapter) {
@@ -1071,31 +1332,31 @@ function structuredRow(initial) {
1071
1332
  }, controls));
1072
1333
  };
1073
1334
  if (decl.type === "record") {
1074
- const value = isObject(o.value) ? o.value : void 0;
1335
+ const value = isObject3(o.value) ? o.value : void 0;
1075
1336
  const required = value === void 0 ? [] : extensions.valueProblems(decl, value).filter((p) => p.code === "required");
1076
1337
  for (const f of decl.fields ?? []) {
1077
1338
  const missing = required.find((p) => p.path === f.name);
1078
1339
  child(`f:${f.name}`, f, value?.[f.name], (v) => {
1079
- const next = { ...isObject(opts.value) ? opts.value : {} };
1340
+ const next = { ...isObject3(opts.value) ? opts.value : {} };
1080
1341
  if (v === void 0) delete next[f.name];
1081
1342
  else next[f.name] = v;
1082
1343
  commit(Object.keys(next).length > 0 ? next : void 0);
1083
1344
  }, void 0, void 0, missing ? "Required." : void 0);
1084
1345
  }
1085
1346
  } else if (decl.type === "map" && decl.of) {
1086
- const value = isObject(o.value) ? o.value : {};
1347
+ const value = isObject3(o.value) ? o.value : {};
1087
1348
  const keyNodes = decl.key?.type === "reference" && o.adapter ? o.adapter.nodes(decl.key.to ?? "") : void 0;
1088
1349
  const keyKind = decl.key?.to ?? "";
1089
1350
  const keyLabel = (k) => (o.adapter && keyNodes !== void 0 ? nodeLabel(o.adapter, keyKind, k) : void 0) ?? k;
1090
1351
  for (const k of Object.keys(value)) {
1091
1352
  const remove = editable() ? [iconBtn("close", `Remove ${keyLabel(k)}`, () => {
1092
- const next = { ...isObject(opts.value) ? opts.value : {} };
1353
+ const next = { ...isObject3(opts.value) ? opts.value : {} };
1093
1354
  delete next[k];
1094
1355
  commit(Object.keys(next).length > 0 ? next : void 0);
1095
1356
  })] : [];
1096
1357
  const gone = keyNodes !== void 0 && o.adapter && nodeLabel(o.adapter, keyKind, k) === void 0 ? `${k} is not here any more.` : void 0;
1097
1358
  child(`k:${k}`, asDecl(decl.of, k), value[k], (v) => {
1098
- const next = { ...isObject(opts.value) ? opts.value : {} };
1359
+ const next = { ...isObject3(opts.value) ? opts.value : {} };
1099
1360
  next[k] = v === void 0 ? extensions.defaultOf(decl.of) : v;
1100
1361
  commit(next);
1101
1362
  }, remove, keyLabel(k), gone);
@@ -1139,7 +1400,7 @@ function structuredRow(initial) {
1139
1400
  }
1140
1401
  } else if (decl.type === "list" && decl.of) {
1141
1402
  const value = Array.isArray(o.value) ? o.value : [];
1142
- const idOf = (item, i) => decl.id !== void 0 && isObject(item) && typeof item[decl.id] === "string" ? `id:${item[decl.id]}` : `i:${i}`;
1403
+ const idOf = (item, i) => decl.id !== void 0 && isObject3(item) && typeof item[decl.id] === "string" ? `id:${item[decl.id]}` : `i:${i}`;
1143
1404
  value.forEach((item, i) => {
1144
1405
  const set = (next) => commit(next.length > 0 ? next : void 0);
1145
1406
  const controls = editable() ? [
@@ -1153,7 +1414,7 @@ function structuredRow(initial) {
1153
1414
  }, i === value.length - 1),
1154
1415
  iconBtn("close", "Remove", () => set(value.filter((_, j) => j !== i)), false, true)
1155
1416
  ] : [];
1156
- const itemLabel = decl.id !== void 0 && isObject(item) && typeof item[decl.id] === "string" ? item[decl.id] : `${i + 1}`;
1417
+ const itemLabel = decl.id !== void 0 && isObject3(item) && typeof item[decl.id] === "string" ? item[decl.id] : `${i + 1}`;
1157
1418
  child(idOf(item, i), asDecl(decl.of, itemLabel), item, (v) => {
1158
1419
  const next = [...Array.isArray(opts.value) ? opts.value : []];
1159
1420
  next[i] = v === void 0 ? extensions.defaultOf(decl.of) : v;
@@ -1164,8 +1425,8 @@ function structuredRow(initial) {
1164
1425
  if (editable()) {
1165
1426
  adder.append(iconBtn("add", "Add", () => {
1166
1427
  const fresh = extensions.defaultOf(decl.of);
1167
- if (decl.id !== void 0 && isObject(fresh)) {
1168
- const taken = new Set(value.map((x) => isObject(x) ? x[decl.id] : void 0));
1428
+ if (decl.id !== void 0 && isObject3(fresh)) {
1429
+ const taken = new Set(value.map((x) => isObject3(x) ? x[decl.id] : void 0));
1169
1430
  let n = value.length + 1;
1170
1431
  while (taken.has(`item-${n}`)) n++;
1171
1432
  fresh[decl.id] = `item-${n}`;
@@ -1214,7 +1475,15 @@ function valueRows() {
1214
1475
  };
1215
1476
  }
1216
1477
 
1478
+ exports.extensionFold = extensionFold;
1479
+ exports.foldCaption = foldCaption;
1480
+ exports.isRecordMap = isRecordMap;
1481
+ exports.isSet = isSet;
1482
+ exports.recordMapGrid = recordMapGrid;
1483
+ exports.recordMapKeyLabel = recordMapKeyLabel;
1484
+ exports.recordMapSummary = recordMapSummary;
1217
1485
  exports.valueRow = valueRow;
1218
1486
  exports.valueRows = valueRows;
1487
+ exports.valueText = valueText;
1219
1488
  //# sourceMappingURL=value-rows.cjs.map
1220
1489
  //# sourceMappingURL=value-rows.cjs.map