@pro-laico/payload-icons 0.0.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.
Files changed (119) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +9 -0
  3. package/dist/cache/index.d.ts +9 -0
  4. package/dist/cache/index.d.ts.map +1 -0
  5. package/dist/cache/index.js +96 -0
  6. package/dist/cache/index.js.map +1 -0
  7. package/dist/collections/Icon.d.ts +14 -0
  8. package/dist/collections/Icon.d.ts.map +1 -0
  9. package/dist/collections/Icon.js +96 -0
  10. package/dist/collections/Icon.js.map +1 -0
  11. package/dist/collections/IconRequest.d.ts +30 -0
  12. package/dist/collections/IconRequest.d.ts.map +1 -0
  13. package/dist/collections/IconRequest.js +82 -0
  14. package/dist/collections/IconRequest.js.map +1 -0
  15. package/dist/collections/IconSet.d.ts +43 -0
  16. package/dist/collections/IconSet.d.ts.map +1 -0
  17. package/dist/collections/IconSet.js +168 -0
  18. package/dist/collections/IconSet.js.map +1 -0
  19. package/dist/components/Icon.d.ts +35 -0
  20. package/dist/components/Icon.d.ts.map +1 -0
  21. package/dist/components/Icon.js +56 -0
  22. package/dist/components/Icon.js.map +1 -0
  23. package/dist/components/admin/clearIconRequests.d.ts +14 -0
  24. package/dist/components/admin/clearIconRequests.d.ts.map +1 -0
  25. package/dist/components/admin/clearIconRequests.js +52 -0
  26. package/dist/components/admin/clearIconRequests.js.map +1 -0
  27. package/dist/components/admin/iconPreview.d.ts +7 -0
  28. package/dist/components/admin/iconPreview.d.ts.map +1 -0
  29. package/dist/components/admin/iconPreview.js +49 -0
  30. package/dist/components/admin/iconPreview.js.map +1 -0
  31. package/dist/components/admin/iconRowLabel.d.ts +8 -0
  32. package/dist/components/admin/iconRowLabel.d.ts.map +1 -0
  33. package/dist/components/admin/iconRowLabel.js +28 -0
  34. package/dist/components/admin/iconRowLabel.js.map +1 -0
  35. package/dist/components/admin/iconUsagePanel.client.d.ts +26 -0
  36. package/dist/components/admin/iconUsagePanel.client.d.ts.map +1 -0
  37. package/dist/components/admin/iconUsagePanel.client.js +290 -0
  38. package/dist/components/admin/iconUsagePanel.client.js.map +1 -0
  39. package/dist/components/admin/iconUsagePanel.d.ts +12 -0
  40. package/dist/components/admin/iconUsagePanel.d.ts.map +1 -0
  41. package/dist/components/admin/iconUsagePanel.js +68 -0
  42. package/dist/components/admin/iconUsagePanel.js.map +1 -0
  43. package/dist/hooks/formatSVG.d.ts +22 -0
  44. package/dist/hooks/formatSVG.d.ts.map +1 -0
  45. package/dist/hooks/formatSVG.js +270 -0
  46. package/dist/hooks/formatSVG.js.map +1 -0
  47. package/dist/index.d.ts +7 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +10 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/lib/activeField.d.ts +24 -0
  52. package/dist/lib/activeField.d.ts.map +1 -0
  53. package/dist/lib/activeField.js +71 -0
  54. package/dist/lib/activeField.js.map +1 -0
  55. package/dist/lib/authenticated.d.ts +5 -0
  56. package/dist/lib/authenticated.d.ts.map +1 -0
  57. package/dist/lib/authenticated.js +4 -0
  58. package/dist/lib/authenticated.js.map +1 -0
  59. package/dist/lib/defaultAccess.d.ts +8 -0
  60. package/dist/lib/defaultAccess.d.ts.map +1 -0
  61. package/dist/lib/defaultAccess.js +6 -0
  62. package/dist/lib/defaultAccess.js.map +1 -0
  63. package/dist/lib/extractSVG.d.ts +3 -0
  64. package/dist/lib/extractSVG.d.ts.map +1 -0
  65. package/dist/lib/extractSVG.js +27 -0
  66. package/dist/lib/extractSVG.js.map +1 -0
  67. package/dist/lib/getPayloadClient.d.ts +22 -0
  68. package/dist/lib/getPayloadClient.d.ts.map +1 -0
  69. package/dist/lib/getPayloadClient.js +39 -0
  70. package/dist/lib/getPayloadClient.js.map +1 -0
  71. package/dist/lib/mergeHooks.d.ts +8 -0
  72. package/dist/lib/mergeHooks.d.ts.map +1 -0
  73. package/dist/lib/mergeHooks.js +19 -0
  74. package/dist/lib/mergeHooks.js.map +1 -0
  75. package/dist/lib/titleCase.d.ts +7 -0
  76. package/dist/lib/titleCase.d.ts.map +1 -0
  77. package/dist/lib/titleCase.js +17 -0
  78. package/dist/lib/titleCase.js.map +1 -0
  79. package/dist/plugin.d.ts +91 -0
  80. package/dist/plugin.d.ts.map +1 -0
  81. package/dist/plugin.js +72 -0
  82. package/dist/plugin.js.map +1 -0
  83. package/dist/scan/cli.d.ts +14 -0
  84. package/dist/scan/cli.d.ts.map +1 -0
  85. package/dist/scan/cli.js +114 -0
  86. package/dist/scan/cli.js.map +1 -0
  87. package/dist/scan/extract.d.ts +58 -0
  88. package/dist/scan/extract.d.ts.map +1 -0
  89. package/dist/scan/extract.js +286 -0
  90. package/dist/scan/extract.js.map +1 -0
  91. package/dist/scan/index.d.ts +81 -0
  92. package/dist/scan/index.d.ts.map +1 -0
  93. package/dist/scan/index.js +133 -0
  94. package/dist/scan/index.js.map +1 -0
  95. package/dist/scan/live.d.ts +20 -0
  96. package/dist/scan/live.d.ts.map +1 -0
  97. package/dist/scan/live.js +109 -0
  98. package/dist/scan/live.js.map +1 -0
  99. package/dist/scan/load.d.ts +28 -0
  100. package/dist/scan/load.d.ts.map +1 -0
  101. package/dist/scan/load.js +37 -0
  102. package/dist/scan/load.js.map +1 -0
  103. package/dist/scan/types.d.ts +29 -0
  104. package/dist/scan/types.d.ts.map +1 -0
  105. package/dist/scan/types.js +8 -0
  106. package/dist/scan/types.js.map +1 -0
  107. package/dist/types.d.ts +38 -0
  108. package/dist/types.d.ts.map +1 -0
  109. package/dist/types.js +3 -0
  110. package/dist/types.js.map +1 -0
  111. package/dist/usage/recordMiss.d.ts +17 -0
  112. package/dist/usage/recordMiss.d.ts.map +1 -0
  113. package/dist/usage/recordMiss.js +60 -0
  114. package/dist/usage/recordMiss.js.map +1 -0
  115. package/dist/usage/trackIconMiss.d.ts +16 -0
  116. package/dist/usage/trackIconMiss.d.ts.map +1 -0
  117. package/dist/usage/trackIconMiss.js +43 -0
  118. package/dist/usage/trackIconMiss.js.map +1 -0
  119. package/package.json +106 -0
