fold-ng 0.10.2 → 0.11.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/CHANGELOG.md CHANGED
@@ -8,6 +8,140 @@ All notable changes to **fold-ng** are documented here. The format follows
8
8
 
9
9
  _Nothing yet._
10
10
 
11
+ ## [0.11.0] - 2026-08-14
12
+
13
+ ### Added
14
+
15
+ - **Five icons the package and its consumers were already asking for**:
16
+ `alert`, `login`, `hash`, `phone`, `inbox`. `alert` is the one that mattered —
17
+ fold's own semantic vocabulary has both `warning` and `alert` tones (callout,
18
+ empty-state, badge), while the icon set only had `warning`, so anyone following
19
+ the package's own words got nothing. `inbox` was being asked for **by
20
+ `fold-calendar-agenda` itself**, and had never existed. `login` mirrors
21
+ `logout` exactly (same path, flipped), which is why they now read as a pair.
22
+ - **`fold-empty-state` takes an `icon` name.** The glyph was reachable only
23
+ through the `[empty-icon]` slot, while `fold-element-title`, `fold-callout` and
24
+ `fold-back-link` all accept an `icon` input — so `icon="check"` on an empty
25
+ state landed as a mute HTML attribute and rendered nothing, with no error at
26
+ build or runtime. The slot stays, for art the registry can't hold (rule 4.7),
27
+ and **wins** when both are supplied. `iconSize` tunes the named glyph.
28
+ - **`fold-nav-tile` takes a `badge`.** A launcher tile and a `fold-menu-item` are
29
+ the same destination, but only the rail could carry a count — so an app that
30
+ showed "3 waiting" in the rail went silent the moment the window narrowed to
31
+ the tiles. Same reading as the menu item's, including **a count of `0`
32
+ rendering nothing**, and the count folds into the tile's accessible name.
33
+ - **`provideFoldCommonLabels()` — one place for the four words the package says
34
+ on its own.** `optional`, `info` (the help-bubble trigger), `clear` and
35
+ `loading` sat on no single component, so they had no owning label token and
36
+ stayed per-instance inputs. A non-English app therefore repeated the same
37
+ translation at every call site — 25 `optionalLabel="facultatif"` across 9 files
38
+ in the app that prompted this — and a forgotten one shipped a lone English word
39
+ into a translated screen. Precedence is the package's usual: English default ←
40
+ app-wide provider ← the component's own input.
41
+ - **`sticky` on `fold-tabs` / `fold-view-nav`.** A bar heading a long view was
42
+ pinned by hand at every call site — `position: sticky; top: 0; z-index: 2` on
43
+ the host, twice in the same app. Pair it with the default
44
+ `background="surface"`; `transparent` would let the content scroll through.
45
+ Bleeding the bar to the page's edges stays the page's business (it owns the
46
+ gutter).
47
+ - **`ariaLabel` on `fold-listbox`, `fold-select` and `fold-input`.** Twelve other
48
+ components have one; these three had no way to be **named without a visible
49
+ label**, so a toolbar filter reached assistive tech announced only by its own
50
+ value ("Week") — which says nothing about what it sets. A visible `label` is
51
+ still the better answer, and setting both is a mistake (`aria-label` wins and
52
+ the two drift).
53
+
54
+ ### Migrating
55
+
56
+ Four breaking changes, and the compiler points at every one of them.
57
+
58
+ 1. **Icon names.** Build fails on a name that isn't registered — which is the
59
+ point: those were rendering nothing. Either the name exists under fold's own
60
+ spelling (`bin` not `trash`, `reload` not `refresh`, `tag` not `label`,
61
+ `shopping-cart` not `cart`, `more-vertical` not `kebab`), or it is yours and
62
+ wants the `FoldCustomIcons` declaration below. **Overriding a built-in needs
63
+ nothing** — same name, your art.
64
+ 2. **Field labels.** Nothing to do unless you translated them. If you did, the
65
+ per-instance attributes still win, and `provideFoldCommonLabels()` now lets
66
+ you delete them.
67
+ 3. **Tab keys.** Nothing to do for an untyped caller (`K` defaults to `string`).
68
+ If your sections are a union, drop the narrowing you were doing by hand.
69
+ 4. **`readonly` arrays.** Strictly more permissive — nothing to do.
70
+
71
+ ### Changed
72
+
73
+ - **`BREAKING` `FoldIconName` no longer admits any string.** It was
74
+ `FoldBuiltinIconName | (string & {})` — autocomplete for the built-ins, but a
75
+ typo or a name that simply doesn't exist compiled, and surfaced only as a
76
+ `console.warn` plus a hole where the glyph should be. Six such holes were live
77
+ across three apps, and one inside this package.
78
+
79
+ It is now `FoldBuiltinIconName | keyof FoldCustomIcons`, where
80
+ **`FoldCustomIcons` is an empty interface you augment**:
81
+
82
+ ```ts
83
+ export const APP_ICONS = { "my-logo": "<svg …>" } as const;
84
+
85
+ declare module "fold-ng" {
86
+ interface FoldCustomIcons extends Record<keyof typeof APP_ICONS, true> {}
87
+ }
88
+
89
+ providers: [provideFoldIcons(APP_ICONS)];
90
+ ```
91
+
92
+ Deriving the declaration from the registered object (`keyof typeof`) is the
93
+ recommended shape: one list, so the names you declare and the art you register
94
+ cannot drift. **Overriding a built-in needs no declaration** — `bin` is already
95
+ a known name; re-register it with your own art and it type-checks as it stands.
96
+
97
+ - **`FoldIconRegistry.names()` and `FOLD_BUILTIN_ICON_CATEGORIES[].names` now
98
+ return `FoldIconName`s**, so tooling that browses the live catalogue can hand a
99
+ name straight back to `fold-icon`. Narrowed through a checked predicate, not an
100
+ assertion.
101
+ - **`BREAKING` `fold-tabs` is generic over its key.** `FoldTabsComponent<K>`,
102
+ `FoldTabItem<K>`, `FoldTabsContext<K>` and `FoldTabPanelComponent<K>` all carry
103
+ the caller's key type (defaulting to `string`, so untyped callers are
104
+ unaffected). A bar whose sections are a closed union now writes the key back
105
+ **already narrowed** — no `isTabKey`-style predicate at every call site — and a
106
+ `<fold-tab-panel key="typo">` outside the union fails to compile instead of
107
+ rendering a panel no tab can reach.
108
+ - **`BREAKING` `optionalLabel`, `infoLabel`, `clearLabel`, `fold-info`'s `label`
109
+ and `fold-loading`'s `message` widen to `string | undefined`.** They each held
110
+ a hard-coded English default, which is what made a provider impossible: an
111
+ unset input was indistinguishable from one deliberately set to the English
112
+ word. Unset now means "ask the token". Every call site passing a string is
113
+ unaffected.
114
+ - **`BREAKING` `tabs` / `items` accept `readonly` arrays.** They were the only
115
+ two array inputs in the package still demanding a mutable array, so a caller
116
+ holding a `readonly` list (the idiomatic shape for static data) had to widen it.
117
+ `fold-view-nav`'s `activeKey` stays `string` on purpose — it has a zero value
118
+ (`""`, "no item selected") that no caller's union contains, so a generic there
119
+ would hand the narrowing straight back.
120
+
121
+ ### Fixed
122
+
123
+ - **`fold-tabs`' JSDoc example projected into the wrong slot.** It wrote
124
+ `<fold-tabs nav …>`; `fold-nav-layout`'s bar slot is `[tabNav]`. Copying the
125
+ example silently dropped the bar into the default slot.
126
+
127
+ ## [0.10.3] - 2026-08-12
128
+
129
+ ### Changed
130
+
131
+ - **A `fold-nav-layout` hands its content a zero gutter, at every width.** The
132
+ bar is that column's header: it spans the layout's full width, so a gutter
133
+ under it insets the cards _relative to the menu that names them_ and the two
134
+ stop reading as one column — a desktop misalignment as much as a mobile one,
135
+ where it also costs room there isn't. The gutter belongs to the PAGE; a nav
136
+ layout's body is the inside of a column the page has already inset. Handed
137
+ down as `--fold-page-gutter: 0` on the body rather than selected as a child: a
138
+ custom property crosses view encapsulation, so it reaches a nested
139
+ `fold-page-layout` (or a `bleed` section, which stays in lockstep) without the
140
+ layout having to know what its content is. Vertical rhythm untouched, and a
141
+ body that wants its inset back sets the token on its own content.
142
+
143
+ _(Widens the 0.10.2 change, which stopped at 640px.)_
144
+
11
145
  ## [0.10.2] - 2026-08-12
