@kubex/zinc 1.1.68 → 1.1.69

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 (30) hide show
  1. package/dist/custom-elements.json +1417 -275
  2. package/dist/vscode.html-custom-data.json +149 -27
  3. package/dist/web-types.json +351 -64
  4. package/dist/zn.d.ts +325 -14
  5. package/dist/zn.min.js +495 -374
  6. package/docs/pages/components/collapsible.md +6 -2
  7. package/docs/pages/components/preview-frame-demo.njk +33 -4
  8. package/docs/pages/components/preview-frame.md +20 -0
  9. package/docs/pages/components/theme-editor.md +269 -0
  10. package/docs/superpowers/plans/2026-08-03-theme-editor.md +1536 -0
  11. package/docs/superpowers/specs/2026-08-03-theme-editor-design.md +327 -0
  12. package/package.json +1 -1
  13. package/src/components/collapsible/collapsible.component.ts +21 -23
  14. package/src/components/collapsible/collapsible.scss +10 -0
  15. package/src/components/dropdown/dropdown.component.ts +9 -0
  16. package/src/components/editor/editor.component.ts +20 -21
  17. package/src/components/preview-frame/preview-frame.component.ts +126 -16
  18. package/src/components/preview-frame/preview-frame.scss +27 -0
  19. package/src/components/preview-frame/preview-frame.test.ts +253 -0
  20. package/src/components/theme-editor/index.ts +12 -0
  21. package/src/components/theme-editor/theme-editor.component.ts +761 -0
  22. package/src/components/theme-editor/theme-editor.scss +309 -0
  23. package/src/components/theme-editor/theme-editor.test.ts +1584 -0
  24. package/src/events/events.ts +2 -0
  25. package/src/events/zn-theme-change.ts +13 -0
  26. package/src/events/zn-theme-submit.ts +9 -0
  27. package/src/types/web-test-runner-commands.d.ts +6 -0
  28. package/src/zinc.ts +1 -0
  29. package/tsconfig.json +7 -1
  30. package/web-test-runner.config.js +3 -1