@@ -0,0 +1,8 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ type Hooks = NonNullable<CollectionConfig['hooks']>;
3
+ /** Append the user's collection hooks AFTER the built-ins, per phase. The built-ins always run
4
+ * first, so a user `beforeChange` hook sees the already-optimized SVG. Phases with no built-in
5
+ * just carry the user's hooks. */
6
+ export declare const mergeHooks: (base: Hooks, extra?: CollectionConfig["hooks"]) => Hooks;
7
+ export {};
8
+ //# sourceMappingURL=mergeHooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergeHooks.d.ts","sourceRoot":"","sources":["../../src/lib/mergeHooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,KAAK,KAAK,GAAG,WAAW,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAA;AAEnD;;mCAEmC;AACnC,eAAO,MAAM,UAAU,GAAI,MAAM,KAAK,EAAE,QAAQ,gBAAgB,CAAC,OAAO,CAAC,KAAG,KAS3E,CAAA"}
@@ -0,0 +1,19 @@
1
+ /** Append the user's collection hooks AFTER the built-ins, per phase. The built-ins always run
2
+ * first, so a user `beforeChange` hook sees the already-optimized SVG. Phases with no built-in
3
+ * just carry the user's hooks. */ export const mergeHooks = (base, extra)=>{
4
+ if (!extra) return base;
5
+ const out = {
6
+ ...base
7
+ };
8
+ for (const phase of Object.keys(extra)){
9
+ const basePhase = base[phase] ?? [];
10
+ const extraPhase = extra[phase] ?? [];
11
+ out[phase] = [
12
+ ...basePhase,
13
+ ...extraPhase
14
+ ];
15
+ }
16
+ return out;
17
+ };
18
+
19
+ //# sourceMappingURL=mergeHooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/mergeHooks.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\ntype Hooks = NonNullable<CollectionConfig['hooks']>\n\n/** Append the user's collection hooks AFTER the built-ins, per phase. The built-ins always run\n * first, so a user `beforeChange` hook sees the already-optimized SVG. Phases with no built-in\n * just carry the user's hooks. */\nexport const mergeHooks = (base: Hooks, extra?: CollectionConfig['hooks']): Hooks => {\n if (!extra) return base\n const out: Hooks = { ...base }\n for (const phase of Object.keys(extra) as Array<keyof Hooks>) {\n const basePhase = (base[phase] ?? []) as unknown[]\n const extraPhase = (extra[phase] ?? []) as unknown[]\n out[phase] = [...basePhase, ...extraPhase] as never\n }\n return out\n}\n"],"names":["mergeHooks","base","extra","out","phase","Object","keys","basePhase","extraPhase"],"mappings":"AAIA;;iCAEiC,GACjC,OAAO,MAAMA,aAAa,CAACC,MAAaC;IACtC,IAAI,CAACA,OAAO,OAAOD;IACnB,MAAME,MAAa;QAAE,GAAGF,IAAI;IAAC;IAC7B,KAAK,MAAMG,SAASC,OAAOC,IAAI,CAACJ,OAA8B;QAC5D,MAAMK,YAAaN,IAAI,CAACG,MAAM,IAAI,EAAE;QACpC,MAAMI,aAAcN,KAAK,CAACE,MAAM,IAAI,EAAE;QACtCD,GAAG,CAACC,MAAM,GAAG;eAAIG;eAAcC;SAAW;IAC5C;IACA,OAAOL;AACT,EAAC"}
@@ -0,0 +1,7 @@
1
+ /** Normalize a free-form icon name to kebab-case (`Arrow Right` / `arrowRight` -> `arrow-right`),
2
+ * so the name an editor types in an `iconsArray` row matches the name used in `<Icon name>`. */
3
+ export declare const toKebabCase: (input?: string | null) => string;
4
+ /** Humanize a kebab/camel/snake icon name into Title Case for admin labels
5
+ * (`arrow-right` -> `Arrow Right`). Pure string work, no deps. */
6
+ export declare const toTitleCase: (input?: string | null) => string;
7
+ //# sourceMappingURL=titleCase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"titleCase.d.ts","sourceRoot":"","sources":["../../src/lib/titleCase.ts"],"names":[],"mappings":"AAAA;iGACiG;AACjG,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,GAAG,IAAI,KAAG,MASnD,CAAA;AAED;mEACmE;AACnE,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,GAAG,IAAI,KAAG,MAenD,CAAA"}
@@ -0,0 +1,17 @@
1
+ /** Normalize a free-form icon name to kebab-case (`Arrow Right` / `arrowRight` -> `arrow-right`),
2
+ * so the name an editor types in an `iconsArray` row matches the name used in `<Icon name>`. */ export const toKebabCase = (input)=>{
3
+ if (!input) return '';
4
+ return input.normalize('NFKD').replace(/[̀-ͯ]/g, '').replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/[^A-Za-z0-9]+/g, '-').replace(/^-+|-+$/g, '').toLowerCase();
5
+ };
6
+ /** Humanize a kebab/camel/snake icon name into Title Case for admin labels
7
+ * (`arrow-right` -> `Arrow Right`). Pure string work, no deps. */ export const toTitleCase = (input)=>{
8
+ if (!input) return '';
9
+ // Strip combining diacritical marks (U+0300-U+036F) left after NFKD decomposition.
10
+ const stripped = input.normalize('NFKD').replace(/[̀-ͯ]/g, '');
11
+ const spaced = stripped.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z0-9]+)/g, '$1 $2').replace(/[-_]+/g, ' ').replace(/[.\s]+/g, ' ');
12
+ const words = spaced.split(/\s+/).filter((w)=>w.length > 0);
13
+ if (words.length === 0) return '';
14
+ return words.map((w)=>/^[A-Z]+$/.test(w) ? w : /^[A-Z][a-z]+$/.test(w) ? w : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ').trim();
15
+ };
16
+
17
+ //# sourceMappingURL=titleCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/titleCase.ts"],"sourcesContent":["/** Normalize a free-form icon name to kebab-case (`Arrow Right` / `arrowRight` -> `arrow-right`),\n * so the name an editor types in an `iconsArray` row matches the name used in `<Icon name>`. */\nexport const toKebabCase = (input?: string | null): string => {\n if (!input) return ''\n return input\n .normalize('NFKD')\n .replace(/[̀-ͯ]/g, '')\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/[^A-Za-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .toLowerCase()\n}\n\n/** Humanize a kebab/camel/snake icon name into Title Case for admin labels\n * (`arrow-right` -> `Arrow Right`). Pure string work, no deps. */\nexport const toTitleCase = (input?: string | null): string => {\n if (!input) return ''\n // Strip combining diacritical marks (U+0300-U+036F) left after NFKD decomposition.\n const stripped = input.normalize('NFKD').replace(/[̀-ͯ]/g, '')\n const spaced = stripped\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z0-9]+)/g, '$1 $2')\n .replace(/[-_]+/g, ' ')\n .replace(/[.\\s]+/g, ' ')\n const words = spaced.split(/\\s+/).filter((w) => w.length > 0)\n if (words.length === 0) return ''\n return words\n .map((w) => (/^[A-Z]+$/.test(w) ? w : /^[A-Z][a-z]+$/.test(w) ? w : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()))\n .join(' ')\n .trim()\n}\n"],"names":["toKebabCase","input","normalize","replace","toLowerCase","toTitleCase","stripped","spaced","words","split","filter","w","length","map","test","charAt","toUpperCase","slice","join","trim"],"mappings":"AAAA;+FAC+F,GAC/F,OAAO,MAAMA,cAAc,CAACC;IAC1B,IAAI,CAACA,OAAO,OAAO;IACnB,OAAOA,MACJC,SAAS,CAAC,QACVC,OAAO,CAAC,UAAU,IAClBA,OAAO,CAAC,sBAAsB,SAC9BA,OAAO,CAAC,kBAAkB,KAC1BA,OAAO,CAAC,YAAY,IACpBC,WAAW;AAChB,EAAC;AAED;iEACiE,GACjE,OAAO,MAAMC,cAAc,CAACJ;IAC1B,IAAI,CAACA,OAAO,OAAO;IACnB,mFAAmF;IACnF,MAAMK,WAAWL,MAAMC,SAAS,CAAC,QAAQC,OAAO,CAAC,UAAU;IAC3D,MAAMI,SAASD,SACZH,OAAO,CAAC,sBAAsB,SAC9BA,OAAO,CAAC,6BAA6B,SACrCA,OAAO,CAAC,UAAU,KAClBA,OAAO,CAAC,WAAW;IACtB,MAAMK,QAAQD,OAAOE,KAAK,CAAC,OAAOC,MAAM,CAAC,CAACC,IAAMA,EAAEC,MAAM,GAAG;IAC3D,IAAIJ,MAAMI,MAAM,KAAK,GAAG,OAAO;IAC/B,OAAOJ,MACJK,GAAG,CAAC,CAACF,IAAO,WAAWG,IAAI,CAACH,KAAKA,IAAI,gBAAgBG,IAAI,CAACH,KAAKA,IAAIA,EAAEI,MAAM,CAAC,GAAGC,WAAW,KAAKL,EAAEM,KAAK,CAAC,GAAGb,WAAW,IACrHc,IAAI,CAAC,KACLC,IAAI;AACT,EAAC"}
@@ -0,0 +1,91 @@
1
+ import type { Plugin } from 'payload';
2
+ import { type IconRequestCollectionOverrides } from './collections/IconRequest';
3
+ import { type IconSetCollectionOverrides } from './collections/IconSet';
4
+ import type { IconCollectionOverrides } from './types';
5
+ /**
6
+ * Options for {@link iconsPlugin}. Composes the `icon` upload collection, the
7
+ * `iconSet` grouping collection, and the `iconRequest` diagnostic collection.
8
+ * The usage panel and request tracking are on by default.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * iconsPlugin({
13
+ * iconOverrides: { fields: [{ name: 'note', type: 'text' }] },
14
+ * trackRequests: false, // omit the iconRequest collection
15
+ * })
16
+ * ```
17
+ */
18
+ export interface IconsPluginOptions {
19
+ /**
20
+ * When `false`, the plugin is a no-op — no collections are registered. Useful
21
+ * behind a feature flag. @default true
22
+ */
23
+ enabled?: boolean;
24
+ /**
25
+ * Overrides for the `icon` upload collection — replaces slug, adminGroup, access,
26
+ * and upload config, and extends `fields` / `hooks`. See {@link IconCollectionOverrides}.
27
+ */
28
+ iconOverrides?: IconCollectionOverrides;
29
+ /**
30
+ * When `false`, the `iconSet` collection is not registered (only `icon`). Use
31
+ * when you want icons in the CMS but not the grouping/active-set concept.
32
+ * @default true
33
+ */
34
+ includeIconSet?: boolean;
35
+ /**
36
+ * Overrides for the `iconSet` collection — live preview, additive hooks,
37
+ * set-level fields, per-row fields, a drafts toggle. See
38
+ * {@link IconSetCollectionOverrides}.
39
+ */
40
+ iconSetOverrides?: IconSetCollectionOverrides;
41
+ /**
42
+ * The IconSet "Requested icons" panel — shows which icons your code needs
43
+ * versus what a set provides (scanned live in dev, from the manifest in prod,
44
+ * plus runtime misses when {@link trackRequests}). Set `false` to omit it.
45
+ * @default true
46
+ */
47
+ usagePanel?: boolean;
48
+ /**
49
+ * Registers the `iconRequest` diagnostic collection AND makes the `<Icon>`
50
+ * server component record every name that fails to resolve at runtime —
51
+ * throttled, deferred via `after()`, fire-and-forget. These misses (including
52
+ * dynamic `name={…}` ones a static scan can't see) surface in the usage panel.
53
+ * Set `false` to omit it, or force-disable only the recorder at runtime with
54
+ * `ICON_USAGE_TRACKING=false`.
55
+ * @default true
56
+ */
57
+ trackRequests?: boolean;
58
+ /**
59
+ * Overrides for the `iconRequest` collection. Only meaningful when
60
+ * {@link trackRequests} isn't `false`.
61
+ */
62
+ iconRequestOverrides?: IconRequestCollectionOverrides;
63
+ }
64
+ /**
65
+ * Payload plugin that contributes the `icon` upload collection and (by default)
66
+ * the `iconSet` grouping collection.
67
+ *
68
+ * - **`icon`** — single-SVG uploads, optimized + sanitized + themed on save
69
+ * (`formatSVGHook`), stored as an inline `svgString`.
70
+ * - **`iconSet`** — named `name → icon` mappings with a single-active toggle and
71
+ * drafts/versions. The frontend renders the active set, so activating a
72
+ * different set re-skins every icon at once.
73
+ * - **`iconRequest`** (on by default; {@link IconsPluginOptions.trackRequests} `false` to omit) —
74
+ * runtime miss diagnostics surfaced in the IconSet usage panel.
75
+ *
76
+ * Self-contained: no `@pro-laico/core` / `@pro-laico/atomic` dependency. The
77
+ * single-active invariant is a small status-lane-scoped `beforeChange` hook.
78
+ * Revalidation is left to the consumer (wire `revalidatePath` / `revalidateTag`
79
+ * via `iconSetOverrides.hooks`).
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * import { buildConfig } from 'payload'
84
+ * import { iconsPlugin } from '@pro-laico/payload-icons'
85
+ *
86
+ * export default buildConfig({ plugins: [iconsPlugin()] })
87
+ * ```
88
+ */
89
+ export declare const iconsPlugin: (opts?: IconsPluginOptions) => Plugin;
90
+ export default iconsPlugin;
91
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA4B,MAAM,EAAE,MAAM,SAAS,CAAA;AAG/D,OAAO,EAAkD,KAAK,8BAA8B,EAAE,MAAM,2BAA2B,CAAA;AAC/H,OAAO,EAA0C,KAAK,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AAE/G,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;AAEtD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;OAGG;IACH,aAAa,CAAC,EAAE,uBAAuB,CAAA;IACvC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,0BAA0B,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,8BAA8B,CAAA;CACtD;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,WAAW,GACrB,OAAM,kBAAuB,KAAG,MA2ChC,CAAA;AAEH,eAAe,WAAW,CAAA"}
package/dist/plugin.js ADDED
@@ -0,0 +1,72 @@
1
+ import { Icon } from "./collections/Icon.js";
2
+ import { createIconRequestCollection, ICON_REQUEST_SLUG } from "./collections/IconRequest.js";
3
+ import { createIconSetCollection, ICON_SET_SLUG } from "./collections/IconSet.js";
4
+ import { stashConfig, stashIconSetSlug } from "./lib/getPayloadClient.js";
5
+ /**
6
+ * Payload plugin that contributes the `icon` upload collection and (by default)
7
+ * the `iconSet` grouping collection.
8
+ *
9
+ * - **`icon`** — single-SVG uploads, optimized + sanitized + themed on save
10
+ * (`formatSVGHook`), stored as an inline `svgString`.
11
+ * - **`iconSet`** — named `name → icon` mappings with a single-active toggle and
12
+ * drafts/versions. The frontend renders the active set, so activating a
13
+ * different set re-skins every icon at once.
14
+ * - **`iconRequest`** (on by default; {@link IconsPluginOptions.trackRequests} `false` to omit) —
15
+ * runtime miss diagnostics surfaced in the IconSet usage panel.
16
+ *
17
+ * Self-contained: no `@pro-laico/core` / `@pro-laico/atomic` dependency. The
18
+ * single-active invariant is a small status-lane-scoped `beforeChange` hook.
19
+ * Revalidation is left to the consumer (wire `revalidatePath` / `revalidateTag`
20
+ * via `iconSetOverrides.hooks`).
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { buildConfig } from 'payload'
25
+ * import { iconsPlugin } from '@pro-laico/payload-icons'
26
+ *
27
+ * export default buildConfig({ plugins: [iconsPlugin()] })
28
+ * ```
29
+ */ export const iconsPlugin = (opts = {})=>(config)=>{
30
+ const { enabled = true, iconOverrides, includeIconSet = true, iconSetOverrides, usagePanel = true, trackRequests = true, iconRequestOverrides } = opts;
31
+ if (!enabled) return config;
32
+ // Stash the resolved iconSet slug at config-build time so the cache resolver (`./cache`)
33
+ // honors `iconSetOverrides.slug` — any process that loads the config runs this.
34
+ stashIconSetSlug(iconSetOverrides?.slug ?? ICON_SET_SLUG);
35
+ const additions = [
36
+ Icon(iconOverrides)
37
+ ];
38
+ if (includeIconSet) additions.push(createIconSetCollection({
39
+ iconSlug: iconOverrides?.slug ?? 'icon',
40
+ usagePanel,
41
+ ...iconSetOverrides
42
+ }));
43
+ if (trackRequests) additions.push(createIconRequestCollection(iconRequestOverrides));
44
+ return {
45
+ ...config,
46
+ collections: [
47
+ ...config.collections ?? [],
48
+ ...additions
49
+ ],
50
+ // Stash the resolved slugs so decoupled tooling (e.g. @pro-laico/payload-dev-tools) can
51
+ // discover the plugin and read them from just `payload.config` — no import.
52
+ custom: {
53
+ ...config.custom,
54
+ payloadIcons: {
55
+ options: opts,
56
+ iconSlug: iconOverrides?.slug ?? 'icon',
57
+ iconSetSlug: includeIconSet ? iconSetOverrides?.slug ?? ICON_SET_SLUG : null,
58
+ iconRequestSlug: trackRequests ? ICON_REQUEST_SLUG : null
59
+ }
60
+ },
61
+ onInit: async (payload)=>{
62
+ await config.onInit?.(payload);
63
+ // Remember the app's config so the server components (<Icon> et al) resolve it from
64
+ // globalThis — no `@payload-config` alias (and thus no transpilePackages) required
65
+ // once Payload has booted.
66
+ stashConfig(payload.config);
67
+ }
68
+ };
69
+ };
70
+ export default iconsPlugin;
71
+
72
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { CollectionConfig, Config, Plugin } from 'payload'\n\nimport { Icon } from './collections/Icon'\nimport { createIconRequestCollection, ICON_REQUEST_SLUG, type IconRequestCollectionOverrides } from './collections/IconRequest'\nimport { createIconSetCollection, ICON_SET_SLUG, type IconSetCollectionOverrides } from './collections/IconSet'\nimport { stashConfig, stashIconSetSlug } from './lib/getPayloadClient'\nimport type { IconCollectionOverrides } from './types'\n\n/**\n * Options for {@link iconsPlugin}. Composes the `icon` upload collection, the\n * `iconSet` grouping collection, and the `iconRequest` diagnostic collection.\n * The usage panel and request tracking are on by default.\n *\n * @example\n * ```ts\n * iconsPlugin({\n * iconOverrides: { fields: [{ name: 'note', type: 'text' }] },\n * trackRequests: false, // omit the iconRequest collection\n * })\n * ```\n */\nexport interface IconsPluginOptions {\n /**\n * When `false`, the plugin is a no-op — no collections are registered. Useful\n * behind a feature flag. @default true\n */\n enabled?: boolean\n /**\n * Overrides for the `icon` upload collection — replaces slug, adminGroup, access,\n * and upload config, and extends `fields` / `hooks`. See {@link IconCollectionOverrides}.\n */\n iconOverrides?: IconCollectionOverrides\n /**\n * When `false`, the `iconSet` collection is not registered (only `icon`). Use\n * when you want icons in the CMS but not the grouping/active-set concept.\n * @default true\n */\n includeIconSet?: boolean\n /**\n * Overrides for the `iconSet` collection — live preview, additive hooks,\n * set-level fields, per-row fields, a drafts toggle. See\n * {@link IconSetCollectionOverrides}.\n */\n iconSetOverrides?: IconSetCollectionOverrides\n /**\n * The IconSet \"Requested icons\" panel — shows which icons your code needs\n * versus what a set provides (scanned live in dev, from the manifest in prod,\n * plus runtime misses when {@link trackRequests}). Set `false` to omit it.\n * @default true\n */\n usagePanel?: boolean\n /**\n * Registers the `iconRequest` diagnostic collection AND makes the `<Icon>`\n * server component record every name that fails to resolve at runtime —\n * throttled, deferred via `after()`, fire-and-forget. These misses (including\n * dynamic `name={…}` ones a static scan can't see) surface in the usage panel.\n * Set `false` to omit it, or force-disable only the recorder at runtime with\n * `ICON_USAGE_TRACKING=false`.\n * @default true\n */\n trackRequests?: boolean\n /**\n * Overrides for the `iconRequest` collection. Only meaningful when\n * {@link trackRequests} isn't `false`.\n */\n iconRequestOverrides?: IconRequestCollectionOverrides\n}\n\n/**\n * Payload plugin that contributes the `icon` upload collection and (by default)\n * the `iconSet` grouping collection.\n *\n * - **`icon`** — single-SVG uploads, optimized + sanitized + themed on save\n * (`formatSVGHook`), stored as an inline `svgString`.\n * - **`iconSet`** — named `name → icon` mappings with a single-active toggle and\n * drafts/versions. The frontend renders the active set, so activating a\n * different set re-skins every icon at once.\n * - **`iconRequest`** (on by default; {@link IconsPluginOptions.trackRequests} `false` to omit) —\n * runtime miss diagnostics surfaced in the IconSet usage panel.\n *\n * Self-contained: no `@pro-laico/core` / `@pro-laico/atomic` dependency. The\n * single-active invariant is a small status-lane-scoped `beforeChange` hook.\n * Revalidation is left to the consumer (wire `revalidatePath` / `revalidateTag`\n * via `iconSetOverrides.hooks`).\n *\n * @example\n * ```ts\n * import { buildConfig } from 'payload'\n * import { iconsPlugin } from '@pro-laico/payload-icons'\n *\n * export default buildConfig({ plugins: [iconsPlugin()] })\n * ```\n */\nexport const iconsPlugin =\n (opts: IconsPluginOptions = {}): Plugin =>\n (config: Config): Config => {\n const {\n enabled = true,\n iconOverrides,\n includeIconSet = true,\n iconSetOverrides,\n usagePanel = true,\n trackRequests = true,\n iconRequestOverrides,\n } = opts\n if (!enabled) return config\n\n // Stash the resolved iconSet slug at config-build time so the cache resolver (`./cache`)\n // honors `iconSetOverrides.slug` — any process that loads the config runs this.\n stashIconSetSlug(iconSetOverrides?.slug ?? ICON_SET_SLUG)\n\n const additions: CollectionConfig[] = [Icon(iconOverrides)]\n if (includeIconSet) additions.push(createIconSetCollection({ iconSlug: iconOverrides?.slug ?? 'icon', usagePanel, ...iconSetOverrides }))\n if (trackRequests) additions.push(createIconRequestCollection(iconRequestOverrides))\n\n return {\n ...config,\n collections: [...(config.collections ?? []), ...additions],\n // Stash the resolved slugs so decoupled tooling (e.g. @pro-laico/payload-dev-tools) can\n // discover the plugin and read them from just `payload.config` — no import.\n custom: {\n ...config.custom,\n payloadIcons: {\n options: opts,\n iconSlug: iconOverrides?.slug ?? 'icon',\n iconSetSlug: includeIconSet ? (iconSetOverrides?.slug ?? ICON_SET_SLUG) : null,\n iconRequestSlug: trackRequests ? ICON_REQUEST_SLUG : null,\n },\n },\n onInit: async (payload) => {\n await config.onInit?.(payload)\n // Remember the app's config so the server components (<Icon> et al) resolve it from\n // globalThis — no `@payload-config` alias (and thus no transpilePackages) required\n // once Payload has booted.\n stashConfig(payload.config)\n },\n }\n }\n\nexport default iconsPlugin\n"],"names":["Icon","createIconRequestCollection","ICON_REQUEST_SLUG","createIconSetCollection","ICON_SET_SLUG","stashConfig","stashIconSetSlug","iconsPlugin","opts","config","enabled","iconOverrides","includeIconSet","iconSetOverrides","usagePanel","trackRequests","iconRequestOverrides","slug","additions","push","iconSlug","collections","custom","payloadIcons","options","iconSetSlug","iconRequestSlug","onInit","payload"],"mappings":"AAEA,SAASA,IAAI,QAAQ,wBAAoB;AACzC,SAASC,2BAA2B,EAAEC,iBAAiB,QAA6C,+BAA2B;AAC/H,SAASC,uBAAuB,EAAEC,aAAa,QAAyC,2BAAuB;AAC/G,SAASC,WAAW,EAAEC,gBAAgB,QAAQ,4BAAwB;AA+DtE;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,MAAMC,cACX,CAACC,OAA2B,CAAC,CAAC,GAC9B,CAACC;QACC,MAAM,EACJC,UAAU,IAAI,EACdC,aAAa,EACbC,iBAAiB,IAAI,EACrBC,gBAAgB,EAChBC,aAAa,IAAI,EACjBC,gBAAgB,IAAI,EACpBC,oBAAoB,EACrB,GAAGR;QACJ,IAAI,CAACE,SAAS,OAAOD;QAErB,yFAAyF;QACzF,gFAAgF;QAChFH,iBAAiBO,kBAAkBI,QAAQb;QAE3C,MAAMc,YAAgC;YAAClB,KAAKW;SAAe;QAC3D,IAAIC,gBAAgBM,UAAUC,IAAI,CAAChB,wBAAwB;YAAEiB,UAAUT,eAAeM,QAAQ;YAAQH;YAAY,GAAGD,gBAAgB;QAAC;QACtI,IAAIE,eAAeG,UAAUC,IAAI,CAAClB,4BAA4Be;QAE9D,OAAO;YACL,GAAGP,MAAM;YACTY,aAAa;mBAAKZ,OAAOY,WAAW,IAAI,EAAE;mBAAMH;aAAU;YAC1D,wFAAwF;YACxF,4EAA4E;YAC5EI,QAAQ;gBACN,GAAGb,OAAOa,MAAM;gBAChBC,cAAc;oBACZC,SAAShB;oBACTY,UAAUT,eAAeM,QAAQ;oBACjCQ,aAAab,iBAAkBC,kBAAkBI,QAAQb,gBAAiB;oBAC1EsB,iBAAiBX,gBAAgBb,oBAAoB;gBACvD;YACF;YACAyB,QAAQ,OAAOC;gBACb,MAAMnB,OAAOkB,MAAM,GAAGC;gBACtB,oFAAoF;gBACpF,mFAAmF;gBACnF,2BAA2B;gBAC3BvB,YAAYuB,QAAQnB,MAAM;YAC5B;QACF;IACF,EAAC;AAEH,eAAeF,YAAW"}
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `payload-icons-scan` — CLI wrapper around {@link scanIconUsages}. Run it in a
4
+ * project's build (e.g. a `prebuild` script or CI step) to regenerate the
5
+ * icon-usage manifest the admin "requested icons" panel reads.
6
+ *
7
+ * @example
8
+ * ```sh
9
+ * payload-icons-scan src app --out icon-usage-manifest.json
10
+ * payload-icons-scan --component Icon --component Glyph --ext tsx,jsx
11
+ * ```
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/scan/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `payload-icons-scan` — CLI wrapper around {@link scanIconUsages}. Run it in a
4
+ * project's build (e.g. a `prebuild` script or CI step) to regenerate the
5
+ * icon-usage manifest the admin "requested icons" panel reads.
6
+ *
7
+ * @example
8
+ * ```sh
9
+ * payload-icons-scan src app --out icon-usage-manifest.json
10
+ * payload-icons-scan --component Icon --component Glyph --ext tsx,jsx
11
+ * ```
12
+ */ import { DEFAULT_ROOTS, resolveManifestPath, scanIconUsages, writeIconUsageManifest } from "./index.js";
13
+ const HELP = `payload-icons-scan — scan source for literal <Icon name="…"> usages
14
+
15
+ Usage:
16
+ payload-icons-scan [roots...] [options]
17
+
18
+ Arguments:
19
+ roots Directories/files to scan (default: src app)
20
+
21
+ Options:
22
+ -o, --out <path> Manifest output path
23
+ (default: $ICON_USAGE_MANIFEST or icon-usage-manifest.json)
24
+ -c, --component <name> Component tag to treat as an icon (repeatable; default: Icon)
25
+ -e, --ext <list> Comma-separated extensions (default: tsx,jsx,ts,js,mdx)
26
+ -i, --ignore <list> Comma-separated dir names to skip
27
+ -h, --help Show this help
28
+ `;
29
+ /** Splits a repeated or comma-joined option value into trimmed, non-empty parts. */ const collect = (acc, raw)=>{
30
+ for (const part of raw.split(',')){
31
+ const v = part.trim();
32
+ if (v) acc.push(v);
33
+ }
34
+ };
35
+ const parseArgs = (argv)=>{
36
+ const parsed = {
37
+ roots: [],
38
+ out: undefined,
39
+ components: [],
40
+ extensions: [],
41
+ ignore: [],
42
+ help: false,
43
+ invalid: false
44
+ };
45
+ for(let i = 0; i < argv.length; i++){
46
+ const arg = argv[i];
47
+ if (arg === undefined) continue;
48
+ const next = ()=>argv[++i] ?? '';
49
+ switch(arg){
50
+ case '-h':
51
+ case '--help':
52
+ parsed.help = true;
53
+ break;
54
+ case '-o':
55
+ case '--out':
56
+ parsed.out = next();
57
+ break;
58
+ case '-c':
59
+ case '--component':
60
+ collect(parsed.components, next());
61
+ break;
62
+ case '-e':
63
+ case '--ext':
64
+ collect(parsed.extensions, next());
65
+ break;
66
+ case '-i':
67
+ case '--ignore':
68
+ collect(parsed.ignore, next());
69
+ break;
70
+ default:
71
+ if (arg.startsWith('-')) {
72
+ process.stderr.write(`Unknown option: ${arg}\n`);
73
+ parsed.invalid = true;
74
+ } else {
75
+ parsed.roots.push(arg);
76
+ }
77
+ }
78
+ }
79
+ return parsed;
80
+ };
81
+ const main = ()=>{
82
+ const args = parseArgs(process.argv.slice(2));
83
+ if (args.help) {
84
+ process.stdout.write(HELP);
85
+ return;
86
+ }
87
+ // Unknown options abort BEFORE scanning — a typo'd flag must not silently write a manifest.
88
+ if (args.invalid) {
89
+ process.stderr.write('payload-icons-scan: aborting (run with --help for usage)\n');
90
+ process.exitCode = 1;
91
+ return;
92
+ }
93
+ const roots = args.roots.length ? args.roots : DEFAULT_ROOTS;
94
+ const { manifest, filesScanned, rootsScanned } = scanIconUsages({
95
+ roots,
96
+ components: args.components.length ? args.components : undefined,
97
+ extensions: args.extensions.length ? args.extensions : undefined,
98
+ ignore: args.ignore.length ? args.ignore : undefined
99
+ });
100
+ // Every root missing means the scan ran in the wrong directory — fail loudly so CI catches it
101
+ // instead of shipping an empty manifest. Real roots with 0 usages still exit 0.
102
+ if (rootsScanned === 0) {
103
+ process.stderr.write(`payload-icons-scan: none of the scan roots exist: ${roots.join(', ')} (wrong directory?)\n`);
104
+ process.exitCode = 1;
105
+ return;
106
+ }
107
+ const outPath = resolveManifestPath(args.out);
108
+ const written = writeIconUsageManifest(manifest, outPath);
109
+ process.stdout.write(`payload-icons-scan: ${manifest.names.length} icon name(s) across ${manifest.usages.length} usage(s) in ${filesScanned} file(s)\n`);
110
+ process.stdout.write(`payload-icons-scan: wrote ${written}\n`);
111
+ };
112
+ main();
113
+
114
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/scan/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `payload-icons-scan` — CLI wrapper around {@link scanIconUsages}. Run it in a\n * project's build (e.g. a `prebuild` script or CI step) to regenerate the\n * icon-usage manifest the admin \"requested icons\" panel reads.\n *\n * @example\n * ```sh\n * payload-icons-scan src app --out icon-usage-manifest.json\n * payload-icons-scan --component Icon --component Glyph --ext tsx,jsx\n * ```\n */\n\nimport { DEFAULT_ROOTS, resolveManifestPath, scanIconUsages, writeIconUsageManifest } from './index.js'\n\ninterface ParsedArgs {\n roots: string[]\n out?: string\n components: string[]\n extensions: string[]\n ignore: string[]\n help: boolean\n invalid: boolean\n}\n\nconst HELP = `payload-icons-scan — scan source for literal <Icon name=\"…\"> usages\n\nUsage:\n payload-icons-scan [roots...] [options]\n\nArguments:\n roots Directories/files to scan (default: src app)\n\nOptions:\n -o, --out <path> Manifest output path\n (default: $ICON_USAGE_MANIFEST or icon-usage-manifest.json)\n -c, --component <name> Component tag to treat as an icon (repeatable; default: Icon)\n -e, --ext <list> Comma-separated extensions (default: tsx,jsx,ts,js,mdx)\n -i, --ignore <list> Comma-separated dir names to skip\n -h, --help Show this help\n`\n\n/** Splits a repeated or comma-joined option value into trimmed, non-empty parts. */\nconst collect = (acc: string[], raw: string): void => {\n for (const part of raw.split(',')) {\n const v = part.trim()\n if (v) acc.push(v)\n }\n}\n\nconst parseArgs = (argv: string[]): ParsedArgs => {\n const parsed: ParsedArgs = { roots: [], out: undefined, components: [], extensions: [], ignore: [], help: false, invalid: false }\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n if (arg === undefined) continue\n const next = (): string => argv[++i] ?? ''\n switch (arg) {\n case '-h':\n case '--help':\n parsed.help = true\n break\n case '-o':\n case '--out':\n parsed.out = next()\n break\n case '-c':\n case '--component':\n collect(parsed.components, next())\n break\n case '-e':\n case '--ext':\n collect(parsed.extensions, next())\n break\n case '-i':\n case '--ignore':\n collect(parsed.ignore, next())\n break\n default:\n if (arg.startsWith('-')) {\n process.stderr.write(`Unknown option: ${arg}\\n`)\n parsed.invalid = true\n } else {\n parsed.roots.push(arg)\n }\n }\n }\n return parsed\n}\n\nconst main = (): void => {\n const args = parseArgs(process.argv.slice(2))\n if (args.help) {\n process.stdout.write(HELP)\n return\n }\n // Unknown options abort BEFORE scanning — a typo'd flag must not silently write a manifest.\n if (args.invalid) {\n process.stderr.write('payload-icons-scan: aborting (run with --help for usage)\\n')\n process.exitCode = 1\n return\n }\n\n const roots = args.roots.length ? args.roots : DEFAULT_ROOTS\n const { manifest, filesScanned, rootsScanned } = scanIconUsages({\n roots,\n components: args.components.length ? args.components : undefined,\n extensions: args.extensions.length ? args.extensions : undefined,\n ignore: args.ignore.length ? args.ignore : undefined,\n })\n // Every root missing means the scan ran in the wrong directory — fail loudly so CI catches it\n // instead of shipping an empty manifest. Real roots with 0 usages still exit 0.\n if (rootsScanned === 0) {\n process.stderr.write(`payload-icons-scan: none of the scan roots exist: ${roots.join(', ')} (wrong directory?)\\n`)\n process.exitCode = 1\n return\n }\n\n const outPath = resolveManifestPath(args.out)\n const written = writeIconUsageManifest(manifest, outPath)\n process.stdout.write(\n `payload-icons-scan: ${manifest.names.length} icon name(s) across ${manifest.usages.length} usage(s) in ${filesScanned} file(s)\\n`,\n )\n process.stdout.write(`payload-icons-scan: wrote ${written}\\n`)\n}\n\nmain()\n"],"names":["DEFAULT_ROOTS","resolveManifestPath","scanIconUsages","writeIconUsageManifest","HELP","collect","acc","raw","part","split","v","trim","push","parseArgs","argv","parsed","roots","out","undefined","components","extensions","ignore","help","invalid","i","length","arg","next","startsWith","process","stderr","write","main","args","slice","stdout","exitCode","manifest","filesScanned","rootsScanned","join","outPath","written","names","usages"],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Pure, dependency-free extraction of literal icon names from one source file's
3
+ * text. This is the core of the build-time usage scan that powers the admin
4
+ * "requested icons" panel — kept side-effect-free (no `fs`) so it is trivially
5
+ * unit-testable and reusable from any walker.
6
+ *
7
+ * It is tag-aware, NOT a naive regex. The scanner walks the file skipping
8
+ * strings and comments, and for every `<Icon …>` opening tag it parses the
9
+ * attribute list, so a `>` inside an expression (`check={a > b}`) or a `name`
10
+ * substring inside a sibling attribute (`iconName`, `data-name`) never trips
11
+ * it.
12
+ *
13
+ * ONLY string-literal names are collected — the value must resolve statically:
14
+ * - `name="x"` / `name='x'` (direct attribute strings)
15
+ * - `name={"x"}` / `name={'x'}` (string literal in an expression slot)
16
+ * - `` name={`x`} `` (template literal, no interpolation)
17
+ * Dynamic `name={expr}` (variables, concatenation, interpolated templates) is
18
+ * intentionally skipped — a static scan cannot know its value.
19
+ */
20
+ /** A single literal `<Icon name="…">` occurrence found in a file. */
21
+ export interface ExtractedUsage {
22
+ /** The resolved literal icon name. */
23
+ name: string;
24
+ /** 1-based line of the `name` value. */
25
+ line: number;
26
+ /** 1-based column of the `name` value. */
27
+ column: number;
28
+ }
29
+ export interface ExtractOptions {
30
+ /**
31
+ * JSX tag names treated as icon usages. Matched exactly against the opening
32
+ * tag name, so `Icon` matches `<Icon …>` but not `<MyIcon …>`.
33
+ *
34
+ * @default ['Icon']
35
+ */
36
+ components?: string[];
37
+ }
38
+ /**
39
+ * Extracts every literal icon-name usage from a single file's source text.
40
+ * See the module doc for what counts as a literal. Returns occurrences in
41
+ * source order; de-duplication across files happens in the aggregator.
42
+ *
43
+ * The scan looks directly for `<Icon …>` tag starts and parses each opening tag
44
+ * with full string/brace awareness (so the `name` value and the tag's end are
45
+ * found correctly). It deliberately does NOT try to mask surrounding strings or
46
+ * comments: JSX text routinely contains apostrophes (`it's`) and `//` (`http://`)
47
+ * that a whole-file lexer would mis-read as a string/comment and skip a real
48
+ * usage past. Instead, two surgical guards keep commentary out without a lexer:
49
+ * a candidate whose line leads with `//` or `*` (line comments, JSDoc
50
+ * continuations — real JSX never starts a line that way) is skipped, and
51
+ * extracted names must look like icon names ({@link PLAUSIBLE_NAME}), so prose
52
+ * placeholders like `…` are dropped. An `<Icon name="…">` written literally in
53
+ * a JS string (or a mid-line comment) can still be picked up — a harmless
54
+ * over-count for an inventory, where missing a real usage would be the worse
55
+ * failure.
56
+ */
57
+ export declare const extractIconUsages: (source: string, options?: ExtractOptions) => ExtractedUsage[];
58
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/scan/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACtB;AAqND;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,EAAE,UAAS,cAAmB,KAAG,cAAc,EA0C9F,CAAA"}