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 +137 -1
- package/fesm2022/fold-ng-src-devtools.mjs +3 -1
- package/fesm2022/fold-ng-src-devtools.mjs.map +1 -1
- package/fesm2022/fold-ng.mjs +292 -95
- package/fesm2022/fold-ng.mjs.map +1 -1
- package/package.json +1 -1
- package/tmp-esm2022/tsconfig.lib.tsbuildinfo +1 -1
- package/types/fold-ng-src-devtools.d.ts +7 -4
- package/types/fold-ng-src-devtools.d.ts.map +1 -1
- package/types/fold-ng.d.ts +274 -66
- package/types/fold-ng.d.ts.map +1 -1
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.
|
|
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
|
-
|
|
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><fold-icon></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><fold-icon></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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"}
|