@kubex/zinc 1.1.94 → 1.1.95

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 (36) hide show
  1. package/dist/custom-elements.json +995 -196
  2. package/dist/vscode.html-custom-data.json +74 -19
  3. package/dist/web-types.json +147 -37
  4. package/dist/zn.d.ts +335 -59
  5. package/dist/zn.min.css +1 -1
  6. package/dist/zn.min.js +467 -355
  7. package/docs/pages/components/page-builder.md +121 -19
  8. package/docs/pages/components/slash-menu.md +132 -6
  9. package/docs/pages/components/textarea.md +16 -0
  10. package/package.json +1 -1
  11. package/scss/_root.scss +7 -1
  12. package/src/components/button/button.scss +5 -2
  13. package/src/components/inline-edit/inline-edit.component.ts +6 -1
  14. package/src/components/input/input.component.ts +12 -2
  15. package/src/components/page/page.scss +7 -2
  16. package/src/components/page-builder/modules/page-section-card/page-section-card.component.ts +16 -9
  17. package/src/components/page-builder/modules/page-section-card/page-section-card.scss +13 -0
  18. package/src/components/page-builder/modules/page-section-card/page-section-card.test.ts +9 -0
  19. package/src/components/page-builder/page-builder.component.ts +483 -225
  20. package/src/components/page-builder/page-builder.scss +64 -10
  21. package/src/components/page-builder/page-builder.test.ts +656 -110
  22. package/src/components/page-builder/page-tree.test.ts +483 -0
  23. package/src/components/page-builder/page-tree.ts +329 -0
  24. package/src/components/page-builder/page.types.ts +75 -7
  25. package/src/components/remarkd-editor/remarkd-editor.component.ts +198 -9
  26. package/src/components/remarkd-editor/remarkd-editor.scss +81 -0
  27. package/src/components/remarkd-editor/remarkd-editor.test.ts +179 -0
  28. package/src/components/settings-container/settings-container.scss +2 -1
  29. package/src/components/slash-item/slash-item.component.ts +1 -1
  30. package/src/components/slash-menu/slash-menu-items.ts +48 -0
  31. package/src/components/slash-menu/slash-menu.component.ts +134 -27
  32. package/src/components/slash-menu/slash-menu.scss +90 -12
  33. package/src/components/slash-menu/slash-menu.test.ts +107 -0
  34. package/src/components/textarea/textarea.component.ts +12 -2
  35. package/src/components/textarea/textarea.test.ts +2 -2
  36. package/src/components/translations/translations.component.ts +5 -1
package/dist/zn.d.ts CHANGED
@@ -1901,6 +1901,14 @@ declare module "components/slash-menu/slash-menu-items" {
1901
1901
  export function parseSlashItems(value: string | null | undefined): SlashMenuItem[];
1902
1902
  /** Filters and ranks items against a query. An empty query keeps every item in its declared order. */
1903
1903
  export function filterSlashItems(items: SlashMenuItem[], query: string): SlashMenuItem[];
1904
+ /** The identity an item is remembered by in a menu's recently used list. */
1905
+ export function slashItemKey(item: SlashMenuItem): string;
1906
+ /** The keys of the items most recently chosen from the menu stored under `key`, newest first. */
1907
+ export function readRecentSlashItems(key: string): string[];
1908
+ /** Moves an item to the front of the recently used list stored under `key`, and returns the list. */
1909
+ export function recordRecentSlashItem(key: string, item: SlashMenuItem): string[];
1910
+ /** Forgets the recently used items stored under `key`. */
1911
+ export function clearRecentSlashItems(key: string): void;
1904
1912
  }