12
146
 
13
147
  ### Changed
@@ -1415,7 +1549,9 @@ design-token stylesheet.
1415
1549
  `currentColor`; `prefers-reduced-motion` + `forced-colors` are respected;
1416
1550
  strings localise via inputs / providers (`provideFoldPanelLabels`).
1417
1551
 
1418
- [unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.10.2...HEAD
1552
+ [unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.11.0...HEAD
1553
+ [0.11.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.11.0
1554
+ [0.10.3]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.10.3
1419
1555
  [0.10.2]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.10.2
1420
1556
  [0.10.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.10.1
1421
1557
  [0.10.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.10.0
@@ -98,7 +98,9 @@ class FoldIconDevtoolComponent {
98
98
  });
99
99
  }
100
100
  /** The icon selected into the playground (empty = none). */
101
- selected = signal("", /* @ts-ignore */
101
+ /** The icon under the playground — `null`, not `""`, for "none picked": an
102
+ * empty string is not a name, and the template already branches on it. */
103
+ selected = signal(null, /* @ts-ignore */
102
104
  ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
103
105
  /** Playground size preset. */
104
106
  size = signal("md", /* @ts-ignore */
@@ -1 +1 @@
1
- {"version":3,"file":"fold-ng-src-devtools.mjs","sources":["../../src/devtools/icon-devtool/icon-devtool.component.ts","../../src/devtools/icon-devtool/icon-devtool.component.html","../../src/devtools/public-api.ts","../../src/devtools/fold-ng-src-devtools.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n inject,\n output,\n signal,\n} from \"@angular/core\";\n// Cross-entry imports resolve through the package name, not a relative path\n// into `../../components`: `fold-ng/devtools` is its own ng-packagr entry point\n// (own rootDir), so it depends on the primary `fold-ng` entry the same way any\n// consumer would. The monorepo's vite/vitest alias `fold-ng` → `src/public-api`.\nimport {\n FOLD_BUILTIN_ICON_CATEGORIES,\n FoldButtonIconComponent,\n FoldIconComponent,\n FoldIconRegistry,\n} from \"fold-ng\";\n\n/** A draggable panel position, in viewport pixels (top-left origin). `null`\n * until the first drag — the panel sits in its default corner via CSS. */\ninterface DevtoolPos {\n readonly x: number;\n readonly y: number;\n}\n\nconst SIZE_STEPS = [\"xs\", \"sm\", \"md\", \"lg\", \"xl\"] as const;\ntype IconSize = (typeof SIZE_STEPS)[number];\n\n/** Bucket id for registry icons that belong to no built-in category. */\nconst CUSTOM_CATEGORY_ID = \"custom\";\n\n/** A category section rendered in the grid: its label, count, and members. */\ninterface IconCategory {\n readonly id: string;\n readonly label: string;\n readonly names: readonly string[];\n}\n\n/** name → built-in category id, for bucketing the live registry by theme. */\nconst CATEGORY_OF_NAME = new Map<string, string>(\n FOLD_BUILTIN_ICON_CATEGORIES.flatMap((category) =>\n category.names.map((name) => [name, category.id] as const),\n ),\n);\n\n/**\n * `<fold-icon-devtool>` — a **dev-only** floating panel that browses the live\n * {@link FoldIconRegistry} (built-ins + whatever the host app has registered),\n * with a search, a preview grid, and a mini playground that builds a\n * `<fold-icon>` snippet and copies it to the clipboard. The panel is **draggable**\n * (grab the header) and **minimisable** (collapses to a pill).\n *\n * **Ship it behind a dev guard.** It is published from the opt-in `fold-ng/devtools`\n * entry so it never lands in a bundle that doesn't ask for it; import it only on a\n * dev path so a production build tree-shakes it away:\n *\n * ```ts\n * import { isDevMode, signal } from \"@angular/core\";\n * protected readonly showIconTool = signal(false);\n * // template: @if (showIconTool()) { <fold-icon-devtool (closed)=\"showIconTool.set(false)\" /> }\n * // …and only reach the import behind `if (isDevMode())`.\n * ```\n *\n * @selector `fold-icon-devtool`\n */\n@Component({\n selector: \"fold-icon-devtool\",\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [FoldIconComponent, FoldButtonIconComponent],\n templateUrl: \"./icon-devtool.component.html\",\n styleUrl: \"./icon-devtool.component.scss\",\n host: {\n role: \"dialog\",\n \"aria-label\": \"Icon devtool\",\n \"[class.minimized]\": \"minimized()\",\n \"[style.left.px]\": \"pos()?.x ?? null\",\n \"[style.top.px]\": \"pos()?.y ?? null\",\n \"[class.docked]\": \"pos() === null\",\n },\n})\nexport class FoldIconDevtoolComponent {\n private readonly registry = inject(FoldIconRegistry);\n\n /** Emitted when the user closes the panel — the host should stop rendering it. */\n readonly closed = output();\n\n protected readonly sizeSteps = SIZE_STEPS;\n\n /** Collapsed to a pill (header only). */\n protected readonly minimized = signal(false);\n /** Free-drag position; `null` = docked in the default corner (CSS). */\n protected readonly pos = signal<DevtoolPos | null>(null);\n\n /** The live registry names (built-ins + host-registered), sorted. */\n private readonly allNames = computed(() => this.registry.names());\n\n protected readonly query = signal(\"\");\n /** Names matching the search (case-insensitive substring). */\n protected readonly matches = computed(() => {\n const q = this.query().trim().toLowerCase();\n const names = this.allNames();\n return q ? names.filter((n) => n.includes(q)) : names;\n });\n\n /** Category ids the user has collapsed (folded to their header). */\n private readonly collapsed = signal<ReadonlySet<string>>(new Set());\n\n /**\n * The matches grouped into category sections, in built-in order, with a\n * trailing **Custom** bucket for host-registered icons. Empty categories are\n * dropped so a search only shows the themes it actually hit.\n */\n protected readonly categories = computed<readonly IconCategory[]>(() => {\n const buckets = new Map<string, string[]>();\n for (const name of this.matches()) {\n const id = CATEGORY_OF_NAME.get(name) ?? CUSTOM_CATEGORY_ID;\n const bucket = buckets.get(id);\n if (bucket) {\n bucket.push(name);\n } else {\n buckets.set(id, [name]);\n }\n }\n const sections: IconCategory[] = [];\n for (const category of FOLD_BUILTIN_ICON_CATEGORIES) {\n const names = buckets.get(category.id);\n if (names) {\n sections.push({ id: category.id, label: category.label, names });\n }\n }\n const custom = buckets.get(CUSTOM_CATEGORY_ID);\n if (custom) {\n sections.push({ id: CUSTOM_CATEGORY_ID, label: \"Custom\", names: custom });\n }\n return sections;\n });\n\n /** Whether a category section is folded to its header. */\n protected isCollapsed(id: string): boolean {\n return this.collapsed().has(id);\n }\n\n protected toggleCategory(id: string): void {\n this.collapsed.update((set) => {\n const next = new Set(set);\n if (!next.delete(id)) {\n next.add(id);\n }\n return next;\n });\n }\n\n /** The icon selected into the playground (empty = none). */\n protected readonly selected = signal(\"\");\n /** Playground size preset. */\n protected readonly size = signal<IconSize>(\"md\");\n\n /** The snippet the copy button writes — size only emitted when non-default. */\n protected readonly snippet = computed(() => {\n const name = this.selected();\n if (!name) {\n return \"\";\n }\n const size = this.size();\n return size === \"md\"\n ? `<fold-icon name=\"${name}\" />`\n : `<fold-icon name=\"${name}\" size=\"${size}\" />`;\n });\n\n /** Flashes true briefly after a successful copy. */\n protected readonly copied = signal(false);\n\n protected select(name: string): void {\n this.selected.set(name);\n }\n\n protected setSize(size: IconSize): void {\n this.size.set(size);\n }\n\n protected onSearch(event: Event): void {\n const target = event.target;\n if (target instanceof HTMLInputElement) {\n this.query.set(target.value);\n }\n }\n\n protected copy(): void {\n const snippet = this.snippet();\n if (!snippet) {\n return;\n }\n void navigator.clipboard.writeText(snippet).then(() => {\n this.copied.set(true);\n setTimeout(() => this.copied.set(false), 1200);\n });\n }\n\n protected toggleMinimize(): void {\n this.minimized.update((m) => !m);\n }\n\n protected close(): void {\n this.closed.emit();\n }\n\n // ── Drag (grab the header) ────────────────────────────────────────────\n private dragDx = 0;\n private dragDy = 0;\n\n protected onDragStart(event: PointerEvent): void {\n const target = event.target;\n const handle = event.currentTarget;\n if (!(target instanceof HTMLElement) || !(handle instanceof HTMLElement)) {\n return;\n }\n // Ignore drags that start on an interactive header control.\n if (target.closest(\"button\")) {\n return;\n }\n const host = handle.closest(\"fold-icon-devtool\");\n if (!host) {\n return;\n }\n const rect = host.getBoundingClientRect();\n this.dragDx = event.clientX - rect.left;\n this.dragDy = event.clientY - rect.top;\n handle.setPointerCapture(event.pointerId);\n event.preventDefault();\n }\n\n protected onDragMove(event: PointerEvent): void {\n const handle = event.currentTarget;\n if (\n !(handle instanceof HTMLElement) ||\n !handle.hasPointerCapture(event.pointerId)\n ) {\n return;\n }\n const width = window.innerWidth;\n const height = window.innerHeight;\n const x = Math.max(0, Math.min(event.clientX - this.dragDx, width - 40));\n const y = Math.max(0, Math.min(event.clientY - this.dragDy, height - 40));\n this.pos.set({ x, y });\n }\n\n protected onDragEnd(event: PointerEvent): void {\n const handle = event.currentTarget;\n if (\n handle instanceof HTMLElement &&\n handle.hasPointerCapture(event.pointerId)\n ) {\n handle.releasePointerCapture(event.pointerId);\n }\n }\n}\n","<header\n class=\"dt-head\"\n (pointerdown)=\"onDragStart($event)\"\n (pointermove)=\"onDragMove($event)\"\n (pointerup)=\"onDragEnd($event)\"\n (pointercancel)=\"onDragEnd($event)\"\n>\n <span class=\"dt-grip\" aria-hidden=\"true\"></span>\n <span class=\"dt-title\">\n <fold-icon name=\"grid\" size=\"sm\" aria-hidden=\"true\" />\n Icons\n <span class=\"dt-count\">{{ matches().length }}</span>\n </span>\n <span class=\"dt-actions\">\n <fold-button-icon\n [icon]=\"minimized() ? 'chevron-up' : 'minus'\"\n size=\"sm\"\n [tooltip]=\"minimized() ? 'Expand' : 'Minimise'\"\n (clicked)=\"toggleMinimize()\"\n />\n <fold-button-icon\n icon=\"close\"\n size=\"sm\"\n tooltip=\"Close\"\n (clicked)=\"close()\"\n />\n </span>\n</header>\n\n@if (!minimized()) {\n <div class=\"dt-body\">\n <div class=\"dt-search\">\n <fold-icon\n name=\"search\"\n size=\"sm\"\n class=\"dt-search-ico\"\n aria-hidden=\"true\"\n />\n <input\n type=\"search\"\n class=\"dt-search-input\"\n placeholder=\"Search icons…\"\n aria-label=\"Search icons\"\n [value]=\"query()\"\n (input)=\"onSearch($event)\"\n />\n </div>\n\n <div class=\"dt-cats\">\n @for (cat of categories(); track cat.id) {\n <section class=\"dt-cat\">\n <button\n type=\"button\"\n class=\"dt-cat-head\"\n [attr.aria-expanded]=\"!isCollapsed(cat.id)\"\n (click)=\"toggleCategory(cat.id)\"\n >\n <fold-icon\n [name]=\"isCollapsed(cat.id) ? 'chevron-right' : 'chevron-down'\"\n size=\"sm\"\n aria-hidden=\"true\"\n />\n <span class=\"dt-cat-label\">{{ cat.label }}</span>\n <span class=\"dt-cat-count\">{{ cat.names.length }}</span>\n </button>\n\n @if (!isCollapsed(cat.id)) {\n <div class=\"dt-grid\" role=\"listbox\" [attr.aria-label]=\"cat.label\">\n @for (name of cat.names; track name) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"dt-cell\"\n [class.is-selected]=\"selected() === name\"\n [attr.aria-selected]=\"selected() === name\"\n [title]=\"name\"\n (click)=\"select(name)\"\n >\n <fold-icon [name]=\"name\" size=\"md\" aria-hidden=\"true\" />\n </button>\n }\n </div>\n }\n </section>\n } @empty {\n <p class=\"dt-empty\">No icon matches “{{ query() }}”.</p>\n }\n </div>\n\n @if (selected(); as name) {\n <div class=\"dt-play\">\n <div class=\"dt-preview\">\n <fold-icon [name]=\"name\" [size]=\"size()\" />\n <code class=\"dt-name\">{{ name }}</code>\n </div>\n <div class=\"dt-controls\">\n <div class=\"dt-sizes\" role=\"group\" aria-label=\"Preview size\">\n @for (s of sizeSteps; track s) {\n <button\n type=\"button\"\n class=\"dt-size\"\n [class.is-on]=\"size() === s\"\n (click)=\"setSize(s)\"\n >\n {{ s }}\n </button>\n }\n </div>\n <div class=\"dt-snippet\">\n <code>{{ snippet() }}</code>\n <button\n type=\"button\"\n class=\"dt-copy\"\n [class.is-copied]=\"copied()\"\n (click)=\"copy()\"\n >\n <fold-icon\n [name]=\"copied() ? 'check' : 'copy'\"\n size=\"sm\"\n aria-hidden=\"true\"\n />\n {{ copied() ? \"Copied\" : \"Copy\" }}\n </button>\n </div>\n </div>\n </div>\n } @else {\n <p class=\"dt-hint\">\n Pick an icon to build a <code>&lt;fold-icon&gt;</code> snippet.\n </p>\n }\n </div>\n}\n","// Opt-in dev-tools entry — `import { … } from \"fold-ng/devtools\"`.\n//\n// Everything here is meant for a DEV build only. Reach the import behind a dev\n// guard (`if (isDevMode())` + a dynamic `import(\"fold-ng/devtools\")`, or a\n// dev-only route/flag) so a production bundle tree-shakes it away — these tools\n// are never part of the shipped UI.\nexport { FoldIconDevtoolComponent } from \"./icon-devtool/icon-devtool.component\";\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fold-ng-src-devtools.mjs","sources":["../../src/devtools/icon-devtool/icon-devtool.component.ts","../../src/devtools/icon-devtool/icon-devtool.component.html","../../src/devtools/public-api.ts","../../src/devtools/fold-ng-src-devtools.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n inject,\n output,\n signal,\n} from \"@angular/core\";\n// Cross-entry imports resolve through the package name, not a relative path\n// into `../../components`: `fold-ng/devtools` is its own ng-packagr entry point\n// (own rootDir), so it depends on the primary `fold-ng` entry the same way any\n// consumer would. The monorepo's vite/vitest alias `fold-ng` → `src/public-api`.\nimport type { FoldIconName } from \"fold-ng\";\nimport {\n FOLD_BUILTIN_ICON_CATEGORIES,\n FoldButtonIconComponent,\n FoldIconComponent,\n FoldIconRegistry,\n} from \"fold-ng\";\n\n/** A draggable panel position, in viewport pixels (top-left origin). `null`\n * until the first drag — the panel sits in its default corner via CSS. */\ninterface DevtoolPos {\n readonly x: number;\n readonly y: number;\n}\n\nconst SIZE_STEPS = [\"xs\", \"sm\", \"md\", \"lg\", \"xl\"] as const;\ntype IconSize = (typeof SIZE_STEPS)[number];\n\n/** Bucket id for registry icons that belong to no built-in category. */\nconst CUSTOM_CATEGORY_ID = \"custom\";\n\n/** A category section rendered in the grid: its label, count, and members. */\ninterface IconCategory {\n readonly id: string;\n readonly label: string;\n readonly names: readonly FoldIconName[];\n}\n\n/** name → built-in category id, for bucketing the live registry by theme. */\nconst CATEGORY_OF_NAME = new Map<string, string>(\n FOLD_BUILTIN_ICON_CATEGORIES.flatMap((category) =>\n category.names.map((name) => [name, category.id] as const),\n ),\n);\n\n/**\n * `<fold-icon-devtool>` — a **dev-only** floating panel that browses the live\n * {@link FoldIconRegistry} (built-ins + whatever the host app has registered),\n * with a search, a preview grid, and a mini playground that builds a\n * `<fold-icon>` snippet and copies it to the clipboard. The panel is **draggable**\n * (grab the header) and **minimisable** (collapses to a pill).\n *\n * **Ship it behind a dev guard.** It is published from the opt-in `fold-ng/devtools`\n * entry so it never lands in a bundle that doesn't ask for it; import it only on a\n * dev path so a production build tree-shakes it away:\n *\n * ```ts\n * import { isDevMode, signal } from \"@angular/core\";\n * protected readonly showIconTool = signal(false);\n * // template: @if (showIconTool()) { <fold-icon-devtool (closed)=\"showIconTool.set(false)\" /> }\n * // …and only reach the import behind `if (isDevMode())`.\n * ```\n *\n * @selector `fold-icon-devtool`\n */\n@Component({\n selector: \"fold-icon-devtool\",\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [FoldIconComponent, FoldButtonIconComponent],\n templateUrl: \"./icon-devtool.component.html\",\n styleUrl: \"./icon-devtool.component.scss\",\n host: {\n role: \"dialog\",\n \"aria-label\": \"Icon devtool\",\n \"[class.minimized]\": \"minimized()\",\n \"[style.left.px]\": \"pos()?.x ?? null\",\n \"[style.top.px]\": \"pos()?.y ?? null\",\n \"[class.docked]\": \"pos() === null\",\n },\n})\nexport class FoldIconDevtoolComponent {\n private readonly registry = inject(FoldIconRegistry);\n\n /** Emitted when the user closes the panel — the host should stop rendering it. */\n readonly closed = output();\n\n protected readonly sizeSteps = SIZE_STEPS;\n\n /** Collapsed to a pill (header only). */\n protected readonly minimized = signal(false);\n /** Free-drag position; `null` = docked in the default corner (CSS). */\n protected readonly pos = signal<DevtoolPos | null>(null);\n\n /** The live registry names (built-ins + host-registered), sorted. */\n private readonly allNames = computed(() => this.registry.names());\n\n protected readonly query = signal(\"\");\n /** Names matching the search (case-insensitive substring). */\n protected readonly matches = computed(() => {\n const q = this.query().trim().toLowerCase();\n const names = this.allNames();\n return q ? names.filter((n) => n.includes(q)) : names;\n });\n\n /** Category ids the user has collapsed (folded to their header). */\n private readonly collapsed = signal<ReadonlySet<string>>(new Set());\n\n /**\n * The matches grouped into category sections, in built-in order, with a\n * trailing **Custom** bucket for host-registered icons. Empty categories are\n * dropped so a search only shows the themes it actually hit.\n */\n protected readonly categories = computed<readonly IconCategory[]>(() => {\n const buckets = new Map<string, FoldIconName[]>();\n for (const name of this.matches()) {\n const id = CATEGORY_OF_NAME.get(name) ?? CUSTOM_CATEGORY_ID;\n const bucket = buckets.get(id);\n if (bucket) {\n bucket.push(name);\n } else {\n buckets.set(id, [name]);\n }\n }\n const sections: IconCategory[] = [];\n for (const category of FOLD_BUILTIN_ICON_CATEGORIES) {\n const names = buckets.get(category.id);\n if (names) {\n sections.push({ id: category.id, label: category.label, names });\n }\n }\n const custom = buckets.get(CUSTOM_CATEGORY_ID);\n if (custom) {\n sections.push({ id: CUSTOM_CATEGORY_ID, label: \"Custom\", names: custom });\n }\n return sections;\n });\n\n /** Whether a category section is folded to its header. */\n protected isCollapsed(id: string): boolean {\n return this.collapsed().has(id);\n }\n\n protected toggleCategory(id: string): void {\n this.collapsed.update((set) => {\n const next = new Set(set);\n if (!next.delete(id)) {\n next.add(id);\n }\n return next;\n });\n }\n\n /** The icon selected into the playground (empty = none). */\n /** The icon under the playground — `null`, not `\"\"`, for \"none picked\": an\n * empty string is not a name, and the template already branches on it. */\n protected readonly selected = signal<FoldIconName | null>(null);\n /** Playground size preset. */\n protected readonly size = signal<IconSize>(\"md\");\n\n /** The snippet the copy button writes — size only emitted when non-default. */\n protected readonly snippet = computed(() => {\n const name = this.selected();\n if (!name) {\n return \"\";\n }\n const size = this.size();\n return size === \"md\"\n ? `<fold-icon name=\"${name}\" />`\n : `<fold-icon name=\"${name}\" size=\"${size}\" />`;\n });\n\n /** Flashes true briefly after a successful copy. */\n protected readonly copied = signal(false);\n\n protected select(name: FoldIconName): void {\n this.selected.set(name);\n }\n\n protected setSize(size: IconSize): void {\n this.size.set(size);\n }\n\n protected onSearch(event: Event): void {\n const target = event.target;\n if (target instanceof HTMLInputElement) {\n this.query.set(target.value);\n }\n }\n\n protected copy(): void {\n const snippet = this.snippet();\n if (!snippet) {\n return;\n }\n void navigator.clipboard.writeText(snippet).then(() => {\n this.copied.set(true);\n setTimeout(() => this.copied.set(false), 1200);\n });\n }\n\n protected toggleMinimize(): void {\n this.minimized.update((m) => !m);\n }\n\n protected close(): void {\n this.closed.emit();\n }\n\n // ── Drag (grab the header) ────────────────────────────────────────────\n private dragDx = 0;\n private dragDy = 0;\n\n protected onDragStart(event: PointerEvent): void {\n const target = event.target;\n const handle = event.currentTarget;\n if (!(target instanceof HTMLElement) || !(handle instanceof HTMLElement)) {\n return;\n }\n // Ignore drags that start on an interactive header control.\n if (target.closest(\"button\")) {\n return;\n }\n const host = handle.closest(\"fold-icon-devtool\");\n if (!host) {\n return;\n }\n const rect = host.getBoundingClientRect();\n this.dragDx = event.clientX - rect.left;\n this.dragDy = event.clientY - rect.top;\n handle.setPointerCapture(event.pointerId);\n event.preventDefault();\n }\n\n protected onDragMove(event: PointerEvent): void {\n const handle = event.currentTarget;\n if (\n !(handle instanceof HTMLElement) ||\n !handle.hasPointerCapture(event.pointerId)\n ) {\n return;\n }\n const width = window.innerWidth;\n const height = window.innerHeight;\n const x = Math.max(0, Math.min(event.clientX - this.dragDx, width - 40));\n const y = Math.max(0, Math.min(event.clientY - this.dragDy, height - 40));\n this.pos.set({ x, y });\n }\n\n protected onDragEnd(event: PointerEvent): void {\n const handle = event.currentTarget;\n if (\n handle instanceof HTMLElement &&\n handle.hasPointerCapture(event.pointerId)\n ) {\n handle.releasePointerCapture(event.pointerId);\n }\n }\n}\n","<header\n class=\"dt-head\"\n (pointerdown)=\"onDragStart($event)\"\n (pointermove)=\"onDragMove($event)\"\n (pointerup)=\"onDragEnd($event)\"\n (pointercancel)=\"onDragEnd($event)\"\n>\n <span class=\"dt-grip\" aria-hidden=\"true\"></span>\n <span class=\"dt-title\">\n <fold-icon name=\"grid\" size=\"sm\" aria-hidden=\"true\" />\n Icons\n <span class=\"dt-count\">{{ matches().length }}</span>\n </span>\n <span class=\"dt-actions\">\n <fold-button-icon\n [icon]=\"minimized() ? 'chevron-up' : 'minus'\"\n size=\"sm\"\n [tooltip]=\"minimized() ? 'Expand' : 'Minimise'\"\n (clicked)=\"toggleMinimize()\"\n />\n <fold-button-icon\n icon=\"close\"\n size=\"sm\"\n tooltip=\"Close\"\n (clicked)=\"close()\"\n />\n </span>\n</header>\n\n@if (!minimized()) {\n <div class=\"dt-body\">\n <div class=\"dt-search\">\n <fold-icon\n name=\"search\"\n size=\"sm\"\n class=\"dt-search-ico\"\n aria-hidden=\"true\"\n />\n <input\n type=\"search\"\n class=\"dt-search-input\"\n placeholder=\"Search icons…\"\n aria-label=\"Search icons\"\n [value]=\"query()\"\n (input)=\"onSearch($event)\"\n />\n </div>\n\n <div class=\"dt-cats\">\n @for (cat of categories(); track cat.id) {\n <section class=\"dt-cat\">\n <button\n type=\"button\"\n class=\"dt-cat-head\"\n [attr.aria-expanded]=\"!isCollapsed(cat.id)\"\n (click)=\"toggleCategory(cat.id)\"\n >\n <fold-icon\n [name]=\"isCollapsed(cat.id) ? 'chevron-right' : 'chevron-down'\"\n size=\"sm\"\n aria-hidden=\"true\"\n />\n <span class=\"dt-cat-label\">{{ cat.label }}</span>\n <span class=\"dt-cat-count\">{{ cat.names.length }}</span>\n </button>\n\n @if (!isCollapsed(cat.id)) {\n <div class=\"dt-grid\" role=\"listbox\" [attr.aria-label]=\"cat.label\">\n @for (name of cat.names; track name) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"dt-cell\"\n [class.is-selected]=\"selected() === name\"\n [attr.aria-selected]=\"selected() === name\"\n [title]=\"name\"\n (click)=\"select(name)\"\n >\n <fold-icon [name]=\"name\" size=\"md\" aria-hidden=\"true\" />\n </button>\n }\n </div>\n }\n </section>\n } @empty {\n <p class=\"dt-empty\">No icon matches “{{ query() }}”.</p>\n }\n </div>\n\n @if (selected(); as name) {\n <div class=\"dt-play\">\n <div class=\"dt-preview\">\n <fold-icon [name]=\"name\" [size]=\"size()\" />\n <code class=\"dt-name\">{{ name }}</code>\n </div>\n <div class=\"dt-controls\">\n <div class=\"dt-sizes\" role=\"group\" aria-label=\"Preview size\">\n @for (s of sizeSteps; track s) {\n <button\n type=\"button\"\n class=\"dt-size\"\n [class.is-on]=\"size() === s\"\n (click)=\"setSize(s)\"\n >\n {{ s }}\n </button>\n }\n </div>\n <div class=\"dt-snippet\">\n <code>{{ snippet() }}</code>\n <button\n type=\"button\"\n class=\"dt-copy\"\n [class.is-copied]=\"copied()\"\n (click)=\"copy()\"\n >\n <fold-icon\n [name]=\"copied() ? 'check' : 'copy'\"\n size=\"sm\"\n aria-hidden=\"true\"\n />\n {{ copied() ? \"Copied\" : \"Copy\" }}\n </button>\n </div>\n </div>\n </div>\n } @else {\n <p class=\"dt-hint\">\n Pick an icon to build a <code>&lt;fold-icon&gt;</code> snippet.\n </p>\n }\n </div>\n}\n","// Opt-in dev-tools entry — `import { … } from \"fold-ng/devtools\"`.\n//\n// Everything here is meant for a DEV build only. Reach the import behind a dev\n// guard (`if (isDevMode())` + a dynamic `import(\"fold-ng/devtools\")`, or a\n// dev-only route/flag) so a production bundle tree-shakes it away — these tools\n// are never part of the shipped UI.\nexport { FoldIconDevtoolComponent } from \"./icon-devtool/icon-devtool.component\";\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}