package/dist/zn.d.ts CHANGED
@@ -1390,6 +1390,7 @@ declare module "components/collapsible/collapsible.component" {
1390
1390
  *
1391
1391
  * @csspart header - The header row (toggle).
1392
1392
  * @csspart caption - The caption text.
1393
+ * @csspart content - The expandable content wrapper.
1393
1394
  */
1394
1395
  export default class ZnCollapsible extends ZincElement {
1395
1396
  static styles: CSSResultGroup;
@@ -10388,11 +10389,19 @@ declare module "components/page-builder/index" {
10388
10389
  declare module "components/preview-frame/preview-frame.component" {
10389
10390
  import { type CSSResultGroup } from 'lit';
10390
10391
  import ZincElement from "internal/zinc-element";
10392
+ const DEVICE_WIDTHS: {
10393
+ readonly desktop: "100%";
10394
+ readonly tablet: "768px";
10395
+ readonly mobile: "390px";
10396
+ };
10397
+ export type PreviewFrameDevice = keyof typeof DEVICE_WIDTHS;
10391
10398
  /**
10392
10399
  * @summary Embeds a live preview iframe and drives the hp-preview postMessage
10393
10400
  * protocol: answers the frame's ready handshake with a config payload fetched
10394
- * from data-uri, auto-saves watched forms on change, and refreshes the
10395
- * preview after each save.
10401
+ * from data-uri, auto-saves watched forms on change, refreshes the preview
10402
+ * after each save (its own, or a shell-driven save of a `refresh-on` form),
10403
+ * and accepts a theme payload via setTheme() that is retained and replayed
10404
+ * after every ready handshake.
10396
10405
  * @documentation https://zinc.style/components/preview-frame
10397
10406
  * @status experimental
10398
10407
  * @since 1.0
@@ -10400,8 +10409,12 @@ declare module "components/preview-frame/preview-frame.component" {
10400
10409
  * @event zn-error - Emitted when the preview reports a render error or a save fails.
10401
10410
  *
10402
10411
  * @csspart base - The component's base wrapper.
10412
+ * @csspart stage - The device-width wrapper around the iframe.
10403
10413
  * @csspart iframe - The preview iframe.
10404
10414
  * @csspart error - The error overlay.
10415
+ *
10416
+ * @cssproperty --zn-preview-frame-dot-spacing - Spacing of the backdrop dot grid (`backdrop="dots"`). Defaults to 20px.
10417
+ * @cssproperty --zn-preview-frame-dot-opacity - Opacity of the backdrop dots (`backdrop="dots"`). Defaults to 0.08.
10405
10418
  */
10406
10419
  export default class ZnPreviewFrame extends ZincElement {
10407
10420
  static styles: CSSResultGroup;
@@ -10418,24 +10431,52 @@ declare module "components/preview-frame/preview-frame.component" {
10418
10431
  * auto-saved, or used to trigger a preview refresh. Override to widen the scope.
10419
10432
  */
10420
10433
  watch: string;
10434
+ /**
10435
+ * Selector for forms whose saves are left to the shell but should still
10436
+ * refresh the preview. These are never intercepted: the shell submits them
10437
+ * (so its own response handling — alerts, refreshes — runs as normal) and the
10438
+ * preview re-fetches its config once the shell reports the save complete.
10439
+ * Set empty to disable.
10440
+ */
10441
+ refreshOn: string;
10421
10442
  /** Debounce in ms between a form change and its auto-save. */
10422
10443
  debounce: number;
10423
10444
  /**
10424
10445
  * Zooms the previewed page out (0–1]. The frame always fills the panel;
10425
10446
  * zoom shrinks the content browser-style, so 0.4 shows the page at 40%
10426
10447
  * size with correspondingly more of it visible. 1 = natural size.
10448
+ * Ignored when `fill` is set.
10427
10449
  */
10428
10450
  zoom: number;
10429
10451
  /**
10430
10452
  * The visible height (in CSS pixels) of the preview panel. Fixed rather
10431
10453
  * than measured, because a measured height would feed back into the
10432
- * scaled iframe's layout box and grow unbounded.
10454
+ * scaled iframe's layout box and grow unbounded. With `fill` set, this
10455
+ * becomes a `min-height` floor instead of the height.
10433
10456
  */
10434
10457
  minHeight: number;
10458
+ /**
10459
+ * Fills the panel's own column height instead of using a fixed
10460
+ * `min-height` pixel height — for hosts (like zn-theme-editor) whose
10461
+ * layout already stretches the column to match a taller sibling.
10462
+ * `zoom` is ignored when set: its oversize maths depends on a known
10463
+ * pixel height, which `fill` deliberately doesn't have.
10464
+ */
10465
+ fill: boolean;
10466
+ /**
10467
+ * Constrains and centres the preview to a device width: `desktop` (100%),
10468
+ * `tablet` (768px) or `mobile` (390px). The iframe element itself is
10469
+ * narrowed, so the embedded page's own media queries fire.
10470
+ */
10471
+ device: PreviewFrameDevice;
10472
+ /** Backdrop behind the stage: `dots` (default) is the canvas dot grid; `panel` is a plain `rgb(var(--zn-panel))` fill. */
10473
+ backdrop: 'dots' | 'panel';
10435
10474
  frame: HTMLIFrameElement;
10436
10475
  private error;
10437
10476
  private _generation;
10477
+ private _theme;
10438
10478
  private readonly _watchedForms;
10479
+ private readonly _refreshForms;
10439
10480
  private readonly _debounceTimers;
10440
10481
  private readonly _formObserver;
10441
10482
  connectedCallback(): void;
@@ -10443,8 +10484,16 @@ declare module "components/preview-frame/preview-frame.component" {
10443
10484
  private readonly _onMessage;
10444
10485
  /** Re-fetches the payload and pushes a fresh config to the preview. */
10445
10486
  refresh(): Promise<void>;
10487
+ /**
10488
+ * Pushes a theme payload into the preview. The payload is retained and
10489
+ * re-posted after every ready handshake, so a frame reload doesn't drop an
10490
+ * in-progress theme.
10491
+ */
10492
+ setTheme(theme: Record<string, unknown>): void;
10493
+ private _postTheme;
10446
10494
  private _sendConfig;
10447
10495
  private _attachForms;
10496
+ private readonly _onShellSave;
10448
10497
  private _detachForm;
10449
10498
  private readonly _onChange;
10450
10499
  private readonly _onSubmit;
@@ -10463,6 +10512,247 @@ declare module "components/preview-frame/index" {
10463
10512
  }
10464
10513
  }
10465
10514
  }
10515
+ declare module "events/zn-error" {
10516
+ export type ZnErrorEvent = CustomEvent<{
10517
+ status?: number;
10518
+ message?: string;
10519
+ }>;
10520
+ global {
10521
+ interface GlobalEventHandlersEventMap {
10522
+ 'zn-error': ZnErrorEvent;
10523
+ }
10524
+ }
10525
+ }
10526
+ declare module "components/theme-editor/theme-editor.component" {
10527
+ import { type CSSResultGroup } from 'lit';
10528
+ import ZincElement from "internal/zinc-element";
10529
+ import ZnButton from "components/button/index";
10530
+ import ZnCollapsible from "components/collapsible/index";
10531
+ import ZnIcon from "components/icon/index";
10532
+ import ZnNavbar from "components/navbar/index";
10533
+ import ZnOption from "components/option/index";
10534
+ import ZnPreviewFrame from "components/preview-frame/index";
10535
+ import ZnSelect from "components/select/index";
10536
+ import ZnTabs from "components/tabs/index";
10537
+ export type ThemeEditorMode = 'light' | 'dark';
10538
+ export type ThemeEditorDevice = 'desktop' | 'tablet' | 'mobile';
10539
+ export interface ThemeEditorGroup {
10540
+ /** The slot name controls are assigned to with `slot="<name>"`. */
10541
+ name: string;
10542
+ caption: string;
10543
+ description?: string;
10544
+ /** Renders expanded initially. */
10545
+ open?: boolean;
10546
+ }
10547
+ export interface ThemeEditorSection extends ThemeEditorGroup {
10548
+ /**
10549
+ * Nests a collapsible per group inside this section's tab instead of the
10550
+ * section's own controls directly. A non-empty `groups` on ANY section
10551
+ * switches every section to `zn-tabs`, regardless of `section-layout`.
10552
+ */
10553
+ groups?: ThemeEditorGroup[];
10554
+ }
10555
+ export interface ThemeEditorSource {
10556
+ label: string;
10557
+ src: string;
10558
+ }
10559
+ /**
10560
+ * @summary A theme editor: slotted form controls drive a live preview frame,
10561
+ * with a toolbar for the preview's light/dark mode and device width.
10562
+ * @documentation https://zinc.style/components/theme-editor
10563
+ * @status experimental
10564
+ * @since 1.0
10565
+ *
10566
+ * @dependency zn-collapsible
10567
+ * @dependency zn-preview-frame
10568
+ * @dependency zn-icon
10569
+ * @dependency zn-button
10570
+ * @dependency zn-tabs
10571
+ * @dependency zn-navbar
10572
+ * @dependency zn-select
10573
+ *
10574
+ * @event zn-theme-change - Emitted when the values, mode or device change.
10575
+ * @event zn-theme-submit - Emitted on submit (button click), carrying the
10576
+ * current values. With `action` set, only fires after a successful save.
10577
+ * @event zn-error - Emitted when a save fails. Also seen for preview render
10578
+ * failures: the frame's zn-error is composed and not stopped, so it bubbles
10579
+ * out through the editor too.
10580
+ *
10581
+ * @slot - Ungrouped theme controls, rendered above any sections. Controls
10582
+ * assigned `slot="<name>"` matching a `sections` entry (or, when nested, a
10583
+ * `groups` entry) render inside that section/group instead. Harvesting and
10584
+ * change detection walk every slot's full assigned subtree, not just direct
10585
+ * children.
10586
+ * @slot toolbar - Actions in the toolbar, right-aligned beside the device
10587
+ * controls. Where a save button belongs.
10588
+ * @slot footer - Actions pinned beneath the controls. The built-in submit button
10589
+ * lives in the toolbar, not here.
10590
+ *
10591
+ * @csspart base - The component's base wrapper.
10592
+ * @csspart controls - The left-hand controls column, full height.
10593
+ * @csspart controls-header - The controls column's header row: `controls-caption` on the left, the light/dark mode toggle on the right.
10594
+ * @csspart toolbar - The preview column's header row: `preview-caption` on the left, the device switcher (and sources/submit) on the right. Spans the preview column only.
10595
+ * @csspart section - A rendered section's or group's collapsible (`section-layout="collapsible"`, or any nested group).
10596
+ * @csspart footer - The footer wrapper beneath the controls.
10597
+ * @csspart preview - The preview column.
10598
+ * @csspart error - The inline error strip.
10599
+ * @csspart preview__base - The frame's base wrapper (forwarded from zn-preview-frame).
10600
+ * @csspart preview__stage - The frame's device-width wrapper (forwarded from zn-preview-frame).
10601
+ * @csspart preview__iframe - The frame's iframe (forwarded from zn-preview-frame).
10602
+ * @csspart preview__error - The frame's own error overlay (forwarded from zn-preview-frame).
10603
+ *
10604
+ * @cssproperty --zn-theme-editor-controls-width - Width of the controls column.
10605
+ */
10606
+ export default class ZnThemeEditor extends ZincElement {
10607
+ static styles: CSSResultGroup;
10608
+ static dependencies: {
10609
+ 'zn-collapsible': typeof ZnCollapsible;
10610
+ 'zn-preview-frame': typeof ZnPreviewFrame;
10611
+ 'zn-icon': typeof ZnIcon;
10612
+ 'zn-button': typeof ZnButton;
10613
+ 'zn-tabs': typeof ZnTabs;
10614
+ 'zn-navbar': typeof ZnNavbar;
10615
+ 'zn-select': typeof ZnSelect;
10616
+ 'zn-option': typeof ZnOption;
10617
+ };
10618
+ /** URL of the preview shell page; forwarded to the frame. */
10619
+ src: string;
10620
+ /** Expected origin of the iframe; forwarded to the frame. */
10621
+ frameOrigin: string;
10622
+ /** Optional endpoint returning the base hp-preview:config payload. */
10623
+ dataUri: string;
10624
+ /** Which mode the preview renders in. Travels in the theme payload. */
10625
+ mode: ThemeEditorMode;
10626
+ /** Preview viewport width. Resizes the frame only; not part of the payload. */
10627
+ device: ThemeEditorDevice;
10628
+ /** Minimum height of the preview row, in pixels; forwarded to the frame as its own floor. */
10629
+ minHeight: number;
10630
+ /** Debounce in ms between a control change and the push to the preview. */
10631
+ debounce: number;
10632
+ /** Optional endpoint the values are POSTed to. Empty = no persistence. */
10633
+ action: string;
10634
+ /** Debounce in ms between a control change and the save POST. */
10635
+ saveDebounce: number;
10636
+ /**
10637
+ * Groups controls into named sections. Empty/unset renders one ungrouped
10638
+ * column. A section with a non-empty `groups` nests a collapsible per
10639
+ * group inside a `zn-tabs` tab for that section - see `groups` on
10640
+ * `ThemeEditorSection`.
10641
+ */
10642
+ sections: ThemeEditorSection[];
10643
+ /**
10644
+ * Presentation for flat, group-less `sections`: stacked `zn-collapsible`s
10645
+ * (default) or a `zn-tabs` strip. Ignored once any section has `groups` -
10646
+ * nested sections always render as tabs.
10647
+ */
10648
+ sectionLayout: 'collapsible' | 'tabs';
10649
+ /** Dropdown of preview sources, `{label, src}`, rendered in the toolbar. Empty/unset renders no dropdown; the first entry wins over an explicit `src` when non-empty. */
10650
+ sources: ThemeEditorSource[];
10651
+ /** Collapses the controls column. */
10652
+ controlsCollapsed: boolean;
10653
+ /** Presents the editor as its own bordered, rounded panel with a plain preview backdrop, rather than embedded in a dotted canvas. */
10654
+ standalone: boolean;
10655
+ /** Caption in the controls column's header row. Empty (default) renders no text; the row itself always renders. */
10656
+ controlsCaption: string;
10657
+ /** Caption at the left of the toolbar, opposite the device and mode controls. Empty (default) renders no text. */
10658
+ previewCaption: string;
10659
+ /** Label for the built-in submit button. Empty (default) renders no button. */
10660
+ submitLabel: string;
10661
+ /** Disables the debounced auto-save; saving then happens only via submit. Preview pushes are unaffected. */
10662
+ manual: boolean;
10663
+ frame: ZnPreviewFrame;
10664
+ private controlsSlot;
10665
+ private sectionSlots;
10666
+ protected error: string;
10667
+ private _submitting;
10668
+ private _sourceIndex;
10669
+ private readonly hasSlotController;
10670
+ private _pushTimer?;
10671
+ private _saveTimer?;
10672
+ private _saving;
10673
+ private _saveQueued;
10674
+ private _saveWaiters;
10675
+ private readonly _narrowQuery;
10676
+ private _wasNarrow;
10677
+ private _mounted;
10678
+ private _lastControls;
10679
+ private readonly _controlsObserverConfig;
10680
+ private readonly _controlsObserver;
10681
+ private _modeValues;
10682
+ private _suppressDepth;
10683
+ /** The current per-mode value sets. Returns copies. */
10684
+ get values(): {
10685
+ light: Record<string, unknown>;
10686
+ dark: Record<string, unknown>;
10687
+ };
10688
+ /** The active mode's values - what gets pushed to the preview frame. */
10689
+ get activeValues(): Record<string, unknown>;
10690
+ /** The default slot plus every rendered section slot. */
10691
+ private _controlSlots;
10692
+ /** Walks every control slot (default and sections) for every enabled, named control. */
10693
+ private _harvestNamed;
10694
+ /** Whether a direct child is assigned to the named slot — an empty section renders no chrome. */
10695
+ private _hasAssignedControls;
10696
+ private _isBooleanControl;
10697
+ private _readControlValue;
10698
+ /** Seeds light/dark entries for any control name not already present. */
10699
+ private _seed;
10700
+ /** Writes a mode's value set back into the controls so they display it. */
10701
+ private _writeBack;
10702
+ /** Harvests the controls' current displayed values into a mode's set. */
10703
+ private _harvestInto;
10704
+ connectedCallback(): void;
10705
+ disconnectedCallback(): void;
10706
+ private readonly _onNarrowChange;
10707
+ protected firstUpdated(): void;
10708
+ /** Pushes the active mode's values into the preview and announces it. */
10709
+ private _push;
10710
+ private _queueSave;
10711
+ private _save;
10712
+ /** Resolves once a save actually carrying the current values has settled. */
10713
+ private _awaitSave;
10714
+ /**
10715
+ * Pushes only if the deep set of named controls (the same set harvesting
10716
+ * walks, so it includes controls nested inside sections) has changed since
10717
+ * the last push. Comparison is by element identity only, never by value.
10718
+ */
10719
+ private _pushIfControlsChanged;
10720
+ /** Harvests and queues a save for a pending debounced edit, then cancels its timer. */
10721
+ private _flushPendingEdit;
10722
+ private readonly _onSubmit;
10723
+ private _announce;
10724
+ private _fail;
10725
+ private readonly _onControlChange;
10726
+ private readonly _onSlotChange;
10727
+ private readonly _setDevice;
10728
+ private readonly _toggleMode;
10729
+ private readonly _onFrameError;
10730
+ private _sourcesSafe;
10731
+ private _frameSrc;
10732
+ private readonly _onSourceChange;
10733
+ private _sectionsSafe;
10734
+ private _groupsFor;
10735
+ /** Whether any section has a populated `groups` - the switch to nested tabs+collapsibles. */
10736
+ private _hasNestedGroups;
10737
+ private _visibleGroups;
10738
+ /** Configured sections that have an assigned control, or (nested) a populated group - shared by every presentation. */
10739
+ private _visibleSections;
10740
+ private _renderSections;
10741
+ private _renderGroups;
10742
+ private _renderTabs;
10743
+ render(): import("lit-html").TemplateResult<1>;
10744
+ }
10745
+ }
10746
+ declare module "components/theme-editor/index" {
10747
+ import ZnThemeEditor from "components/theme-editor/theme-editor.component";
10748
+ export * from "components/theme-editor/theme-editor.component";
10749
+ export default ZnThemeEditor;
10750
+ global {
10751
+ interface HTMLElementTagNameMap {
10752
+ 'zn-theme-editor': ZnThemeEditor;
10753
+ }
10754
+ }
10755
+ }
10466
10756
  declare module "utilities/form" {
10467
10757
  export { clearFormStoreValues } from "internal/form";
10468
10758
  }
@@ -10629,6 +10919,35 @@ declare module "events/zn-page-selection-change" {
10629
10919
  }
10630
10920
  }
10631
10921
  }
10922
+ declare module "events/zn-theme-change" {
10923
+ import type { ThemeEditorDevice, ThemeEditorMode } from "components/theme-editor/theme-editor.component";
10924
+ export type ZnThemeChangeEvent = CustomEvent<{
10925
+ values: {
10926
+ light: Record<string, unknown>;
10927
+ dark: Record<string, unknown>;
10928
+ };
10929
+ mode: ThemeEditorMode;
10930
+ device: ThemeEditorDevice;
10931
+ }>;
10932
+ global {
10933
+ interface GlobalEventHandlersEventMap {
10934
+ 'zn-theme-change': ZnThemeChangeEvent;
10935
+ }
10936
+ }
10937
+ }
10938
+ declare module "events/zn-theme-submit" {
10939
+ export type ZnThemeSubmitEvent = CustomEvent<{
10940
+ values: {
10941
+ light: Record<string, unknown>;
10942
+ dark: Record<string, unknown>;
10943
+ };
10944
+ }>;
10945
+ global {
10946
+ interface GlobalEventHandlersEventMap {
10947
+ 'zn-theme-submit': ZnThemeSubmitEvent;
10948
+ }
10949
+ }
10950
+ }
10632
10951
  declare module "events/events" {
10633
10952
  export type { ZnAfterHideEvent } from "events/zn-after-hide";
10634
10953
  export type { ZnAfterShowEvent } from "events/zn-after-show";
@@ -10649,6 +10968,8 @@ declare module "events/events" {
10649
10968
  export type { ZnFlowConnectEvent } from "events/zn-flow-connect";
10650
10969
  export type { ZnPageChangeEvent } from "events/zn-page-change";
10651
10970
  export type { ZnPageSelectionChangeEvent } from "events/zn-page-selection-change";
10971
+ export type { ZnThemeChangeEvent } from "events/zn-theme-change";
10972
+ export type { ZnThemeSubmitEvent } from "events/zn-theme-submit";
10652
10973
  }
10653
10974
  declare module "zinc" {
10654
10975
  export { default as Button } from "components/button/index";
@@ -10763,6 +11084,7 @@ declare module "zinc" {
10763
11084
  export { default as PagePaletteItem } from "components/page-builder/modules/page-palette-item/index";
10764
11085
  export { default as PageSectionCard } from "components/page-builder/modules/page-section-card/index";
10765
11086
  export { default as PreviewFrame } from "components/preview-frame/index";
11087
+ export { default as ThemeEditor } from "components/theme-editor/index";
10766
11088
  export { default as ZincElement } from "internal/zinc-element";
10767
11089
  export * from "utilities/on";
10768
11090
  export * from "utilities/query";
@@ -10875,17 +11197,6 @@ declare module "events/zn-element-added" {
10875
11197
  }
10876
11198
  }
10877
11199
  }
10878
- declare module "events/zn-error" {
10879
- export type ZnErrorEvent = CustomEvent<{
10880
- status?: number;
10881
- message?: string;
10882
- }>;
10883
- global {
10884
- interface GlobalEventHandlersEventMap {
10885
- 'zn-error': ZnErrorEvent;
10886
- }
10887
- }
10888
- }
10889
11200
  declare module "events/zn-expand" {
10890
11201
  export type ZnExpandEvent = CustomEvent<Record<PropertyKey, never>>;
10891
11202
  global {