1905
1913
  declare module "utilities/caret-position" {
1906
1914
  export interface CaretCoordinates {
@@ -1927,7 +1935,7 @@ declare module "utilities/caret-position" {
1927
1935
  declare module "components/slash-menu/slash-menu.component" {
1928
1936
  import ZincElement from "internal/zinc-element";
1929
1937
  import ZnIcon from "components/icon/index";
1930
- import type { CSSResultGroup, PropertyValues } from 'lit';
1938
+ import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
1931
1939
  import type { Placement, VirtualElement } from '@floating-ui/dom';
1932
1940
  import type { SlashMenuItem } from "components/slash-menu/slash-menu-items";
1933
1941
  export const SLASH_ITEM_SELECT = "zn-slash-item-select";
@@ -1943,12 +1951,19 @@ declare module "components/slash-menu/slash-menu.component" {
1943
1951
  * component driving the menu (e.g. `zn-textarea`) re-emits it as `zn-slash-select`.
1944
1952
  *
1945
1953
  * @csspart panel - The floating panel that holds the list.
1946
- * @csspart heading - The panel's heading.
1954
+ * @csspart list - The scrolling list of items.
1947
1955
  * @csspart item - An item in the list.
1956
+ * @csspart icon - The chip holding an item's icon.
1948
1957
  * @csspart group-heading - A group heading between items.
1958
+ * @csspart divider - The rule closing the recently used section, when the items below it have no heading of their own.
1949
1959
  * @csspart footer - The truncation footer, shown when not every match fits.
1960
+ * @csspart hints - The pinned footer of keyboard hints.
1961
+ * @csspart hint - A single keyboard hint within the footer.
1962
+ * @csspart hint-key - The key shown against a hint.
1950
1963
  *
1951
1964
  * @cssproperty --slash-menu-width - The width of the panel.
1965
+ * @cssproperty --slash-menu-border-radius - The corner radius of the panel.
1966
+ * @cssproperty --slash-menu-item-border-radius - The corner radius of the items and their icon chips.
1952
1967
  * @cssproperty --slash-menu-max-height - The maximum height of the panel before it scrolls.
1953
1968
  */
1954
1969
  export default class ZnSlashMenu extends ZincElement {
@@ -1957,6 +1972,7 @@ declare module "components/slash-menu/slash-menu.component" {
1957
1972
  'zn-icon': typeof ZnIcon;
1958
1973
  };
1959
1974
  private panel;
1975
+ private list;
1960
1976
  private stopAutoUpdate?;
1961
1977
  /** Whether the menu is showing. */
1962
1978
  open: boolean;
@@ -1964,7 +1980,7 @@ declare module "components/slash-menu/slash-menu.component" {
1964
1980
  items: SlashMenuItem[];
1965
1981
  /** The query the items were matched against, shown in the heading. */
1966
1982
  query: string;
1967
- /** The heading shown when there is no query. */
1983
+ /** The name the list is announced by when there is no query. */
1968
1984
  heading: string;
1969
1985
  /** Shown in place of the list when there are no items. */
1970
1986
  emptyText: string;
@@ -1972,6 +1988,18 @@ declare module "components/slash-menu/slash-menu.component" {
1972
1988
  maxItems: number;
1973
1989
  /** Hides the insertion key (the item's value) normally shown against each item. */
1974
1990
  hideKeys: boolean;
1991
+ /** Hides the pinned footer of keyboard hints. */
1992
+ hideHints: boolean;
1993
+ /**
1994
+ * Remembers the items chosen here and lists the most recent of them first, under their own heading.
1995
+ * The key scopes the list to where the menu is used, so each place keeps its own history in
1996
+ * `localStorage`. Leave unset to offer no recently used section.
1997
+ */
1998
+ recentKey: string;
1999
+ /** The most recently used items to list. */
2000
+ maxRecent: number;
2001
+ /** The heading shown above the recently used items. */
2002
+ recentHeading: string;
1975
2003
  /** The element or caret rect the panel is positioned against. */
1976
2004
  anchor: Element | VirtualElement | null;
1977
2005
  /** The preferred placement of the panel. */
@@ -1979,11 +2007,22 @@ declare module "components/slash-menu/slash-menu.component" {
1979
2007
  /** The gap between the caret and the panel. */
1980
2008
  distance: number;
1981
2009
  private activeIndex;
2010
+ private recentKeys;
2011
+ /** How many recently used items the last update listed, to spot the list appearing or reordering. */
2012
+ private recentCount;
2013
+ private get listItems();
2014
+ /**
2015
+ * The remembered items that are in the current list, newest first. Only offered without a query —
2016
+ * once the user is searching, the ranked matches are the better answer.
2017
+ */
2018
+ private get recentItems();
1982
2019
  private get visibleItems();
1983
2020
  /** The item that Enter would insert. */
1984
2021
  get activeItem(): SlashMenuItem | undefined;
1985
2022
  show(): void;
1986
2023
  hide(): void;
2024
+ /** Forgets the items remembered under `recent-key`. */
2025
+ clearRecent(): void;
1987
2026
  /** Sets the active item by index, wrapping at both ends and skipping disabled items. */
1988
2027
  setActiveIndex(index: number): void;
1989
2028
  /** Moves the active item by `delta` places. */
@@ -2006,7 +2045,9 @@ declare module "components/slash-menu/slash-menu.component" {
2006
2045
  protected updated(changed: PropertyValues): void;
2007
2046
  private renderItem;
2008
2047
  private renderItems;
2009
- render(): import("lit-html").TemplateResult<1>;
2048
+ private renderHint;
2049
+ private renderHints;
2050
+ render(): TemplateResult<1>;
2010
2051
  }
2011
2052
  }
2012
2053
  declare module "components/slash-menu/slash-menu-controller" {
@@ -2080,10 +2121,22 @@ declare module "components/slash-menu/slash-menu-controller" {
2080
2121
  private replace;
2081
2122
  }
2082
2123
  }
2124
+ declare module "components/slash-menu/index" {
2125
+ import ZnSlashMenu from "components/slash-menu/slash-menu.component";
2126
+ export * from "components/slash-menu/slash-menu.component";
2127
+ export * from "components/slash-menu/slash-menu-controller";
2128
+ export * from "components/slash-menu/slash-menu-items";
2129
+ export default ZnSlashMenu;
2130
+ global {
2131
+ interface HTMLElementTagNameMap {
2132
+ 'zn-slash-menu': ZnSlashMenu;
2133
+ }
2134
+ }
2135
+ }
2083
2136
  declare module "components/slash-item/slash-item.component" {
2084
2137
  import ZincElement from "internal/zinc-element";
2085
2138
  import type { CSSResultGroup } from 'lit';
2086
- import type { SlashMenuItem } from "components/slash-menu/slash-menu-items";
2139
+ import type { SlashMenuItem } from "components/slash-menu/index";
2087
2140
  /**
2088
2141
  * @summary Declares a single insertion for a slash menu. Renders nothing itself — it describes an
2089
2142
  * entry for the component it is slotted into, e.g. `<zn-textarea>`'s `slash-items` slot.
@@ -2132,18 +2185,6 @@ declare module "components/slash-item/index" {
2132
2185
  }
2133
2186
  }
2134
2187
  }
2135
- declare module "components/slash-menu/index" {
2136
- import ZnSlashMenu from "components/slash-menu/slash-menu.component";
2137
- export * from "components/slash-menu/slash-menu.component";
2138
- export * from "components/slash-menu/slash-menu-controller";
2139
- export * from "components/slash-menu/slash-menu-items";
2140
- export default ZnSlashMenu;
2141
- global {
2142
- interface HTMLElementTagNameMap {
2143
- 'zn-slash-menu': ZnSlashMenu;
2144
- }
2145
- }
2146
- }
2147
2188
  declare module "components/input/input.component" {
2148
2189
  import { type SlashMenuItem } from "components/slash-menu/slash-menu-items";
2149
2190
  import ZincElement from "internal/zinc-element";
@@ -2337,10 +2378,15 @@ declare module "components/input/input.component" {
2337
2378
  slashPreset: string;
2338
2379
  /** The characters that open the slash menu. */
2339
2380
  slashTrigger: string;
2340
- /** The heading shown above the slash menu's items. */
2381
+ /** The name the slash menu's list is announced by. */
2341
2382
  slashHeading: string;
2342
2383
  /** Hides the insertion keys normally shown against the slash menu's items. */
2343
2384
  slashHideKeys: boolean;
2385
+ /**
2386
+ * Lists the items most recently chosen here above the rest, remembered in `localStorage` under this
2387
+ * key. Share a key between the fields that should share a history; leave unset to offer no such list.
2388
+ */
2389
+ slashRecentKey: string;
2344
2390
  /**
2345
2391
  * Resolves additional items each time the menu opens, for lists that come from elsewhere (e.g. an
2346
2392
  * API). Receives the current query and may return a promise. JavaScript only.
@@ -5233,10 +5279,12 @@ declare module "components/inline-edit/inline-edit.component" {
5233
5279
  slashPreset: string;
5234
5280
  /** The characters that open the slash menu. */
5235
5281
  slashTrigger: string;
5236
- /** The heading shown above the slash menu's items. */
5282
+ /** The name the slash menu's list is announced by. */
5237
5283
  slashHeading: string;
5238
5284
  /** Hides the insertion keys normally shown against the slash menu's items. */
5239
5285
  slashHideKeys: boolean;
5286
+ /** Lists the slash menu items most recently chosen here above the rest, remembered under this key. */
5287
+ slashRecentKey: string;
5240
5288
  /** Resolves additional slash menu items each time the menu opens. JavaScript only. */
5241
5289
  slashItemsProvider?: (query: string) => SlashMenuItem[] | Promise<SlashMenuItem[]>;
5242
5290
  options: {
@@ -6680,10 +6728,15 @@ declare module "components/textarea/textarea.component" {
6680
6728
  slashPreset: string;
6681
6729
  /** The characters that open the slash menu. */
6682
6730
  slashTrigger: string;
6683
- /** The heading shown above the slash menu's items. */
6731
+ /** The name the slash menu's list is announced by. */
6684
6732
  slashHeading: string;
6685
6733
  /** Hides the insertion keys normally shown against the slash menu's items. */
6686
6734
  slashHideKeys: boolean;
6735
+ /**
6736
+ * Lists the items most recently chosen here above the rest, remembered in `localStorage` under this
6737
+ * key. Share a key between the fields that should share a history; leave unset to offer no such list.
6738
+ */
6739
+ slashRecentKey: string;
6687
6740
  /**
6688
6741
  * Resolves additional items each time the menu opens, for lists that come from elsewhere (e.g. an
6689
6742
  * API). Receives the current query and may return a promise. JavaScript only.
@@ -8724,10 +8777,12 @@ declare module "components/translations/translations.component" {
8724
8777
  slashPreset: string;
8725
8778
  /** The characters that open the slash menu. */
8726
8779
  slashTrigger: string;
8727
- /** The heading shown above the slash menu's items. */
8780
+ /** The name the slash menu's list is announced by. */
8728
8781
  slashHeading: string;
8729
8782
  /** Hides the insertion keys normally shown against the slash menu's items. */
8730
8783
  slashHideKeys: boolean;
8784
+ /** Lists the slash menu items most recently chosen here above the rest, remembered under this key. */
8785
+ slashRecentKey: string;
8731
8786
  /** Resolves additional slash menu items each time the menu opens. JavaScript only. */
8732
8787
  slashItemsProvider?: (query: string) => SlashMenuItem[] | Promise<SlashMenuItem[]>;
8733
8788
  /** When true, hides the individual language navbar and defers language control to a parent zn-translation-group. */
@@ -9278,6 +9333,8 @@ declare module "components/remarkd-editor/remarkd-editor.component" {
9278
9333
  * @csspart raw - The full-document textarea shown in raw source mode.
9279
9334
  * @csspart slash-menu - The `zn-slash-menu` opened by typing "/" in an empty block.
9280
9335
  * @csspart image-controls - The caption / alignment / size panel shown when an image block is clicked.
9336
+ * @csspart include - The chip rendered in place of an `include::` directive.
9337
+ * @csspart include-picker - The inline Include picker opened from the toolbar or "/include".
9281
9338
  */
9282
9339
  export default class ZnRemarkdEditor extends ZincElement implements ZincFormControl {
9283
9340
  static styles: CSSResultGroup;
@@ -9287,6 +9344,7 @@ declare module "components/remarkd-editor/remarkd-editor.component" {
9287
9344
  private readonly formControlController;
9288
9345
  private readonly slashController;
9289
9346
  private editingDraft;
9347
+ private includeRequest;
9290
9348
  private rawEntryValue;
9291
9349
  private suppressValueSync;
9292
9350
  private suppressBlurCommit;
@@ -9294,6 +9352,11 @@ declare module "components/remarkd-editor/remarkd-editor.component" {
9294
9352
  private slashMenuElement;
9295
9353
  private blocks;
9296
9354
  private editingIndex;
9355
+ /**
9356
+ * Lists the blocks most recently inserted here above the rest of the slash menu, remembered in
9357
+ * `localStorage` under this key. Leave unset to offer no recently used section.
9358
+ */
9359
+ slashRecentKey: string;
9297
9360
  /** Renders the slash menu only once it has been needed. */
9298
9361
  private hasSlashMenu;
9299
9362
  private imagePickerIndex;
@@ -9302,6 +9365,9 @@ declare module "components/remarkd-editor/remarkd-editor.component" {
9302
9365
  private dragIndex;
9303
9366
  private editShell;
9304
9367
  private rawMode;
9368
+ private includeOptions;
9369
+ private includePickerIndex;
9370
+ private includeQuery;
9305
9371
  private pendingDragHandle;
9306
9372
  private dragStartX;
9307
9373
  private dragStartY;
@@ -9320,6 +9386,12 @@ declare module "components/remarkd-editor/remarkd-editor.component" {
9320
9386
  * to `uploadUrl` and the returned `uploadPath` is embedded as the image URL.
9321
9387
  */
9322
9388
  attachmentUrl: string;
9389
+ /**
9390
+ * Endpoint listing the Includes this document may embed, as
9391
+ * `{"items":[{id,title,description,scope,keywords,languages,url}]}`. Labels the
9392
+ * chips rendered for `include::` directives and feeds the include picker.
9393
+ */
9394
+ includeUrl: string;
9323
9395
  /** Adds a toolbar toggle that swaps the block view for the full remarkd source. */
9324
9396
  allowRaw: boolean;
9325
9397
  /** Makes the editor required for form submission. */
@@ -9341,6 +9413,7 @@ declare module "components/remarkd-editor/remarkd-editor.component" {
9341
9413
  protected firstUpdated(_changedProperties: PropertyValues): void;
9342
9414
  disconnectedCallback(): void;
9343
9415
  handleValueChange(): void;
9416
+ handleIncludeUrlChange(): void;
9344
9417
  /**
9345
9418
  * Splits remarkd source into blocks on blank lines, keeping fenced /
9346
9419
  * delimited containers (``` ==== !!!! .... ----) as single blocks.
@@ -9352,6 +9425,8 @@ declare module "components/remarkd-editor/remarkd-editor.component" {
9352
9425
  private handleRenderedClick;
9353
9426
  /** Parses a block that is purely an image (with optional caption/align lines). */
9354
9427
  private parseImageBlock;
9428
+ /** Parses a block that is nothing but an include directive. */
9429
+ private parseIncludeBlock;
9355
9430
  private serializeImageBlock;
9356
9431
  private toggleCheckbox;
9357
9432
  private startEdit;
@@ -9398,10 +9473,16 @@ declare module "components/remarkd-editor/remarkd-editor.component" {
9398
9473
  private createDragGhost;
9399
9474
  private moveDragGhost;
9400
9475
  private pickImage;
9476
+ private pickInclude;
9477
+ private closeIncludePicker;
9478
+ private insertInclude;
9401
9479
  private closeImagePicker;
9402
9480
  private handleImagePicked;
9403
9481
  private insertImage;
9404
9482
  private uploadImage;
9483
+ private hasIncludeBlock;
9484
+ /** Fetches the include list once; every later caller shares the same promise. */
9485
+ private loadIncludeOptions;
9405
9486
  private autosize;
9406
9487
  private toggleRawMode;
9407
9488
  private focusRaw;
@@ -9419,11 +9500,13 @@ declare module "components/remarkd-editor/remarkd-editor.component" {
9419
9500
  private commitRaw;
9420
9501
  private handleToolbarInsert;
9421
9502
  private renderImageControls;
9503
+ private renderIncludeChip;
9422
9504
  private renderBlock;
9423
9505
  render(): import("lit-html").TemplateResult<1>;
9424
9506
  private renderRaw;
9425
9507
  /** The block views, with the inline image picker spliced in when active. */
9426
9508
  private renderBody;
9509
+ private renderIncludePicker;
9427
9510
  private renderImagePicker;
9428
9511
  }
9429
9512
  }
@@ -10532,9 +10615,23 @@ declare module "components/page-builder/page.types" {
10532
10615
  label?: string;
10533
10616
  /** Section content, keyed by field name (the inspector's `name` attributes). */
10534
10617
  data: Record<string, unknown>;
10535
- /** Slot contents for container sections, sized to the type's `slots`. Empty slots are null. */
10618
+ /** Container instances only: the editor-chosen layout. */
10619
+ layout?: PageContainerLayout;
10620
+ /** Container instances only: one ordered stack per cell, row-major. */
10621
+ cells?: PageSection[][];
10622
+ /**
10623
+ * @deprecated The pre-cells fixed-slot shape. Read on load and migrated to
10624
+ * `cells`; never written back.
10625
+ */
10536
10626
  children?: (PageSection | null)[];
10537
10627
  }
10628
+ /** A container instance's editor-chosen layout. */
10629
+ export interface PageContainerLayout {
10630
+ /** One weight per column; the array length IS the column count. */
10631
+ widths: number[];
10632
+ /** When true the builder offers a trailing empty row instead of a pinned cell count. */
10633
+ grow: boolean;
10634
+ }
10538
10635
  /** The complete serialisable state of a page. Order = render order. */
10539
10636
  export interface PageState {
10540
10637
  sections: PageSection[];
@@ -10560,16 +10657,48 @@ declare module "components/page-builder/page.types" {
10560
10657
  configTemplate?: HTMLTemplateElement;
10561
10658
  /** Programmatic inspector body — takes precedence over `configTemplate`. */
10562
10659
  renderConfig?: (section: PageSection, update: (data: Record<string, unknown>) => void) => TemplateResult;
10660
+ /** Marks this type a container: its card renders a grid of cells on the canvas. */
10661
+ container?: boolean;
10563
10662
  /**
10564
- * Number of child slots this section offers on the canvas (a container tile);
10565
- * rendered as a 3-column grid. Containers cannot be placed inside other containers.
10663
+ * Column weights a new instance of this container starts with. Stored as parsed, not
10664
+ * guaranteed sanitised route through `sanitiseWidths`/`defaultLayout` before use.
10665
+ */
10666
+ defaultWidths?: number[];
10667
+ /** Whether a new instance of this container starts growable. */
10668
+ defaultGrow?: boolean;
10669
+ /**
10670
+ * @deprecated Use `container` with `columns`/`widths`. Read as a container of
10671
+ * `DEFAULT_WIDTHS` columns with the cell count pinned to this number.
10566
10672
  */
10567
10673
  slots?: number;
10568
- /** Section type keys allowed in this container's slots. Omit to allow any non-container type. */
10674
+ /**
10675
+ * Section type keys allowed in this container's cells. On a `container` type,
10676
+ * omitting it allows any type, subject to the nesting cap — that is how nesting
10677
+ * is reachable without enumerating types. The deprecated `slots=` alias keeps the
10678
+ * old rule instead: omitting it there allows any non-container type.
10679
+ */
10569
10680
  accepts?: string[];
10570
10681
  }
10571
- /** A container section's slot contents, padded/truncated to the type's slot count. */
10572
- export function sectionChildren(section: PageSection, type: PageSectionType): (PageSection | null)[];
10682
+ /** Beyond 6 columns a card is under ~170px on the 1024px canvas — unreadable. */
10683
+ export const MAX_COLUMNS = 6;
10684
+ /** A single weight beyond 12 makes the other columns unusably thin. */
10685
+ export const MAX_WIDTH = 12;
10686
+ /** Container nesting levels: a top-level container is 1, one inside a cell is 2. */
10687
+ export const MAX_CONTAINER_LEVELS = 2;
10688
+ /** Global section budget; also the sanity clamp on an incoming `cells` length. */
10689
+ export const MAX_SECTIONS = 500;
10690
+ /** What a bare `container` declaration seeds. */
10691
+ export const DEFAULT_WIDTHS: readonly number[];
10692
+ /** Container-ness is a property of the registered type, never of the instance. */
10693
+ export function isContainer(type: PageSectionType | undefined): boolean;
10694
+ /**
10695
+ * Coerces a weights list into a usable one: each entry a whole number from 1 to
10696
+ * MAX_WIDTH (anything unusable becomes 1), at most MAX_COLUMNS entries, falling
10697
+ * back to DEFAULT_WIDTHS only when nothing usable remains.
10698
+ */
10699
+ export function sanitiseWidths(raw: unknown): number[];
10700
+ /** The layout a newly placed instance of a container type starts with. */
10701
+ export function defaultLayout(type: PageSectionType | undefined): PageContainerLayout;
10573
10702
  /** Drag-and-drop MIME carrying a section type id from the palette to the canvas. */
10574
10703
  export const PAGE_TYPE_MIME = "application/x-zn-page-type";
10575
10704
  /** Drag-and-drop MIME carrying a placed section's id when reordering. */
@@ -10584,6 +10713,82 @@ declare module "components/page-builder/page.types" {
10584
10713
  */
10585
10714
  export function sectionSummary(section: PageSection, type?: PageSectionType): string;
10586
10715
  }
10716
+ declare module "components/page-builder/page-tree" {
10717
+ import { type PageSection, type PageSectionType } from "components/page-builder/page.types";
10718
+ /** Drops trailing cells that hold nothing. Interior empties are meaningful and kept. */
10719
+ export function trimTrailingEmptyCells(cells: PageSection[][]): PageSection[][];
10720
+ /** Pads to a whole number of rows, always leaving at least one row to drop into. */
10721
+ export function padCells(cells: PageSection[][], columns: number): PageSection[][];
10722
+ /**
10723
+ * The canonical cell list for a container. A growable container never keeps a
10724
+ * trailing all-empty row (the renderer supplies the `+` row itself, so keeping
10725
+ * one would show two); a fixed container's trailing empty row is its layout and
10726
+ * survives untouched.
10727
+ */
10728
+ export function normaliseCells(cells: PageSection[][], columns: number, grow: boolean): PageSection[][];
10729
+ /**
10730
+ * Re-chunks the same ordered list of stacks into a different column count. Only
10731
+ * trailing empties are dropped, so no section can be lost by a column change.
10732
+ */
10733
+ export function recolumnCells(cells: PageSection[][], columns: number): PageSection[][];
10734
+ /** Row-major view of the flat cell list, for rendering. */
10735
+ export function cellRows(cells: PageSection[][], columns: number): PageSection[][][];
10736
+ export function containerWidths(section: PageSection, type?: PageSectionType): number[];
10737
+ export function containerColumns(section: PageSection, type?: PageSectionType): number;
10738
+ export function containerGrow(section: PageSection, type?: PageSectionType): boolean;
10739
+ /** A container's cells, normalised. Empty for a non-container. */
10740
+ export function containerCells(section: PageSection, type?: PageSectionType): PageSection[][];
10741
+ /** Depth-first search across top-level sections and every cell stack. */
10742
+ export function findSection(sections: PageSection[], id: string | null): PageSection | undefined;
10743
+ /** New section array with `id` replaced by `patch(section)`, wherever it lives. */
10744
+ export function patchSection(sections: PageSection[], id: string, patch: (section: PageSection) => PageSection): PageSection[];
10745
+ /** Detaches a section from wherever it lives, returning it and the remaining tree. */
10746
+ export function extractSection(sections: PageSection[], id: string): [PageSection | undefined, PageSection[]];
10747
+ /** Inserts a section into a container's cell at a stack position. */
10748
+ export function insertIntoCell(sections: PageSection[], containerId: string, cellIndex: number, insertIndex: number, section: PageSection, columns: number): PageSection[];
10749
+ /**
10750
+ * Nesting level of the container with this id: 1 at the top level, 2 inside a
10751
+ * cell, 0 when not found. Only containers have cells, so every cell level is a
10752
+ * container level.
10753
+ */
10754
+ export function containerDepth(sections: PageSection[], containerId: string, depth?: number): number;
10755
+ /** Deep copy with a fresh id for the section and every descendant. */
10756
+ export function cloneWithNewIds(section: PageSection): PageSection;
10757
+ /** Resolves a type key to its registered type — the registry's `get`. */
10758
+ export type TypeLookup = (type: string) => PageSectionType | undefined;
10759
+ /**
10760
+ * Height of a container's subtree: 1 for a container with no nested containers,
10761
+ * otherwise 1 + the tallest nested container's height. 0 for a non-container, so
10762
+ * it composes with containerDepth to bound how deep a moved subtree would reach:
10763
+ * dropping a section into a container at depth d puts the section's own deepest
10764
+ * descendant at d + containerHeight(section).
10765
+ */
10766
+ export function containerHeight(section: PageSection): number;
10767
+ /**
10768
+ * Re-applies each container's own grow/columns normalisation throughout the
10769
+ * tree, so a growable container never carries a trailing all-empty row once
10770
+ * committed — not only when read out via containerCells. A section whose
10771
+ * current type isn't a registered container is left untouched, preserving its
10772
+ * cells verbatim per the unknown-type contract.
10773
+ */
10774
+ export function normaliseGrowth(sections: PageSection[], lookup: TypeLookup): PageSection[];
10775
+ /**
10776
+ * Rewrites the pre-cells `children` shape as `cells`. The old grid was always
10777
+ * DEFAULT_WIDTHS wide, and the slot count was the layout, so cells are padded
10778
+ * out to the declared slot count, then rounded up to a whole number of
10779
+ * DEFAULT_WIDTHS-wide rows; never trimmed.
10780
+ */
10781
+ export function migrateSection(section: PageSection, type?: PageSectionType): PageSection;
10782
+ /**
10783
+ * Normalises externally supplied sections: migrates the old shape, gives every
10784
+ * section a unique id, drops malformed entries, sanitises layouts, caps nesting
10785
+ * and clamps sizes. Pure — warnings are returned for the caller to log.
10786
+ */
10787
+ export function normaliseSections(sections: unknown, lookup: TypeLookup): {
10788
+ sections: PageSection[];
10789
+ warnings: string[];
10790
+ };
10791
+ }
10587
10792
  declare module "components/page-builder/page-registry" {
10588
10793
  import type { PageSectionType } from "components/page-builder/page.types";
10589
10794
  /**
@@ -10683,6 +10888,8 @@ declare module "components/page-builder/modules/page-section-card/page-section-c
10683
10888
  selected: boolean;
10684
10889
  /** Set when the section's type has no registered template — renders greyed. */
10685
10890
  unknown: boolean;
10891
+ /** Set when the builder pins this section to the page — drops the remove action. */
10892
+ locked: boolean;
10686
10893
  protected updated(changed: PropertyValues): void;
10687
10894
  private _action;
10688
10895
  private _actionKeydown;
@@ -10700,7 +10907,7 @@ declare module "components/page-builder/modules/page-section-card/index" {
10700
10907
  }
10701
10908
  }
10702
10909
  declare module "components/page-builder/page-builder.component" {
10703
- import { type CSSResultGroup, type PropertyValues } from 'lit';
10910
+ import { type CSSResultGroup, type PropertyValues, type TemplateResult } from 'lit';
10704
10911
  import { type PageSection, type PageSectionType, type PageState } from "components/page-builder/page.types";
10705
10912
  import ZincElement from "internal/zinc-element";
10706
10913
  import ZnCollapsible from "components/collapsible/index";
@@ -10708,6 +10915,7 @@ declare module "components/page-builder/page-builder.component" {
10708
10915
  import ZnInput from "components/input/index";
10709
10916
  import ZnPagePaletteItem from "components/page-builder/modules/page-palette-item/index";
10710
10917
  import ZnPageSectionCard from "components/page-builder/modules/page-section-card/index";
10918
+ import ZnToggle from "components/toggle/index";
10711
10919
  /**
10712
10920
  * @summary A config-driven page composer: a palette of predefined section types, a linear
10713
10921
  * canvas of section cards, and an inspector for editing each section's content.
@@ -10725,10 +10933,14 @@ declare module "components/page-builder/page-builder.component" {
10725
10933
  * @event zn-page-selection-change - Emitted when the selected section changes. `event.detail.sectionId`.
10726
10934
  *
10727
10935
  * @slot config - `<template type="…">` declarations; never displayed. Each template's attributes
10728
- * (type, label, icon, icon-library, color, category, description, slots, accepts) declare a
10729
- * palette entry and its content declares the inspector form for that type. `slots` makes the
10730
- * section a container with that many child slots; `accepts` is a comma-separated list of the
10731
- * type keys its slots allow.
10936
+ * (type, label, icon, icon-library, color, category, description, container, columns, widths,
10937
+ * grow, slots, accepts) declare a palette entry and its content declares the inspector form for
10938
+ * that type. `container` makes the type a container whose editor-configurable layout starts from
10939
+ * `columns` (seeds equal widths) or `widths` (explicit weights, wins over `columns`); `grow` seeds
10940
+ * a growable instance. `accepts` is a comma-separated list of the type keys its cells allow —
10941
+ * omitted on a `container` type, any type is allowed subject to the nesting cap. `slots` is
10942
+ * @deprecated: it declares a fixed-slot container of `DEFAULT_WIDTHS` columns pinned to that many
10943
+ * cells, and keeps the old any-non-container-type rule when `accepts` is omitted.
10732
10944
  * @slot header-left - Actions shown on the left of the header bar.
10733
10945
  * @slot header-right - Actions shown on the right of the header bar.
10734
10946
  *
@@ -10748,6 +10960,7 @@ declare module "components/page-builder/page-builder.component" {
10748
10960
  'zn-input': typeof ZnInput;
10749
10961
  'zn-page-palette-item': typeof ZnPagePaletteItem;
10750
10962
  'zn-page-section-card': typeof ZnPageSectionCard;
10963
+ 'zn-toggle': typeof ZnToggle;
10751
10964
  };
10752
10965
  private readonly formControlController;
10753
10966
  /** The name of the control, submitted as a name/value pair with form data. */
@@ -10758,6 +10971,13 @@ declare module "components/page-builder/page-builder.component" {
10758
10971
  config: string;
10759
10972
  heading: string;
10760
10973
  subheading: string;
10974
+ /**
10975
+ * Section type key that must lead the page. The builder hoists an existing section of
10976
+ * that type to the top, or inserts an empty one, and pins it there: it can't be
10977
+ * removed, reordered or dragged into a slot, and nothing can be dropped above it.
10978
+ * Its content stays fully editable in the inspector.
10979
+ */
10980
+ requiredFirst: string;
10761
10981
  /** Section types to make available, registered into the internal registry. */
10762
10982
  sectionTypes: PageSectionType[];
10763
10983
  /** Collapses the left palette. Auto-set when the builder becomes narrow. */
@@ -10777,10 +10997,10 @@ declare module "components/page-builder/page-builder.component" {
10777
10997
  /** Index of the drop zone whose "+" type picker is open, if any. */
10778
10998
  private _pickerIndex;
10779
10999
  private _dragOverIndex;
10780
- /** The container slot a drag is currently over, if any. */
10781
- private _slotDragOver;
10782
- /** The container slot whose "+" type picker is open, if any. */
10783
- private _slotPicker;
11000
+ /** The cell a drag is currently over, if any. */
11001
+ private _cellDragOver;
11002
+ /** The cell whose "+" type picker is open, if any. */
11003
+ private _cellPicker;
10784
11004
  /** The stamped config form for the selected section; rebuilt on selection change. */
10785
11005
  private _form;
10786
11006
  private readonly _hasSlot;
@@ -10809,6 +11029,7 @@ declare module "components/page-builder/page-builder.component" {
10809
11029
  /** Sets a custom validation message. Pass an empty string to restore validity. */
10810
11030
  setCustomValidity(_message?: string): void;
10811
11031
  handleConfigChange(): void;
11032
+ handleRequiredFirstChange(): void;
10812
11033
  handleSectionTypesChange(): void;
10813
11034
  registerSectionType(type: PageSectionType): this;
10814
11035
  registerSectionTypes(types: PageSectionType[]): this;
@@ -10854,42 +11075,82 @@ declare module "components/page-builder/page-builder.component" {
10854
11075
  private _registerSlottedTemplates;
10855
11076
  /** Normalises and installs an externally provided state; resets selection. */
10856
11077
  private _applyExternalState;
10857
- /** Finds a section by id, searching top-level sections and slotted children. */
10858
- private _findSection;
10859
- /** New sections array with the section patched wherever it lives (top level or slot). */
10860
- private _patchSection;
10861
- /** Detaches a section wherever it lives: removed from the top level, or its slot nulled. */
10862
- private _extract;
10863
- /** Installs a new state from a user edit and notifies listeners. */
11078
+ /**
11079
+ * Id of the section pinned to the top of the page, or null when `required-first`
11080
+ * is unset. Derived from the state rather than stored on it, so nothing about the
11081
+ * lock leaks into the persisted config.
11082
+ */
11083
+ private get _pinnedId();
11084
+ private _isPinned;
11085
+ /** Lowest top-level index a section may be added or moved to. */
11086
+ private get _firstFreeIndex();
11087
+ /**
11088
+ * Sections reordered so `required-first` leads the page: an existing section of that
11089
+ * type is hoisted to the front, otherwise an empty one is prepended. Returns the
11090
+ * argument unchanged when there is nothing to do, so callers can compare by identity.
11091
+ */
11092
+ private _requireFirst;
11093
+ /**
11094
+ * Installs a new state from a user edit and notifies listeners. Re-applies
11095
+ * growth normalisation across the tree first, so a growable container never
11096
+ * carries a trailing empty row past this point — the read path (`containerCells`)
11097
+ * already hides it, but state/value/zn-page-change/auto-save must not diverge
11098
+ * from what the canvas renders.
11099
+ */
10864
11100
  private _commit;
10865
11101
  private _selectedSection;
10866
11102
  private _select;
10867
11103
  private _pushHistory;
10868
11104
  undo: () => void;
10869
11105
  redo: () => void;
11106
+ /** A fresh section of a registered type, seeded with layout/cells when it's a container. */
11107
+ private _newSection;
10870
11108
  /** Adds a section of a registered type at `index` (default: end). Returns null for unknown types. */
10871
11109
  addSection(type: string, index?: number): PageSection | null;
10872
- /** Adds a new section of a registered type into a container's slot. Returns null if not allowed. */
10873
- addSectionToSlot(type: string, containerId: string, slotIndex: number): PageSection | null;
10874
11110
  private _removeSection;
10875
11111
  private _duplicateSection;
10876
- /** Moves a section (top-level or slotted) to a top-level position. */
11112
+ /** Locates a section living inside a container cell. */
11113
+ private _findCellOwner;
11114
+ /** Moves a section (top-level or inside a cell) to a top-level position. */
10877
11115
  private _moveSection;
10878
- /**
10879
- * Moves a section into a container's slot. Dropping onto an occupied slot swaps
10880
- * the two children (slot-to-slot reordering); top-level sections and containers
10881
- * only enter empty slots / never enter slots respectively.
10882
- */
10883
- private _moveToSlot;
11116
+ /** Id of the section currently being dragged — dataTransfer is unreadable during dragover. */
11117
+ private _draggingId;
10884
11118
  private _onCardDragStart;
10885
11119
  /** Whether a drag carries one of the builder's own payloads. */
10886
11120
  private _isPageDrag;
10887
- private _onSlotDragOver;
10888
- private _onSlotDrop;
10889
11121
  private _onZoneDragOver;
10890
11122
  private _onZoneDrop;
10891
11123
  private _onCanvasDragOver;
10892
11124
  private _onCanvasDrop;
11125
+ /**
11126
+ * Whether `typeKey`'s registered type is allowed into this container by its
11127
+ * `accepts`/`slots=` rule — the nesting depth cap is a separate concern,
11128
+ * checked by each caller against its own notion of how tall the dropped
11129
+ * subtree is.
11130
+ */
11131
+ private _acceptsType;
11132
+ /**
11133
+ * Whether `typeKey` may be dropped into this container's cells from the
11134
+ * palette. A newly created section always has height 1, so the depth cap
11135
+ * only needs the target's own depth.
11136
+ */
11137
+ private _acceptsInCell;
11138
+ /**
11139
+ * Whether a dragged, already-placed section may land in this container's
11140
+ * cells. Unlike a palette drop, the moved subtree can already be several
11141
+ * containers tall (it may itself hold a nested container), so the cap has to
11142
+ * account for that height, not just the target's depth: dropping `moved` at
11143
+ * a new depth of `containerDepth(container) + 1` puts its own deepest
11144
+ * descendant at `containerDepth(container) + containerHeight(moved)`.
11145
+ */
11146
+ private _canMoveIntoCell;
11147
+ private _onCellDragOver;
11148
+ private _onCellDrop;
11149
+ /** Adds a new section of a registered type into a container cell. */
11150
+ addSectionToCell(type: string, containerId: string, cellIndex: number, insertIndex?: number): PageSection | null;
11151
+ private _moveToCell;
11152
+ /** Types offerable in a container's cells. */
11153
+ private _cellTypes;
10893
11154
  private _onCardKeydown;
10894
11155
  private _renderPalette;
10895
11156
  private _renderPaletteItem;
@@ -10899,10 +11160,17 @@ declare module "components/page-builder/page-builder.component" {
10899
11160
  /** The one card template both the page list and slot cells render. */
10900
11161
  private _renderSectionCard;
10901
11162
  private _renderCard;
10902
- private _renderSlot;
10903
- /** Types allowed in a container's slots: non-containers, filtered by its accepts list. */
10904
- private _slotTypes;
10905
- /** The one type-picker template both drop zones and slot cells render. */
11163
+ /**
11164
+ * A card, or — for a container its card plus its own cell grid. Shared by the
11165
+ * top-level section list and container-cell stacks, so nesting renders at every level.
11166
+ */
11167
+ private _renderNode;
11168
+ /** A container's cell grid: one track per width, one stack per cell. */
11169
+ private _renderCells;
11170
+ private _renderCell;
11171
+ /** Thin insertion target between two cards in a stack. */
11172
+ private _renderCellStrip;
11173
+ /** The one type-picker template both drop zones and container cells render. */
10906
11174
  private _renderTypePicker;
10907
11175
  private _renderDropZone;
10908
11176
  /**
@@ -10916,8 +11184,16 @@ declare module "components/page-builder/page-builder.component" {
10916
11184
  private _onInspectorInput;
10917
11185
  private _updateSectionData;
10918
11186
  private _renameSection;
11187
+ /** Replaces a container's layout, keeping cells consistent with it. */
11188
+ private _setLayout;
11189
+ private _setColumns;
11190
+ private _setWidth;
11191
+ private _setGrow;
11192
+ /** Pads with empty rows, or trims trailing empty rows down to the last occupied one. */
11193
+ private _setRows;
11194
+ private _renderLayoutGroup;
10919
11195
  private _renderInspector;
10920
- render(): import("lit-html").TemplateResult<1>;
11196
+ render(): TemplateResult<1>;
10921
11197
  }
10922
11198
  }
10923
11199
  declare module "components/page-builder/index" {