@nice2dev/icons 1.3.15 → 1.3.16

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 (140) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/NiceDesktopIconExporter.js +688 -599
  3. package/dist/cjs/NiceFaviconGenerator.js +603 -417
  4. package/dist/cjs/NiceIconPicker.js +788 -508
  5. package/dist/cjs/NiceIconPreview.js +246 -188
  6. package/dist/cjs/NiceIconSetCreator.js +683 -552
  7. package/dist/cjs/NiceIconStacker.js +547 -171
  8. package/dist/cjs/NiceIconValue.js +27 -35
  9. package/dist/cjs/NtdIconBadge.js +384 -360
  10. package/dist/cjs/NtdIconMorph.js +419 -354
  11. package/dist/cjs/NtdStateMorph.js +150 -147
  12. package/dist/cjs/advancedIconSearch.js +373 -510
  13. package/dist/cjs/animationControls.js +518 -642
  14. package/dist/cjs/client.js +12 -15
  15. package/dist/cjs/createIcon.js +349 -251
  16. package/dist/cjs/design-tokens.js +324 -390
  17. package/dist/cjs/figmaExport.js +162 -257
  18. package/dist/cjs/figmaSync.js +234 -316
  19. package/dist/cjs/headless-ui.js +240 -368
  20. package/dist/cjs/iconAnalytics.js +337 -393
  21. package/dist/cjs/iconHtml.js +64 -86
  22. package/dist/cjs/iconMetadata.js +392 -472
  23. package/dist/cjs/iconParts.js +120 -90
  24. package/dist/cjs/iconStack.js +136 -155
  25. package/dist/cjs/iconStyles.js +1609 -504
  26. package/dist/cjs/iconValue.js +359 -430
  27. package/dist/cjs/icons/accessibility.js +1119 -134
  28. package/dist/cjs/icons/actions.js +1290 -330
  29. package/dist/cjs/icons/ai.js +1424 -144
  30. package/dist/cjs/icons/brand.js +1344 -172
  31. package/dist/cjs/icons/business.js +903 -193
  32. package/dist/cjs/icons/devtech.js +1656 -227
  33. package/dist/cjs/icons/ecommerce.js +802 -111
  34. package/dist/cjs/icons/education.js +1345 -199
  35. package/dist/cjs/icons/files.js +1597 -328
  36. package/dist/cjs/icons/fintech.js +793 -104
  37. package/dist/cjs/icons/food.js +1507 -153
  38. package/dist/cjs/icons/fun.js +786 -109
  39. package/dist/cjs/icons/gaming.js +916 -170
  40. package/dist/cjs/icons/health.js +623 -112
  41. package/dist/cjs/icons/index.js +32 -35
  42. package/dist/cjs/icons/input.js +282 -83
  43. package/dist/cjs/icons/legal.js +1065 -122
  44. package/dist/cjs/icons/media.js +1061 -267
  45. package/dist/cjs/icons/navigation.js +387 -122
  46. package/dist/cjs/icons/omniverk.js +747 -90
  47. package/dist/cjs/icons/realestate.js +1520 -193
  48. package/dist/cjs/icons/saas.js +867 -98
  49. package/dist/cjs/icons/science.js +1451 -202
  50. package/dist/cjs/icons/shapes.js +1316 -308
  51. package/dist/cjs/icons/smarthome.js +786 -90
  52. package/dist/cjs/icons/social.js +591 -59
  53. package/dist/cjs/icons/sports.js +1225 -133
  54. package/dist/cjs/icons/sustainability.js +1211 -139
  55. package/dist/cjs/icons/travel.js +1470 -148
  56. package/dist/cjs/icons/ui.js +1508 -315
  57. package/dist/cjs/icons/weather.js +1160 -173
  58. package/dist/cjs/index.js +566 -568
  59. package/dist/cjs/lottieIntegration.js +261 -266
  60. package/dist/cjs/nicetodev-icons.css +1 -1
  61. package/dist/cjs/particleEffects.js +270 -251
  62. package/dist/cjs/resolver.js +229 -284
  63. package/dist/cjs/rsc.js +175 -193
  64. package/dist/cjs/sharpen.js +73 -130
  65. package/dist/cjs/smilAnimations.js +371 -165
  66. package/dist/cjs/stacking.js +87 -122
  67. package/dist/cjs/tailwind-plugin.js +280 -320
  68. package/dist/cjs/types.js +46 -72
  69. package/dist/cjs/utilities.js +163 -324
  70. package/dist/cjs/variantProps.js +12 -52
  71. package/dist/esm/NiceDesktopIconExporter.js +671 -598
  72. package/dist/esm/NiceFaviconGenerator.js +583 -416
  73. package/dist/esm/NiceIconPicker.js +789 -507
  74. package/dist/esm/NiceIconPreview.js +247 -187
  75. package/dist/esm/NiceIconSetCreator.js +685 -551
  76. package/dist/esm/NiceIconStacker.js +549 -170
  77. package/dist/esm/NiceIconValue.js +29 -34
  78. package/dist/esm/NtdIconBadge.js +388 -359
  79. package/dist/esm/NtdIconMorph.js +423 -353
  80. package/dist/esm/NtdStateMorph.js +152 -146
  81. package/dist/esm/advancedIconSearch.js +391 -509
  82. package/dist/esm/animationControls.js +527 -641
  83. package/dist/esm/client.js +48 -10
  84. package/dist/esm/createIcon.js +350 -250
  85. package/dist/esm/design-tokens.js +334 -387
  86. package/dist/esm/figmaExport.js +170 -256
  87. package/dist/esm/figmaSync.js +237 -315
  88. package/dist/esm/headless-ui.js +254 -365
  89. package/dist/esm/iconAnalytics.js +342 -392
  90. package/dist/esm/iconHtml.js +67 -85
  91. package/dist/esm/iconMetadata.js +404 -471
  92. package/dist/esm/iconParts.js +125 -89
  93. package/dist/esm/iconStack.js +145 -154
  94. package/dist/esm/iconStyles.js +1620 -503
  95. package/dist/esm/iconValue.js +361 -429
  96. package/dist/esm/icons/accessibility.js +1133 -133
  97. package/dist/esm/icons/actions.js +1320 -329
  98. package/dist/esm/icons/ai.js +1441 -143
  99. package/dist/esm/icons/brand.js +1351 -171
  100. package/dist/esm/icons/business.js +931 -201
  101. package/dist/esm/icons/devtech.js +1687 -226
  102. package/dist/esm/icons/ecommerce.js +816 -110
  103. package/dist/esm/icons/education.js +1364 -198
  104. package/dist/esm/icons/files.js +1628 -327
  105. package/dist/esm/icons/fintech.js +807 -103
  106. package/dist/esm/icons/food.js +1524 -152
  107. package/dist/esm/icons/fun.js +801 -108
  108. package/dist/esm/icons/gaming.js +933 -169
  109. package/dist/esm/icons/health.js +636 -111
  110. package/dist/esm/icons/index.js +596 -32
  111. package/dist/esm/icons/input.js +326 -82
  112. package/dist/esm/icons/legal.js +1079 -121
  113. package/dist/esm/icons/media.js +1086 -266
  114. package/dist/esm/icons/navigation.js +401 -121
  115. package/dist/esm/icons/omniverk.js +759 -89
  116. package/dist/esm/icons/realestate.js +1544 -198
  117. package/dist/esm/icons/saas.js +880 -97
  118. package/dist/esm/icons/science.js +1477 -208
  119. package/dist/esm/icons/shapes.js +1343 -307
  120. package/dist/esm/icons/smarthome.js +799 -89
  121. package/dist/esm/icons/social.js +599 -58
  122. package/dist/esm/icons/sports.js +1240 -132
  123. package/dist/esm/icons/sustainability.js +1226 -138
  124. package/dist/esm/icons/travel.js +1486 -147
  125. package/dist/esm/icons/ui.js +1540 -314
  126. package/dist/esm/icons/weather.js +1181 -172
  127. package/dist/esm/index.js +756 -58
  128. package/dist/esm/lottieIntegration.js +264 -265
  129. package/dist/esm/nicetodev-icons.css +1 -1
  130. package/dist/esm/particleEffects.js +271 -250
  131. package/dist/esm/resolver.js +231 -283
  132. package/dist/esm/rsc.js +182 -192
  133. package/dist/esm/sharpen.js +78 -129
  134. package/dist/esm/smilAnimations.js +376 -164
  135. package/dist/esm/stacking.js +99 -121
  136. package/dist/esm/tailwind-plugin.js +282 -319
  137. package/dist/esm/types.js +50 -71
  138. package/dist/esm/utilities.js +177 -321
  139. package/dist/esm/variantProps.js +23 -52
  140. package/package.json +15 -22
@@ -1,451 +1,444 @@
1
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
- import { useRovingFocus, useNiceTranslation, useControllableState, useOverlayFocus } from '@nice2dev/ui-core';
3
- import { forwardRef, useRef, useState, useId, useMemo, useCallback, useImperativeHandle, useEffect, useLayoutEffect } from 'react';
4
- import { scopeIconHtmlIds, iconHtmlAttributes } from './iconHtml.js';
5
- import { NtdSearch, NtdCheck } from './icons/actions.js';
6
- import { NtdGrid } from './icons/files.js';
7
- import { NtdInbox } from './icons/media.js';
8
- import { NtdArrowLeft, NtdChevronDown } from './icons/navigation.js';
9
- import { NtdStar, NtdClock } from './icons/ui.js';
10
- import { iconDisplayName, renderIconStack } from './iconStack.js';
11
- import { parseIconValue, formatIconValue } from './iconValue.js';
12
- import { NiceIconStacker } from './NiceIconStacker.js';
13
-
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { useRovingFocus, useNiceTranslation, useControllableState, useOverlayFocus } from "@nice2dev/ui-core";
3
+ import { forwardRef, useRef, useState, useId, useMemo, useCallback, useImperativeHandle, useEffect, useLayoutEffect } from "react";
4
+ import { scopeIconHtmlIds, iconHtmlAttributes } from "./iconHtml.js";
5
+ import { NtdSearch, NtdCheck } from "./icons/actions.js";
6
+ import { NtdGrid } from "./icons/files.js";
7
+ import { NtdInbox } from "./icons/media.js";
8
+ import { NtdArrowLeft, NtdChevronDown } from "./icons/navigation.js";
9
+ import { NtdStar, NtdClock } from "./icons/ui.js";
10
+ import { iconDisplayName, renderIconStack } from "./iconStack.js";
11
+ import { parseIconValue, formatIconValue } from "./iconValue.js";
12
+ import { NiceIconStacker } from "./NiceIconStacker.js";
13
+ /* empty css */
14
14
  const MAX_HISTORY_ITEMS = 20;
15
- /** `useLayoutEffect` in the browser, `useEffect` on the server (no warning there). */
16
- const useIsoLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
17
- /* ─── Small icons: the package's own (until 1.2.0 these were pasted SVGs) ─── */
18
- const Chevron = () => jsx(NtdChevronDown, { size: 12, variant: "outline" });
19
- const SearchIcon = () => jsx(NtdSearch, { size: 14, variant: "outline" });
20
- const StarIcon = ({ filled }) => (jsx(NtdStar, { size: 12, variant: filled ? 'solid' : 'outline' }));
21
- const ClockIcon = () => jsx(NtdClock, { size: 12, variant: "outline" });
22
- const CheckIcon = () => jsx(NtdCheck, { size: 10, variant: "outline" });
15
+ const useIsoLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
16
+ const Chevron = () => /* @__PURE__ */ jsx(NtdChevronDown, { size: 12, variant: "outline" });
17
+ const SearchIcon = () => /* @__PURE__ */ jsx(NtdSearch, { size: 14, variant: "outline" });
18
+ const StarIcon = ({ filled }) => /* @__PURE__ */ jsx(NtdStar, { size: 12, variant: filled ? "solid" : "outline" });
19
+ const ClockIcon = () => /* @__PURE__ */ jsx(NtdClock, { size: 12, variant: "outline" });
20
+ const CheckIcon = () => /* @__PURE__ */ jsx(NtdCheck, { size: 10, variant: "outline" });
23
21
  const NO_NAMES = [];
24
22
  const NO_LAYERS = [];
25
- /** The accent the picker draws with: the page's background, white without a theme. */
26
- const PAGE_ACCENT = 'var(--nice-bg-primary, white)';
27
- /** An entry's name as shown: its label, else the export name without `Ntd`. */
28
- const entryLabel = (entry) => { var _a; return (_a = entry.label) !== null && _a !== void 0 ? _a : iconDisplayName(entry.name); };
29
- /** The part of the window an element's popup can show in: the window, cut by every ancestor that clips or scrolls. */
23
+ const PAGE_ACCENT = "var(--nice-bg-primary, white)";
24
+ const entryLabel = (entry) => entry.label ?? iconDisplayName(entry.name);
30
25
  function visibleBounds(el) {
31
- const bounds = { top: 0, bottom: window.innerHeight, left: 0, right: window.innerWidth };
32
- for (let p = el.parentElement; p && p !== document.body; p = p.parentElement) {
33
- const style = window.getComputedStyle(p);
34
- if (style.overflowX !== 'visible' || style.overflowY !== 'visible') {
35
- const r = p.getBoundingClientRect();
36
- bounds.top = Math.max(bounds.top, r.top);
37
- bounds.bottom = Math.min(bounds.bottom, r.bottom);
38
- bounds.left = Math.max(bounds.left, r.left);
39
- bounds.right = Math.min(bounds.right, r.right);
40
- }
26
+ const bounds = { top: 0, bottom: window.innerHeight, left: 0, right: window.innerWidth };
27
+ for (let p = el.parentElement; p && p !== document.body; p = p.parentElement) {
28
+ const style = window.getComputedStyle(p);
29
+ if (style.overflowX !== "visible" || style.overflowY !== "visible") {
30
+ const r = p.getBoundingClientRect();
31
+ bounds.top = Math.max(bounds.top, r.top);
32
+ bounds.bottom = Math.min(bounds.bottom, r.bottom);
33
+ bounds.left = Math.max(bounds.left, r.left);
34
+ bounds.right = Math.min(bounds.right, r.right);
41
35
  }
42
- return bounds;
36
+ }
37
+ return bounds;
43
38
  }
44
- /* ─── localStorage helpers ─── */
45
39
  function loadFromStorage(key, fallback) {
46
- if (typeof window === 'undefined') {
47
- return fallback;
48
- }
49
- try {
50
- const stored = localStorage.getItem(key);
51
- return stored ? JSON.parse(stored) : fallback;
52
- }
53
- catch (_a) {
54
- return fallback;
55
- }
40
+ if (typeof window === "undefined") {
41
+ return fallback;
42
+ }
43
+ try {
44
+ const stored = localStorage.getItem(key);
45
+ return stored ? JSON.parse(stored) : fallback;
46
+ } catch {
47
+ return fallback;
48
+ }
56
49
  }
57
50
  function saveToStorage(key, value) {
58
- if (typeof window === 'undefined') {
59
- return;
60
- }
61
- try {
62
- localStorage.setItem(key, JSON.stringify(value));
63
- }
64
- catch (_a) {
65
- // Storage full or disabled
66
- }
51
+ if (typeof window === "undefined") {
52
+ return;
53
+ }
54
+ try {
55
+ localStorage.setItem(key, JSON.stringify(value));
56
+ } catch {
57
+ }
67
58
  }
68
- /* ═══════════════════════════════════════════════════════
69
- NiceIconPicker Component
70
- ═══════════════════════════════════════════════════════ */
71
- /**
72
- * A fully-featured icon picker dropdown component.
73
- *
74
- * Provides a trigger button that opens a searchable, categorized icon grid. The value binds both
75
- * ways — `value` / `defaultValue` + `onChange` (1.3.9) — as the icon's export name, a stack as
76
- * short code (`Folder(Star@tr)`), JSON or HTML (`valueFormat`). With `stackable` the panel gets
77
- * an "Overlays" tab where icons are stacked on the chosen one. Names show without `Ntd`; the
78
- * trigger shows the icon alone or the icon and its name (`displayMode`). Keyboard: arrows in the
79
- * grid, Escape to close.
80
- *
81
- * **Styling:** Uses CSS variables for theming (see `NiceIconPicker.css`).
82
- * Override `--ntd-picker-bg`, `--ntd-picker-accent`, etc. on a parent element.
83
- *
84
- * @example
85
- * ```tsx
86
- * import { NiceIconPicker } from '@nice2dev/icons';
87
- *
88
- * function IconField({ icons, value, onChange }) {
89
- * // stores `NtdFolder`, or `Folder(Star@tr)` with a star stacked in its corner
90
- * return <NiceIconPicker icons={icons} value={value} onChange={onChange} stackable />;
91
- * }
92
- * ```
93
- */
94
- const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icons, value: valueProp, defaultValue, values: valuesProp, defaultValues, onChange, onChangeMulti, valueFormat: valueFormatProp, stackable = false, displayMode = 'icon-name', optionDisplayMode = 'icon', name, 'aria-label': ariaLabel, id, testId, 'data-testid': dataTestId, multiSelect = false, maxSelections, placeholder, variant = 'solid', iconProps, gridIconSize = 'md', triggerIconSize = 'sm', categories: categoriesProp, className, disabled = false, clearable = true, virtualized = false, virtualizedItemCount = 100, showHistory = false, showFavorites = false, storageKeyPrefix = 'ntd-icon-picker', enableDragAndDrop = false, onDragStart, onDragEnd, }, ref) {
95
- var _a;
59
+ const NiceIconPicker = /* @__PURE__ */ forwardRef(
60
+ function NiceIconPicker2({
61
+ icons,
62
+ value: valueProp,
63
+ defaultValue,
64
+ values: valuesProp,
65
+ defaultValues,
66
+ onChange,
67
+ onChangeMulti,
68
+ valueFormat: valueFormatProp,
69
+ stackable = false,
70
+ displayMode = "icon-name",
71
+ optionDisplayMode = "icon",
72
+ name,
73
+ "aria-label": ariaLabel,
74
+ id,
75
+ testId,
76
+ "data-testid": dataTestId,
77
+ multiSelect = false,
78
+ maxSelections,
79
+ placeholder,
80
+ variant = "solid",
81
+ iconProps,
82
+ gridIconSize = "md",
83
+ triggerIconSize = "sm",
84
+ categories: categoriesProp,
85
+ className,
86
+ disabled = false,
87
+ clearable = true,
88
+ virtualized = false,
89
+ virtualizedItemCount = 100,
90
+ showHistory = false,
91
+ showFavorites = false,
92
+ storageKeyPrefix = "ntd-icon-picker",
93
+ enableDragAndDrop = false,
94
+ onDragStart,
95
+ onDragEnd
96
+ }, ref) {
96
97
  const tablistRef = useRef(null);
97
- useRovingFocus(tablistRef, { orientation: 'horizontal', wrap: true, activateOnMove: true });
98
+ useRovingFocus(tablistRef, { orientation: "horizontal", wrap: true, activateOnMove: true });
98
99
  const viewsRef = useRef(null);
99
- useRovingFocus(viewsRef, { orientation: 'horizontal', activateOnMove: true });
100
+ useRovingFocus(viewsRef, { orientation: "horizontal", activateOnMove: true });
100
101
  const { t } = useNiceTranslation();
101
102
  const [isOpen, setIsOpen] = useState(false);
102
- const [search, setSearch] = useState('');
103
+ const [search, setSearch] = useState("");
103
104
  const [activeCategory, setActiveCategory] = useState(null);
104
- // the panel's view: the icon grid or the stack; `picking` = the grid chooses an icon to stack
105
- const [view, setView] = useState('icons');
105
+ const [view, setView] = useState("icons");
106
106
  const [picking, setPicking] = useState(null);
107
107
  const panelId = useId();
108
- const valueFormat = valueFormatProp !== null && valueFormatProp !== void 0 ? valueFormatProp : 'name';
108
+ const valueFormat = valueFormatProp ?? "name";
109
109
  const canStack = stackable && !multiSelect;
110
- const drawProps = useMemo(() => ({ accentColor: PAGE_ACCENT, ...iconProps, variant }), [iconProps, variant]);
111
- // Virtualization state
110
+ const drawProps = useMemo(
111
+ () => ({ accentColor: PAGE_ACCENT, ...iconProps, variant }),
112
+ [iconProps, variant]
113
+ );
112
114
  const [visibleRange, setVisibleRange] = useState({ start: 0, end: virtualizedItemCount });
113
115
  const gridWrapRef = useRef(null);
114
- // Favorites & History state
115
116
  const favoritesKey = `${storageKeyPrefix}-favorites`;
116
117
  const historyKey = `${storageKeyPrefix}-history`;
117
118
  const [favorites, setFavorites] = useState(() => loadFromStorage(favoritesKey, []));
118
119
  const [history, setHistory] = useState(() => loadFromStorage(historyKey, []));
119
- // The value binds both ways (1.3.9): controlled by `value`, or kept here from `defaultValue`.
120
120
  const [value, setValue] = useControllableState({
121
- value: valueProp,
122
- defaultValue,
123
- fallbackValue: null,
124
- componentName: 'NiceIconPicker',
121
+ value: valueProp,
122
+ defaultValue,
123
+ fallbackValue: null,
124
+ componentName: "NiceIconPicker"
125
125
  });
126
126
  const [values, setValues] = useControllableState({
127
- value: valuesProp,
128
- defaultValue: defaultValues,
129
- fallbackValue: NO_NAMES,
130
- componentName: 'NiceIconPicker',
127
+ value: valuesProp,
128
+ defaultValue: defaultValues,
129
+ fallbackValue: NO_NAMES,
130
+ componentName: "NiceIconPicker"
131
131
  });
132
132
  const rootRef = useRef(null);
133
133
  const searchInputRef = useRef(null);
134
134
  const stackerRef = useRef(null);
135
135
  const serializerRef = useRef(null);
136
- /* ─── Icons by name: exact, or without the Ntd a stored value may lack ─── */
137
136
  const byName = useMemo(() => new Map(icons.map((e) => [e.name, e])), [icons]);
138
- const entryOf = useCallback((n) => { var _a; return (n ? ((_a = byName.get(n)) !== null && _a !== void 0 ? _a : byName.get(`Ntd${n}`)) : undefined); }, [byName]);
139
- const labelOf = useCallback((n) => {
137
+ const entryOf = useCallback(
138
+ (n) => n ? byName.get(n) ?? byName.get(`Ntd${n}`) : void 0,
139
+ [byName]
140
+ );
141
+ const labelOf = useCallback(
142
+ (n) => {
140
143
  const entry = entryOf(n);
141
144
  return entry ? entryLabel(entry) : iconDisplayName(n);
142
- }, [entryOf]);
143
- const resolve = useCallback((n) => { var _a; return (_a = entryOf(n)) === null || _a === void 0 ? void 0 : _a.Component; }, [entryOf]);
144
- /* ─── The single value as a stack — a plain icon is a stack of one ─── */
145
- // The last value emitted with its layers: read back, the text would number the layers anew
146
- // (it keeps no ids) and the stacker would lose the layer it has selected.
147
- const [emitted, setEmitted] = useState(null);
145
+ },
146
+ [entryOf]
147
+ );
148
+ const resolve = useCallback(
149
+ (n) => entryOf(n)?.Component,
150
+ [entryOf]
151
+ );
152
+ const [emitted, setEmitted] = useState(
153
+ null
154
+ );
148
155
  const model = useMemo(() => {
149
- if (!value) {
150
- return NO_LAYERS;
151
- }
152
- if ((emitted === null || emitted === void 0 ? void 0 : emitted.value) === value) {
153
- return emitted.layers;
154
- }
155
- // a name as is first: an older value may hold one of the code's signs
156
- const exact = value.includes('<') ? undefined : entryOf(value);
157
- if (exact) {
158
- return [{ id: '1', icon: exact.name }];
159
- }
160
- const layers = parseIconValue(value);
161
- if (!layers || !entryOf(layers[0].icon)) {
162
- return NO_LAYERS;
163
- }
164
- // the code names icons without `Ntd`; the stacker and the grid know them by entry name
165
- return layers.map((l) => {
166
- const entry = entryOf(l.icon);
167
- return entry && entry.name !== l.icon ? { ...l, icon: entry.name } : l;
168
- });
156
+ if (!value) {
157
+ return NO_LAYERS;
158
+ }
159
+ if (emitted?.value === value) {
160
+ return emitted.layers;
161
+ }
162
+ const exact = value.includes("<") ? void 0 : entryOf(value);
163
+ if (exact) {
164
+ return [{ id: "1", icon: exact.name }];
165
+ }
166
+ const layers = parseIconValue(value);
167
+ if (!layers || !entryOf(layers[0].icon)) {
168
+ return NO_LAYERS;
169
+ }
170
+ return layers.map((l) => {
171
+ const entry = entryOf(l.icon);
172
+ return entry && entry.name !== l.icon ? { ...l, icon: entry.name } : l;
173
+ });
169
174
  }, [value, emitted, entryOf]);
170
- // A stack waiting to be drawn into HTML: shown at once, emitted once drawn (below).
171
175
  const [pending, setPending] = useState(null);
172
- const shown = pending !== null && pending !== void 0 ? pending : model;
176
+ const shown = pending ?? model;
173
177
  const base = shown[0];
174
- /** The name a stack's code gives an icon: without `Ntd`, unless that is another icon's name. */
175
- const shortName = useCallback((n) => {
178
+ const shortName = useCallback(
179
+ (n) => {
176
180
  const short = iconDisplayName(n);
177
181
  return short !== n && !byName.has(short) ? short : n;
178
- }, [byName]);
182
+ },
183
+ [byName]
184
+ );
179
185
  const onChangeRef = useRef(onChange);
180
186
  onChangeRef.current = onChange;
181
- const emit = useCallback((next, layers) => {
182
- var _a, _b, _c;
187
+ const emit = useCallback(
188
+ (next, layers) => {
183
189
  setEmitted(next === null ? null : { value: next, layers });
184
190
  setValue(next);
185
- (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, next, (_c = entryOf((_b = layers[0]) === null || _b === void 0 ? void 0 : _b.icon)) !== null && _c !== void 0 ? _c : null);
186
- }, [setValue, entryOf]);
187
- /** Sets the single value to a stack: written as `valueFormat` holds it, HTML once drawn. */
188
- const commit = useCallback((layers) => {
191
+ onChangeRef.current?.(next, entryOf(layers[0]?.icon) ?? null);
192
+ },
193
+ [setValue, entryOf]
194
+ );
195
+ const commit = useCallback(
196
+ (layers) => {
189
197
  if (layers.length === 0) {
190
- setPending(null);
191
- emit(null, NO_LAYERS);
192
- }
193
- else if (valueFormat === 'html') {
194
- setPending(layers);
195
- }
196
- else {
197
- emit(formatIconValue(layers, valueFormat, { shortName }), layers);
198
- }
199
- }, [valueFormat, emit, shortName]);
200
- // The HTML is the icon as drawn: the hidden copy below renders the pending stack, and its
201
- // markup — ids renamed after the drawing — is the new value, emitted before the paint.
198
+ setPending(null);
199
+ emit(null, NO_LAYERS);
200
+ } else if (valueFormat === "html") {
201
+ setPending(layers);
202
+ } else {
203
+ emit(formatIconValue(layers, valueFormat, { shortName }), layers);
204
+ }
205
+ },
206
+ [valueFormat, emit, shortName]
207
+ );
202
208
  useIsoLayoutEffect(() => {
203
- var _a;
204
- if (!pending) {
205
- return;
206
- }
207
- const svg = (_a = serializerRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('svg');
208
- setPending(null);
209
- if (svg) {
210
- emit(scopeIconHtmlIds(svg.outerHTML), pending);
211
- }
209
+ if (!pending) {
210
+ return;
211
+ }
212
+ const svg = serializerRef.current?.querySelector("svg");
213
+ setPending(null);
214
+ if (svg) {
215
+ emit(scopeIconHtmlIds(svg.outerHTML), pending);
216
+ }
212
217
  }, [pending, emit]);
213
- const selectedValues = useMemo(() => (multiSelect ? values : base ? [base.icon] : NO_NAMES), [multiSelect, values, base]);
214
- /* ─── Favorites helpers ─── */
215
- const addFavorite = useCallback((favorite) => {
218
+ const selectedValues = useMemo(
219
+ () => multiSelect ? values : base ? [base.icon] : NO_NAMES,
220
+ [multiSelect, values, base]
221
+ );
222
+ const addFavorite = useCallback(
223
+ (favorite) => {
216
224
  setFavorites((prev) => {
217
- if (prev.includes(favorite)) {
218
- return prev;
219
- }
220
- const next = [...prev, favorite];
221
- saveToStorage(favoritesKey, next);
222
- return next;
225
+ if (prev.includes(favorite)) {
226
+ return prev;
227
+ }
228
+ const next = [...prev, favorite];
229
+ saveToStorage(favoritesKey, next);
230
+ return next;
223
231
  });
224
- }, [favoritesKey]);
225
- const removeFavorite = useCallback((favorite) => {
232
+ },
233
+ [favoritesKey]
234
+ );
235
+ const removeFavorite = useCallback(
236
+ (favorite) => {
226
237
  setFavorites((prev) => {
227
- const next = prev.filter((n) => n !== favorite);
228
- saveToStorage(favoritesKey, next);
229
- return next;
238
+ const next = prev.filter((n) => n !== favorite);
239
+ saveToStorage(favoritesKey, next);
240
+ return next;
230
241
  });
231
- }, [favoritesKey]);
232
- const toggleFavorite = useCallback((favorite) => {
242
+ },
243
+ [favoritesKey]
244
+ );
245
+ const toggleFavorite = useCallback(
246
+ (favorite) => {
233
247
  if (favorites.includes(favorite)) {
234
- removeFavorite(favorite);
235
- }
236
- else {
237
- addFavorite(favorite);
248
+ removeFavorite(favorite);
249
+ } else {
250
+ addFavorite(favorite);
238
251
  }
239
- }, [favorites, addFavorite, removeFavorite]);
252
+ },
253
+ [favorites, addFavorite, removeFavorite]
254
+ );
240
255
  const clearFavorites = useCallback(() => {
241
- setFavorites([]);
242
- saveToStorage(favoritesKey, []);
256
+ setFavorites([]);
257
+ saveToStorage(favoritesKey, []);
243
258
  }, [favoritesKey]);
244
- /* ─── History helpers ─── */
245
- const addToHistory = useCallback((used) => {
259
+ const addToHistory = useCallback(
260
+ (used) => {
246
261
  setHistory((prev) => {
247
- const filtered = prev.filter((n) => n !== used);
248
- const next = [used, ...filtered].slice(0, MAX_HISTORY_ITEMS);
249
- saveToStorage(historyKey, next);
250
- return next;
262
+ const filtered2 = prev.filter((n) => n !== used);
263
+ const next = [used, ...filtered2].slice(0, MAX_HISTORY_ITEMS);
264
+ saveToStorage(historyKey, next);
265
+ return next;
251
266
  });
252
- }, [historyKey]);
267
+ },
268
+ [historyKey]
269
+ );
253
270
  const clearHistory = useCallback(() => {
254
- setHistory([]);
255
- saveToStorage(historyKey, []);
271
+ setHistory([]);
272
+ saveToStorage(historyKey, []);
256
273
  }, [historyKey]);
257
- /* ─── Imperative handle ─── */
258
274
  useImperativeHandle(ref, () => ({
259
- open: () => setIsOpen(true),
260
- close: () => setIsOpen(false),
261
- toggle: () => setIsOpen((o) => !o),
262
- getFavorites: () => favorites,
263
- addFavorite,
264
- removeFavorite,
265
- toggleFavorite,
266
- clearFavorites,
267
- getHistory: () => history,
268
- clearHistory,
275
+ open: () => setIsOpen(true),
276
+ close: () => setIsOpen(false),
277
+ toggle: () => setIsOpen((o) => !o),
278
+ getFavorites: () => favorites,
279
+ addFavorite,
280
+ removeFavorite,
281
+ toggleFavorite,
282
+ clearFavorites,
283
+ getHistory: () => history,
284
+ clearHistory
269
285
  }));
270
- /* ─── Derive categories ─── */
271
286
  const allCategories = useMemo(() => {
272
- if (categoriesProp) {
273
- return categoriesProp;
274
- }
275
- const seen = new Set();
276
- const result = [];
277
- for (const icon of icons) {
278
- if (!seen.has(icon.category)) {
279
- seen.add(icon.category);
280
- result.push(icon.category);
281
- }
282
- }
283
- return result;
287
+ if (categoriesProp) {
288
+ return categoriesProp;
289
+ }
290
+ const seen = /* @__PURE__ */ new Set();
291
+ const result = [];
292
+ for (const icon of icons) {
293
+ if (!seen.has(icon.category)) {
294
+ seen.add(icon.category);
295
+ result.push(icon.category);
296
+ }
297
+ }
298
+ return result;
284
299
  }, [icons, categoriesProp]);
285
- /* ─── Filter icons: by the name shown, the export name or the category ─── */
286
300
  const filtered = useMemo(() => {
287
- let items = icons;
288
- const q = search.trim().toLowerCase();
289
- if (q) {
290
- items = items.filter((i) => entryLabel(i).toLowerCase().includes(q) ||
291
- i.name.toLowerCase().includes(q) ||
292
- i.category.toLowerCase().includes(q));
293
- }
294
- if (activeCategory === '__favorites__') {
295
- items = items.filter((i) => favorites.includes(i.name));
296
- }
297
- else if (activeCategory === '__history__') {
298
- const historySet = new Set(history);
299
- items = items.filter((i) => historySet.has(i.name));
300
- // Sort by history order
301
- items.sort((a, b) => history.indexOf(a.name) - history.indexOf(b.name));
302
- }
303
- else if (activeCategory) {
304
- items = items.filter((i) => i.category === activeCategory);
305
- }
306
- return items;
301
+ let items = icons;
302
+ const q = search.trim().toLowerCase();
303
+ if (q) {
304
+ items = items.filter(
305
+ (i) => entryLabel(i).toLowerCase().includes(q) || i.name.toLowerCase().includes(q) || i.category.toLowerCase().includes(q)
306
+ );
307
+ }
308
+ if (activeCategory === "__favorites__") {
309
+ items = items.filter((i) => favorites.includes(i.name));
310
+ } else if (activeCategory === "__history__") {
311
+ const historySet = new Set(history);
312
+ items = items.filter((i) => historySet.has(i.name));
313
+ items.sort((a, b) => history.indexOf(a.name) - history.indexOf(b.name));
314
+ } else if (activeCategory) {
315
+ items = items.filter((i) => i.category === activeCategory);
316
+ }
317
+ return items;
307
318
  }, [icons, search, activeCategory, favorites, history]);
308
- /* ─── Virtualized items ─── */
309
319
  const virtualizedFiltered = useMemo(() => {
310
- if (!virtualized) {
311
- return filtered;
312
- }
313
- return filtered.slice(visibleRange.start, visibleRange.end);
320
+ if (!virtualized) {
321
+ return filtered;
322
+ }
323
+ return filtered.slice(visibleRange.start, visibleRange.end);
314
324
  }, [filtered, virtualized, visibleRange]);
315
- /* ─── Group by category ─── */
316
325
  const grouped = useMemo(() => {
317
- const items = virtualized ? virtualizedFiltered : filtered;
318
- const map = new Map();
319
- for (const icon of items) {
320
- const list = map.get(icon.category);
321
- if (list) {
322
- list.push(icon);
323
- }
324
- else {
325
- map.set(icon.category, [icon]);
326
- }
327
- }
328
- return map;
326
+ const items = virtualized ? virtualizedFiltered : filtered;
327
+ const map = /* @__PURE__ */ new Map();
328
+ for (const icon of items) {
329
+ const list = map.get(icon.category);
330
+ if (list) {
331
+ list.push(icon);
332
+ } else {
333
+ map.set(icon.category, [icon]);
334
+ }
335
+ }
336
+ return map;
329
337
  }, [filtered, virtualized, virtualizedFiltered]);
330
- /* ─── Selected entries (multi-select) ─── */
331
- const selectedEntries = useMemo(() => values.map((v) => entryOf(v)).filter((e) => e !== undefined), [values, entryOf]);
332
- /* ─── Click outside ─── */
338
+ const selectedEntries = useMemo(
339
+ () => values.map((v) => entryOf(v)).filter((e) => e !== void 0),
340
+ [values, entryOf]
341
+ );
333
342
  useEffect(() => {
334
- if (!isOpen) {
335
- return;
336
- }
337
- function onClickOutside(e) {
338
- if (rootRef.current && !rootRef.current.contains(e.target)) {
339
- setIsOpen(false);
340
- }
341
- }
342
- document.addEventListener('mousedown', onClickOutside);
343
- return () => document.removeEventListener('mousedown', onClickOutside);
343
+ if (!isOpen) {
344
+ return;
345
+ }
346
+ function onClickOutside(e) {
347
+ if (rootRef.current && !rootRef.current.contains(e.target)) {
348
+ setIsOpen(false);
349
+ }
350
+ }
351
+ document.addEventListener("mousedown", onClickOutside);
352
+ return () => document.removeEventListener("mousedown", onClickOutside);
344
353
  }, [isOpen]);
345
- /* ─── Reset on close ─── */
346
354
  useEffect(() => {
347
- if (!isOpen) {
348
- setSearch('');
349
- setActiveCategory(null);
350
- setView('icons');
351
- setPicking(null);
352
- setVisibleRange({ start: 0, end: virtualizedItemCount });
353
- }
355
+ if (!isOpen) {
356
+ setSearch("");
357
+ setActiveCategory(null);
358
+ setView("icons");
359
+ setPicking(null);
360
+ setVisibleRange({ start: 0, end: virtualizedItemCount });
361
+ }
354
362
  }, [isOpen, virtualizedItemCount]);
355
- /* ─── Keyboard and pad (1.3.0) ─── */
356
- // The panel is a dialog on the overlay stack: focus on the search field when it opens, Escape
357
- // / B close only it (a document listener before) and focus returns to the trigger. The icon
358
- // grid is an APG listbox moving in two dimensions — the cells were reachable by Tab only.
359
- // While the grid picks an icon to stack, Escape goes back to the stack instead (1.3.9).
360
363
  const dropdownRef = useRef(null);
361
364
  useOverlayFocus(dropdownRef, {
362
- open: isOpen,
363
- modal: false,
364
- initialFocus: searchInputRef,
365
- onClose: () => (picking ? setPicking(null) : setIsOpen(false)),
365
+ open: isOpen,
366
+ modal: false,
367
+ initialFocus: searchInputRef,
368
+ onClose: () => picking ? setPicking(null) : setIsOpen(false)
366
369
  });
367
- useRovingFocus(gridWrapRef, { orientation: 'grid', itemSelector: '.ntd-picker-cell' });
368
- // into the grid to pick an icon to stack: focus the search; back: the layer it went into
370
+ useRovingFocus(gridWrapRef, { orientation: "grid", itemSelector: ".ntd-picker-cell" });
369
371
  const wasPicking = useRef(false);
370
372
  useEffect(() => {
371
- var _a, _b, _c;
372
- if (picking) {
373
- (_a = searchInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
374
- }
375
- else if (wasPicking.current) {
376
- (_c = (_b = dropdownRef.current) === null || _b === void 0 ? void 0 : _b.querySelector('.nice-icon-stacker__layer--selected .nice-icon-stacker__layer-select')) === null || _c === void 0 ? void 0 : _c.focus();
377
- }
378
- wasPicking.current = !!picking;
373
+ if (picking) {
374
+ searchInputRef.current?.focus();
375
+ } else if (wasPicking.current) {
376
+ dropdownRef.current?.querySelector(
377
+ ".nice-icon-stacker__layer--selected .nice-icon-stacker__layer-select"
378
+ )?.focus();
379
+ }
380
+ wasPicking.current = !!picking;
379
381
  }, [picking]);
380
- /* ─── Virtualization scroll handler ─── */
381
382
  useEffect(() => {
382
- if (!virtualized || !isOpen || !gridWrapRef.current) {
383
- return;
384
- }
385
- const handleScroll = () => {
386
- const el = gridWrapRef.current;
387
- if (!el) {
388
- return;
389
- }
390
- const scrollTop = el.scrollTop;
391
- const itemHeight = 48; // Approximate cell height
392
- const itemsPerRow = 6; // Approximate items per row
393
- const rowHeight = itemHeight;
394
- const visibleRows = Math.ceil(el.clientHeight / rowHeight);
395
- const startRow = Math.floor(scrollTop / rowHeight);
396
- const buffer = 2; // Extra rows to render
397
- const start = Math.max(0, (startRow - buffer) * itemsPerRow);
398
- const end = Math.min(filtered.length, (startRow + visibleRows + buffer) * itemsPerRow);
399
- setVisibleRange({ start, end });
400
- };
401
- const el = gridWrapRef.current;
402
- el.addEventListener('scroll', handleScroll, { passive: true });
403
- return () => el.removeEventListener('scroll', handleScroll);
383
+ if (!virtualized || !isOpen || !gridWrapRef.current) {
384
+ return;
385
+ }
386
+ const handleScroll = () => {
387
+ const el2 = gridWrapRef.current;
388
+ if (!el2) {
389
+ return;
390
+ }
391
+ const scrollTop = el2.scrollTop;
392
+ const itemHeight = 48;
393
+ const itemsPerRow = 6;
394
+ const rowHeight = itemHeight;
395
+ const visibleRows = Math.ceil(el2.clientHeight / rowHeight);
396
+ const startRow = Math.floor(scrollTop / rowHeight);
397
+ const buffer = 2;
398
+ const start = Math.max(0, (startRow - buffer) * itemsPerRow);
399
+ const end = Math.min(filtered.length, (startRow + visibleRows + buffer) * itemsPerRow);
400
+ setVisibleRange({ start, end });
401
+ };
402
+ const el = gridWrapRef.current;
403
+ el.addEventListener("scroll", handleScroll, { passive: true });
404
+ return () => el.removeEventListener("scroll", handleScroll);
404
405
  }, [virtualized, isOpen, filtered.length]);
405
- /* ─── Handlers ─── */
406
- const handleSelect = useCallback((entry) => {
407
- var _a;
408
- // an icon to stack: into the layer the stacker has selected, then back to the stack
406
+ const handleSelect = useCallback(
407
+ (entry) => {
409
408
  if (picking) {
410
- (_a = stackerRef.current) === null || _a === void 0 ? void 0 : _a.add({ icon: entry.name });
411
- setPicking(null);
412
- setSearch('');
413
- return;
409
+ stackerRef.current?.add({ icon: entry.name });
410
+ setPicking(null);
411
+ setSearch("");
412
+ return;
414
413
  }
415
- // Add to history
416
414
  if (showHistory) {
417
- addToHistory(entry.name);
415
+ addToHistory(entry.name);
418
416
  }
419
417
  if (multiSelect) {
420
- const isSelected = values.includes(entry.name);
421
- let newValues;
422
- if (isSelected) {
423
- newValues = values.filter((v) => v !== entry.name);
424
- }
425
- else {
426
- if (maxSelections && values.length >= maxSelections) {
427
- return; // Max selections reached
428
- }
429
- newValues = [...values, entry.name];
418
+ const isSelected = values.includes(entry.name);
419
+ let newValues;
420
+ if (isSelected) {
421
+ newValues = values.filter((v) => v !== entry.name);
422
+ } else {
423
+ if (maxSelections && values.length >= maxSelections) {
424
+ return;
430
425
  }
431
- setValues(newValues);
432
- if (onChangeMulti) {
433
- const entries = newValues
434
- .map((v) => entryOf(v))
435
- .filter((e) => e !== undefined);
436
- onChangeMulti(newValues, entries);
437
- }
438
- // Don't close in multi-select mode
439
- }
440
- else {
441
- // the HTML records the variant it was drawn in; swapping the icon keeps its stack
442
- const own = valueFormat === 'html' ? { variant } : {};
443
- commit(canStack && base
444
- ? [{ ...base, icon: entry.name, ...own }, ...shown.slice(1)]
445
- : [{ id: '1', icon: entry.name, ...own }]);
446
- setIsOpen(false);
447
- }
448
- }, [
426
+ newValues = [...values, entry.name];
427
+ }
428
+ setValues(newValues);
429
+ if (onChangeMulti) {
430
+ const entries = newValues.map((v) => entryOf(v)).filter((e) => e !== void 0);
431
+ onChangeMulti(newValues, entries);
432
+ }
433
+ } else {
434
+ const own = valueFormat === "html" ? { variant } : {};
435
+ commit(
436
+ canStack && base ? [{ ...base, icon: entry.name, ...own }, ...shown.slice(1)] : [{ id: "1", icon: entry.name, ...own }]
437
+ );
438
+ setIsOpen(false);
439
+ }
440
+ },
441
+ [
449
442
  picking,
450
443
  variant,
451
444
  multiSelect,
@@ -460,171 +453,460 @@ const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icon
460
453
  canStack,
461
454
  base,
462
455
  shown,
463
- commit,
464
- ]);
465
- const handleClear = useCallback((e) => {
456
+ commit
457
+ ]
458
+ );
459
+ const handleClear = useCallback(
460
+ (e) => {
466
461
  e.stopPropagation();
467
462
  if (multiSelect) {
468
- setValues(NO_NAMES);
469
- onChangeMulti === null || onChangeMulti === void 0 ? void 0 : onChangeMulti([], []);
470
- }
471
- else {
472
- commit(NO_LAYERS);
473
- setView('icons');
474
- }
475
- }, [multiSelect, setValues, onChangeMulti, commit]);
463
+ setValues(NO_NAMES);
464
+ onChangeMulti?.([], []);
465
+ } else {
466
+ commit(NO_LAYERS);
467
+ setView("icons");
468
+ }
469
+ },
470
+ [multiSelect, setValues, onChangeMulti, commit]
471
+ );
476
472
  const handleToggle = useCallback(() => {
477
- if (!disabled) {
478
- setIsOpen((o) => !o);
479
- }
473
+ if (!disabled) {
474
+ setIsOpen((o) => !o);
475
+ }
480
476
  }, [disabled]);
481
- const handleFavoriteClick = useCallback((e, favorite) => {
477
+ const handleFavoriteClick = useCallback(
478
+ (e, favorite) => {
482
479
  e.stopPropagation();
483
480
  toggleFavorite(favorite);
484
- }, [toggleFavorite]);
485
- /* ─── Drag & Drop handlers ─── */
486
- const handleDragStart = useCallback((entry, e) => {
481
+ },
482
+ [toggleFavorite]
483
+ );
484
+ const handleDragStart = useCallback(
485
+ (entry, e) => {
487
486
  if (!enableDragAndDrop) {
488
- return;
487
+ return;
489
488
  }
490
- // Set various data formats for maximum compatibility
491
489
  const readName = entryLabel(entry);
492
- // Plain text - icon name
493
- e.dataTransfer.setData('text/plain', readName);
494
- // JSON data with full icon info
490
+ e.dataTransfer.setData("text/plain", readName);
495
491
  const iconData = {
496
- name: entry.name,
497
- readableName: readName,
498
- category: entry.category,
499
- variant,
492
+ name: entry.name,
493
+ readableName: readName,
494
+ category: entry.category,
495
+ variant
500
496
  };
501
- e.dataTransfer.setData('application/json', JSON.stringify(iconData));
502
- e.dataTransfer.setData('text/x-ntd-icon', JSON.stringify(iconData));
503
- // The cell's own drawing as an image
497
+ e.dataTransfer.setData("application/json", JSON.stringify(iconData));
498
+ e.dataTransfer.setData("text/x-ntd-icon", JSON.stringify(iconData));
504
499
  try {
505
- const svgElement = e.currentTarget.querySelector('svg');
506
- if (svgElement) {
507
- const svgClone = svgElement.cloneNode(true);
508
- svgClone.setAttribute('width', '64');
509
- svgClone.setAttribute('height', '64');
510
- const svgString = new XMLSerializer().serializeToString(svgClone);
511
- const svgBlob = new Blob([svgString], { type: 'image/svg+xml' });
512
- e.dataTransfer.setData('text/html', `<img src="data:image/svg+xml;base64,${btoa(svgString)}" alt="${readName}" />`);
513
- // Create drag image
514
- const img = new Image(64, 64);
515
- img.src = URL.createObjectURL(svgBlob);
516
- e.dataTransfer.setDragImage(img, 32, 32);
517
- }
518
- }
519
- catch (_a) {
520
- // Fallback - use default drag image
521
- }
522
- e.dataTransfer.effectAllowed = 'copy';
523
- onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart(entry, e);
524
- }, [enableDragAndDrop, variant, onDragStart]);
525
- const handleDragEnd = useCallback((entry, e) => {
526
- onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(entry, e);
527
- }, [onDragEnd]);
500
+ const svgElement = e.currentTarget.querySelector("svg");
501
+ if (svgElement) {
502
+ const svgClone = svgElement.cloneNode(true);
503
+ svgClone.setAttribute("width", "64");
504
+ svgClone.setAttribute("height", "64");
505
+ const svgString = new XMLSerializer().serializeToString(svgClone);
506
+ const svgBlob = new Blob([svgString], { type: "image/svg+xml" });
507
+ e.dataTransfer.setData(
508
+ "text/html",
509
+ `<img src="data:image/svg+xml;base64,${btoa(svgString)}" alt="${readName}" />`
510
+ );
511
+ const img = new Image(64, 64);
512
+ img.src = URL.createObjectURL(svgBlob);
513
+ e.dataTransfer.setDragImage(img, 32, 32);
514
+ }
515
+ } catch {
516
+ }
517
+ e.dataTransfer.effectAllowed = "copy";
518
+ onDragStart?.(entry, e);
519
+ },
520
+ [enableDragAndDrop, variant, onDragStart]
521
+ );
522
+ const handleDragEnd = useCallback(
523
+ (entry, e) => {
524
+ onDragEnd?.(entry, e);
525
+ },
526
+ [onDragEnd]
527
+ );
528
528
  const triggerLabel = useMemo(() => {
529
- if (multiSelect) {
530
- if (selectedEntries.length === 1) {
531
- return entryLabel(selectedEntries[0]);
532
- }
533
- if (selectedEntries.length > 1) {
534
- return t('iconPicker.iconsSelected', '{count} icons selected').replace('{count}', String(selectedEntries.length));
535
- }
536
- }
537
- else if (base) {
538
- return labelOf(base.icon);
539
- }
540
- return placeholder !== null && placeholder !== void 0 ? placeholder : t('iconPicker.selectIcon', 'Select icon…');
529
+ if (multiSelect) {
530
+ if (selectedEntries.length === 1) {
531
+ return entryLabel(selectedEntries[0]);
532
+ }
533
+ if (selectedEntries.length > 1) {
534
+ return t("iconPicker.iconsSelected", "{count} icons selected").replace(
535
+ "{count}",
536
+ String(selectedEntries.length)
537
+ );
538
+ }
539
+ } else if (base) {
540
+ return labelOf(base.icon);
541
+ }
542
+ return placeholder ?? t("iconPicker.selectIcon", "Select icon…");
541
543
  }, [multiSelect, selectedEntries, base, labelOf, placeholder, t]);
542
544
  const hasSelection = multiSelect ? selectedEntries.length > 0 : !!base;
543
- const iconOnly = displayMode === 'icon';
544
- // the field's label first, then what is chosen — the icon-only trigger has no text of its own
545
- const triggerName = ariaLabel
546
- ? `${ariaLabel}: ${triggerLabel}`
547
- : iconOnly
548
- ? triggerLabel
549
- : undefined;
545
+ const iconOnly = displayMode === "icon";
546
+ const triggerName = ariaLabel ? `${ariaLabel}: ${triggerLabel}` : iconOnly ? triggerLabel : void 0;
550
547
  const overlays = Math.max(0, shown.length - 1);
551
- const showGrid = view === 'icons' || !!picking;
552
- // Where the panel opens (1.3.9): above the trigger when there is more room there, against its
553
- // right edge near the window's, never taller than the room it has — the stack view is tall.
554
- // The room is what the window and every clipping or scrolling ancestor leave visible.
548
+ const showGrid = view === "icons" || !!picking;
555
549
  const [placement, setPlacement] = useState({
556
- up: false,
557
- end: false,
550
+ up: false,
551
+ end: false
558
552
  });
559
553
  useIsoLayoutEffect(() => {
560
- const root = rootRef.current;
561
- if (!isOpen || !root || typeof window === 'undefined') {
562
- return;
563
- }
564
- const r = root.getBoundingClientRect();
565
- const bounds = visibleBounds(root);
566
- const want = showGrid ? 460 : 620;
567
- const below = bounds.bottom - r.bottom - 12;
568
- const above = r.top - bounds.top - 12;
569
- const up = below < want && above > below;
570
- const room = up ? above : below;
571
- setPlacement({
572
- up,
573
- end: r.left + 380 > bounds.right - 8 && r.right - bounds.left >= 380,
574
- maxHeight: room < want ? Math.max(240, Math.floor(room)) : undefined,
575
- });
554
+ const root = rootRef.current;
555
+ if (!isOpen || !root || typeof window === "undefined") {
556
+ return;
557
+ }
558
+ const r = root.getBoundingClientRect();
559
+ const bounds = visibleBounds(root);
560
+ const want = showGrid ? 460 : 620;
561
+ const below = bounds.bottom - r.bottom - 12;
562
+ const above = r.top - bounds.top - 12;
563
+ const up = below < want && above > below;
564
+ const room = up ? above : below;
565
+ setPlacement({
566
+ up,
567
+ end: r.left + 380 > bounds.right - 8 && r.right - bounds.left >= 380,
568
+ maxHeight: room < want ? Math.max(240, Math.floor(room)) : void 0
569
+ });
576
570
  }, [isOpen, showGrid]);
577
- const pickingInto = picking ? shown.find((l) => l.id === picking.target) : undefined;
578
- const SingleMulti = (_a = selectedEntries[0]) === null || _a === void 0 ? void 0 : _a.Component;
579
- return (jsxs("div", { ref: rootRef, id: id, "data-testid": dataTestId !== null && dataTestId !== void 0 ? dataTestId : testId, className: `ntd-picker${isOpen ? ' ntd-picker--open' : ''}${disabled ? ' ntd-picker--disabled' : ''}${multiSelect ? ' ntd-picker--multi' : ''}${iconOnly ? ' ntd-picker--icon-only' : ''}${className ? ` ${className}` : ''}`, children: [jsxs("button", { type: "button", className: "ntd-picker-trigger", onClick: handleToggle, disabled: disabled, "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-label": triggerName, title: iconOnly ? triggerLabel : undefined, children: [jsx("span", { className: "ntd-picker-trigger-icon", children: !multiSelect && base ? (renderIconStack(shown, resolve, {
580
- baseProps: { ...drawProps, size: triggerIconSize },
581
- })) : multiSelect && selectedEntries.length > 1 ? (jsx("span", { className: "ntd-picker-multi-badge", children: selectedEntries.length })) : SingleMulti ? (jsx(SingleMulti, { ...drawProps, size: triggerIconSize })) : (jsx(NtdGrid, { size: 16, variant: "outline" })) }), !iconOnly && (jsxs(Fragment, { children: [jsx("span", { className: `ntd-picker-trigger-label${hasSelection ? '' : ' ntd-picker-trigger-placeholder'}`, children: triggerLabel }), jsx("span", { className: "ntd-picker-trigger-chevron", children: jsx(Chevron, {}) })] }))] }), name &&
582
- (multiSelect ? (values.map((v) => jsx("input", { type: "hidden", name: name, value: v }, v))) : (jsx("input", { type: "hidden", name: name, value: value !== null && value !== void 0 ? value : '' }))), pending && (jsx("span", { ref: serializerRef, className: "ntd-picker-serializer", hidden: true, children: renderIconStack(pending, resolve, {
583
- baseProps: {
584
- ...drawProps,
585
- width: '1em',
586
- height: '1em',
587
- title: labelOf(pending[0].icon),
588
- ...iconHtmlAttributes(pending),
589
- },
590
- }) })), isOpen && (jsxs("div", { ref: dropdownRef, className: `ntd-picker-dropdown${showGrid ? '' : ' ntd-picker-dropdown--stack'}${placement.up ? ' ntd-picker-dropdown--up' : ''}${placement.end ? ' ntd-picker-dropdown--end' : ''}`, style: placement.maxHeight ? { maxHeight: placement.maxHeight } : undefined, role: "dialog", "aria-label": t('iconPicker.selectIcon', 'Select icon…'), children: [canStack &&
591
- (picking ? (jsxs("div", { className: "ntd-picker-banner", children: [jsx("button", { type: "button", className: "ntd-picker-back", onClick: () => setPicking(null), "aria-label": t('iconPicker.back', 'Back'), title: t('iconPicker.back', 'Back'), children: jsx(NtdArrowLeft, { size: 14, variant: "outline" }) }), jsx("span", { children: t('iconPicker.pickOverlay', 'Pick an icon to put on {icon}').replace('{icon}', pickingInto ? labelOf(pickingInto.icon) : base ? labelOf(base.icon) : '') })] })) : (jsxs("div", { className: "ntd-picker-views", ref: viewsRef, role: "tablist", "aria-label": t('iconPicker.selectIcon', 'Select icon…'), children: [jsx("button", { type: "button", role: "tab", id: `${panelId}-icons`, "aria-selected": view === 'icons', "aria-controls": `${panelId}-panel`, className: `ntd-picker-view${view === 'icons' ? ' ntd-picker-view--active' : ''}`, onClick: () => setView('icons'), children: t('iconPicker.tabIcon', 'Icon') }), jsxs("button", { type: "button", role: "tab", id: `${panelId}-stack`, "aria-selected": view === 'stack', "aria-controls": `${panelId}-panel`, className: `ntd-picker-view${view === 'stack' ? ' ntd-picker-view--active' : ''}`, onClick: () => setView('stack'), disabled: !base, title: base ? undefined : t('iconPicker.stackNeedsIcon', 'Pick an icon first'), children: [t('iconPicker.tabStack', 'Overlays'), overlays > 0 && jsx("span", { className: "ntd-picker-tab-count", children: overlays })] })] }))), jsxs("div", { className: "ntd-picker-panel", id: `${panelId}-panel`, ...(canStack && !picking
592
- ? {
593
- role: 'tabpanel',
594
- 'aria-labelledby': `${panelId}-${view}`,
571
+ const pickingInto = picking ? shown.find((l) => l.id === picking.target) : void 0;
572
+ const SingleMulti = selectedEntries[0]?.Component;
573
+ return /* @__PURE__ */ jsxs(
574
+ "div",
575
+ {
576
+ ref: rootRef,
577
+ id,
578
+ "data-testid": dataTestId ?? testId,
579
+ className: `ntd-picker${isOpen ? " ntd-picker--open" : ""}${disabled ? " ntd-picker--disabled" : ""}${multiSelect ? " ntd-picker--multi" : ""}${iconOnly ? " ntd-picker--icon-only" : ""}${className ? ` ${className}` : ""}`,
580
+ children: [
581
+ /* @__PURE__ */ jsxs(
582
+ "button",
583
+ {
584
+ type: "button",
585
+ className: "ntd-picker-trigger",
586
+ onClick: handleToggle,
587
+ disabled,
588
+ "aria-haspopup": "dialog",
589
+ "aria-expanded": isOpen,
590
+ "aria-label": triggerName,
591
+ title: iconOnly ? triggerLabel : void 0,
592
+ children: [
593
+ /* @__PURE__ */ jsx("span", { className: "ntd-picker-trigger-icon", children: !multiSelect && base ? renderIconStack(shown, resolve, {
594
+ baseProps: { ...drawProps, size: triggerIconSize }
595
+ }) : multiSelect && selectedEntries.length > 1 ? /* @__PURE__ */ jsx("span", { className: "ntd-picker-multi-badge", children: selectedEntries.length }) : SingleMulti ? /* @__PURE__ */ jsx(SingleMulti, { ...drawProps, size: triggerIconSize }) : /* @__PURE__ */ jsx(NtdGrid, { size: 16, variant: "outline" }) }),
596
+ !iconOnly && /* @__PURE__ */ jsxs(Fragment, { children: [
597
+ /* @__PURE__ */ jsx(
598
+ "span",
599
+ {
600
+ className: `ntd-picker-trigger-label${hasSelection ? "" : " ntd-picker-trigger-placeholder"}`,
601
+ children: triggerLabel
602
+ }
603
+ ),
604
+ /* @__PURE__ */ jsx("span", { className: "ntd-picker-trigger-chevron", children: /* @__PURE__ */ jsx(Chevron, {}) })
605
+ ] })
606
+ ]
607
+ }
608
+ ),
609
+ name && (multiSelect ? values.map((v) => /* @__PURE__ */ jsx("input", { type: "hidden", name, value: v }, v)) : /* @__PURE__ */ jsx("input", { type: "hidden", name, value: value ?? "" })),
610
+ pending && /* @__PURE__ */ jsx("span", { ref: serializerRef, className: "ntd-picker-serializer", hidden: true, children: renderIconStack(pending, resolve, {
611
+ baseProps: {
612
+ ...drawProps,
613
+ width: "1em",
614
+ height: "1em",
615
+ title: labelOf(pending[0].icon),
616
+ ...iconHtmlAttributes(pending)
617
+ }
618
+ }) }),
619
+ isOpen && /* @__PURE__ */ jsxs(
620
+ "div",
621
+ {
622
+ ref: dropdownRef,
623
+ className: `ntd-picker-dropdown${showGrid ? "" : " ntd-picker-dropdown--stack"}${placement.up ? " ntd-picker-dropdown--up" : ""}${placement.end ? " ntd-picker-dropdown--end" : ""}`,
624
+ style: placement.maxHeight ? { maxHeight: placement.maxHeight } : void 0,
625
+ role: "dialog",
626
+ "aria-label": t("iconPicker.selectIcon", "Select icon…"),
627
+ children: [
628
+ canStack && (picking ? /* @__PURE__ */ jsxs("div", { className: "ntd-picker-banner", children: [
629
+ /* @__PURE__ */ jsx(
630
+ "button",
631
+ {
632
+ type: "button",
633
+ className: "ntd-picker-back",
634
+ onClick: () => setPicking(null),
635
+ "aria-label": t("iconPicker.back", "Back"),
636
+ title: t("iconPicker.back", "Back"),
637
+ children: /* @__PURE__ */ jsx(NtdArrowLeft, { size: 14, variant: "outline" })
638
+ }
639
+ ),
640
+ /* @__PURE__ */ jsx("span", { children: t("iconPicker.pickOverlay", "Pick an icon to put on {icon}").replace(
641
+ "{icon}",
642
+ pickingInto ? labelOf(pickingInto.icon) : base ? labelOf(base.icon) : ""
643
+ ) })
644
+ ] }) : /* @__PURE__ */ jsxs(
645
+ "div",
646
+ {
647
+ className: "ntd-picker-views",
648
+ ref: viewsRef,
649
+ role: "tablist",
650
+ "aria-label": t("iconPicker.selectIcon", "Select icon…"),
651
+ children: [
652
+ /* @__PURE__ */ jsx(
653
+ "button",
654
+ {
655
+ type: "button",
656
+ role: "tab",
657
+ id: `${panelId}-icons`,
658
+ "aria-selected": view === "icons",
659
+ "aria-controls": `${panelId}-panel`,
660
+ className: `ntd-picker-view${view === "icons" ? " ntd-picker-view--active" : ""}`,
661
+ onClick: () => setView("icons"),
662
+ children: t("iconPicker.tabIcon", "Icon")
663
+ }
664
+ ),
665
+ /* @__PURE__ */ jsxs(
666
+ "button",
667
+ {
668
+ type: "button",
669
+ role: "tab",
670
+ id: `${panelId}-stack`,
671
+ "aria-selected": view === "stack",
672
+ "aria-controls": `${panelId}-panel`,
673
+ className: `ntd-picker-view${view === "stack" ? " ntd-picker-view--active" : ""}`,
674
+ onClick: () => setView("stack"),
675
+ disabled: !base,
676
+ title: base ? void 0 : t("iconPicker.stackNeedsIcon", "Pick an icon first"),
677
+ children: [
678
+ t("iconPicker.tabStack", "Overlays"),
679
+ overlays > 0 && /* @__PURE__ */ jsx("span", { className: "ntd-picker-tab-count", children: overlays })
680
+ ]
681
+ }
682
+ )
683
+ ]
684
+ }
685
+ )),
686
+ /* @__PURE__ */ jsxs(
687
+ "div",
688
+ {
689
+ className: "ntd-picker-panel",
690
+ id: `${panelId}-panel`,
691
+ ...canStack && !picking ? {
692
+ role: "tabpanel",
693
+ "aria-labelledby": `${panelId}-${view}`
694
+ } : {},
695
+ children: [
696
+ showGrid && /* @__PURE__ */ jsxs(Fragment, { children: [
697
+ /* @__PURE__ */ jsxs("div", { className: "ntd-picker-search", children: [
698
+ /* @__PURE__ */ jsx("span", { className: "ntd-picker-search-icon", children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
699
+ /* @__PURE__ */ jsx(
700
+ "input",
701
+ {
702
+ ref: searchInputRef,
703
+ type: "text",
704
+ placeholder: t("iconPicker.searchIcons", "Search icons…"),
705
+ value: search,
706
+ onChange: (e) => setSearch(e.target.value),
707
+ "aria-label": t("iconPicker.searchIcons2", "Search icons")
595
708
  }
596
- : {}), children: [showGrid && (jsxs(Fragment, { children: [jsxs("div", { className: "ntd-picker-search", children: [jsx("span", { className: "ntd-picker-search-icon", children: jsx(SearchIcon, {}) }), jsx("input", { ref: searchInputRef, type: "text", placeholder: t('iconPicker.searchIcons', 'Search icons…'), value: search, onChange: (e) => setSearch(e.target.value), "aria-label": t('iconPicker.searchIcons2', 'Search icons') })] }), (allCategories.length > 1 || showFavorites || showHistory) && (jsxs("div", { className: "ntd-picker-tabs", ref: tablistRef, role: "tablist", children: [jsx("button", { type: "button", className: `ntd-picker-tab${activeCategory === null ? ' ntd-picker-tab--active' : ''}`, onClick: () => setActiveCategory(null), role: "tab", "aria-selected": activeCategory === null, children: t('iconPicker.all', 'All') }), showFavorites && (jsxs("button", { type: "button", className: `ntd-picker-tab ntd-picker-tab-special${activeCategory === '__favorites__' ? ' ntd-picker-tab--active' : ''}`, onClick: () => setActiveCategory('__favorites__'), role: "tab", "aria-selected": activeCategory === '__favorites__', title: t('iconPicker.favorites', 'Favorites'), children: [jsx(StarIcon, { filled: true }), " ", t('iconPicker.favorites', 'Favorites'), favorites.length > 0 && (jsx("span", { className: "ntd-picker-tab-count", children: favorites.length }))] })), showHistory && history.length > 0 && (jsxs("button", { type: "button", className: `ntd-picker-tab ntd-picker-tab-special${activeCategory === '__history__' ? ' ntd-picker-tab--active' : ''}`, onClick: () => setActiveCategory('__history__'), role: "tab", "aria-selected": activeCategory === '__history__', title: t('iconPicker.recentlyUsed', 'Recently used'), children: [jsx(ClockIcon, {}), " ", t('iconPicker.recent', 'Recent')] })), allCategories.map((cat) => (jsx("button", { type: "button", className: `ntd-picker-tab${activeCategory === cat ? ' ntd-picker-tab--active' : ''}`, onClick: () => setActiveCategory(cat), role: "tab", "aria-selected": activeCategory === cat, children: cat }, cat)))] })), jsx("div", { className: "ntd-picker-grid-wrap", ref: gridWrapRef, role: "listbox", "aria-label": t('iconPicker.icons', 'Icons'), "aria-multiselectable": multiSelect || undefined, children: filtered.length === 0 ? (jsxs("div", { className: "ntd-picker-empty", children: [jsx("div", { className: "ntd-picker-empty-icon", children: jsx(NtdInbox, { size: 28, variant: "outline" }) }), activeCategory === '__favorites__'
597
- ? t('iconPicker.noFavoritesYetClickThe', 'No favorites yet. Click the star icon to add.')
598
- : activeCategory === '__history__'
599
- ? t('iconPicker.noRecentIcons', 'No recent icons.')
600
- : t('iconPicker.noIconsFound', 'No icons found')] })) : (jsxs(Fragment, { children: [virtualized && visibleRange.start > 0 && (jsx("div", { style: { height: Math.floor(visibleRange.start / 6) * 48 } })), Array.from(grouped.entries()).map(([category, entries]) => (jsxs("div", { children: [!activeCategory && (jsx("div", { className: "ntd-picker-category-label", children: category })), jsx("div", { className: `ntd-picker-grid${optionDisplayMode === 'icon-name' ? ' ntd-picker-grid--names' : ''}`, children: entries.map((entry) => {
601
- const isSelected = !picking && selectedValues.includes(entry.name);
602
- const isFavorite = favorites.includes(entry.name);
603
- const label = entryLabel(entry);
604
- const cell = (jsxs("button", { type: "button", className: `ntd-picker-cell${isSelected ? ' ntd-picker-cell--selected' : ''}${multiSelect ? ' ntd-picker-cell--multi-select' : ''}${enableDragAndDrop ? ' ntd-picker-cell--draggable' : ''}`, onClick: () => handleSelect(entry), "data-name": label, role: "option", "aria-selected": isSelected, "aria-label": label, title: label, draggable: enableDragAndDrop, onDragStart: enableDragAndDrop
605
- ? (e) => handleDragStart(entry, e)
606
- : undefined, onDragEnd: enableDragAndDrop ? (e) => handleDragEnd(entry, e) : undefined, children: [jsx(entry.Component, { ...drawProps, size: gridIconSize }), optionDisplayMode === 'icon-name' && (jsx("span", { className: "ntd-picker-cell-name", children: label })), multiSelect && isSelected && (jsx("span", { className: "ntd-picker-cell-check", children: jsx(CheckIcon, {}) }))] }, entry.name));
607
- if (!showFavorites) {
608
- return cell;
609
- }
610
- // the star beside the cell, not in it — a button in a button is
611
- // invalid HTML (1.3.9)
612
- const favLabel = isFavorite
613
- ? t('iconPicker.removeFromFavorites', 'Remove from favorites')
614
- : t('iconPicker.addToFavorites', 'Add to favorites');
615
- return (jsxs("div", { className: "ntd-picker-cell-wrap", children: [cell, jsx("button", { type: "button", tabIndex: -1, className: `ntd-picker-cell-fav${isFavorite ? ' ntd-picker-cell-fav--active' : ''}`, onClick: (e) => handleFavoriteClick(e, entry.name), "aria-label": `${favLabel}: ${label}`, title: favLabel, children: jsx(StarIcon, { filled: isFavorite }) })] }, entry.name));
616
- }) })] }, category))), virtualized && visibleRange.end < filtered.length && (jsx("div", { style: {
617
- height: Math.ceil((filtered.length - visibleRange.end) / 6) * 48,
618
- } }))] })) })] })), canStack && view === 'stack' && base && (jsx("div", { className: "ntd-picker-stack", hidden: !!picking, children: jsx(NiceIconStacker, { ref: stackerRef, icons: resolve, layers: shown, onLayersChange: commit, onAddClick: (target) => {
619
- var _a;
620
- setSearch('');
621
- setActiveCategory(null);
622
- setPicking({ target: (_a = target === null || target === void 0 ? void 0 : target.id) !== null && _a !== void 0 ? _a : base.id });
623
- }, previewProps: drawProps, iconLabel: labelOf, showCode: false, keepBase: true }) }))] }), !picking && (jsxs("div", { className: "ntd-picker-footer", children: [jsx("span", { className: "ntd-picker-count", children: showGrid && (jsxs(Fragment, { children: [(filtered.length === 1
624
- ? t('iconPicker.iconCountOne', '{count} icon')
625
- : t('iconPicker.iconCountMany', '{count} icons')).replace('{count}', String(filtered.length)), multiSelect && selectedValues.length > 0 && (jsxs(Fragment, { children: [' ', t('iconPicker.selected', '• {selectedValues} selected').replace('{selectedValues}', String(selectedValues.length))] })), maxSelections && multiSelect && (jsxs(Fragment, { children: [' ', t('iconPicker.max', '(max {maxSelections})').replace('{maxSelections}', String(maxSelections))] }))] })) }), clearable && hasSelection && (jsx("button", { type: "button", className: "ntd-picker-clear", onClick: handleClear, children: multiSelect
626
- ? t('iconPicker.clearAll', 'Clear all')
627
- : t('iconPicker.clearSelection', 'Clear selection') })), (multiSelect || view === 'stack') && (jsx("button", { type: "button", className: "ntd-picker-done", onClick: () => setIsOpen(false), children: t('iconPicker.done', 'Done') }))] }))] }))] }));
628
- });
629
-
630
- export { NiceIconPicker };
709
+ )
710
+ ] }),
711
+ (allCategories.length > 1 || showFavorites || showHistory) && /* @__PURE__ */ jsxs("div", { className: "ntd-picker-tabs", ref: tablistRef, role: "tablist", children: [
712
+ /* @__PURE__ */ jsx(
713
+ "button",
714
+ {
715
+ type: "button",
716
+ className: `ntd-picker-tab${activeCategory === null ? " ntd-picker-tab--active" : ""}`,
717
+ onClick: () => setActiveCategory(null),
718
+ role: "tab",
719
+ "aria-selected": activeCategory === null,
720
+ children: t("iconPicker.all", "All")
721
+ }
722
+ ),
723
+ showFavorites && /* @__PURE__ */ jsxs(
724
+ "button",
725
+ {
726
+ type: "button",
727
+ className: `ntd-picker-tab ntd-picker-tab-special${activeCategory === "__favorites__" ? " ntd-picker-tab--active" : ""}`,
728
+ onClick: () => setActiveCategory("__favorites__"),
729
+ role: "tab",
730
+ "aria-selected": activeCategory === "__favorites__",
731
+ title: t("iconPicker.favorites", "Favorites"),
732
+ children: [
733
+ /* @__PURE__ */ jsx(StarIcon, { filled: true }),
734
+ " ",
735
+ t("iconPicker.favorites", "Favorites"),
736
+ favorites.length > 0 && /* @__PURE__ */ jsx("span", { className: "ntd-picker-tab-count", children: favorites.length })
737
+ ]
738
+ }
739
+ ),
740
+ showHistory && history.length > 0 && /* @__PURE__ */ jsxs(
741
+ "button",
742
+ {
743
+ type: "button",
744
+ className: `ntd-picker-tab ntd-picker-tab-special${activeCategory === "__history__" ? " ntd-picker-tab--active" : ""}`,
745
+ onClick: () => setActiveCategory("__history__"),
746
+ role: "tab",
747
+ "aria-selected": activeCategory === "__history__",
748
+ title: t("iconPicker.recentlyUsed", "Recently used"),
749
+ children: [
750
+ /* @__PURE__ */ jsx(ClockIcon, {}),
751
+ " ",
752
+ t("iconPicker.recent", "Recent")
753
+ ]
754
+ }
755
+ ),
756
+ allCategories.map((cat) => /* @__PURE__ */ jsx(
757
+ "button",
758
+ {
759
+ type: "button",
760
+ className: `ntd-picker-tab${activeCategory === cat ? " ntd-picker-tab--active" : ""}`,
761
+ onClick: () => setActiveCategory(cat),
762
+ role: "tab",
763
+ "aria-selected": activeCategory === cat,
764
+ children: cat
765
+ },
766
+ cat
767
+ ))
768
+ ] }),
769
+ /* @__PURE__ */ jsx(
770
+ "div",
771
+ {
772
+ className: "ntd-picker-grid-wrap",
773
+ ref: gridWrapRef,
774
+ role: "listbox",
775
+ "aria-label": t("iconPicker.icons", "Icons"),
776
+ "aria-multiselectable": multiSelect || void 0,
777
+ children: filtered.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "ntd-picker-empty", children: [
778
+ /* @__PURE__ */ jsx("div", { className: "ntd-picker-empty-icon", children: /* @__PURE__ */ jsx(NtdInbox, { size: 28, variant: "outline" }) }),
779
+ activeCategory === "__favorites__" ? t(
780
+ "iconPicker.noFavoritesYetClickThe",
781
+ "No favorites yet. Click the star icon to add."
782
+ ) : activeCategory === "__history__" ? t("iconPicker.noRecentIcons", "No recent icons.") : t("iconPicker.noIconsFound", "No icons found")
783
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
784
+ virtualized && visibleRange.start > 0 && /* @__PURE__ */ jsx("div", { style: { height: Math.floor(visibleRange.start / 6) * 48 } }),
785
+ Array.from(grouped.entries()).map(([category, entries]) => /* @__PURE__ */ jsxs("div", { children: [
786
+ !activeCategory && /* @__PURE__ */ jsx("div", { className: "ntd-picker-category-label", children: category }),
787
+ /* @__PURE__ */ jsx(
788
+ "div",
789
+ {
790
+ className: `ntd-picker-grid${optionDisplayMode === "icon-name" ? " ntd-picker-grid--names" : ""}`,
791
+ children: entries.map((entry) => {
792
+ const isSelected = !picking && selectedValues.includes(entry.name);
793
+ const isFavorite = favorites.includes(entry.name);
794
+ const label = entryLabel(entry);
795
+ const cell = /* @__PURE__ */ jsxs(
796
+ "button",
797
+ {
798
+ type: "button",
799
+ className: `ntd-picker-cell${isSelected ? " ntd-picker-cell--selected" : ""}${multiSelect ? " ntd-picker-cell--multi-select" : ""}${enableDragAndDrop ? " ntd-picker-cell--draggable" : ""}`,
800
+ onClick: () => handleSelect(entry),
801
+ "data-name": label,
802
+ role: "option",
803
+ "aria-selected": isSelected,
804
+ "aria-label": label,
805
+ title: label,
806
+ draggable: enableDragAndDrop,
807
+ onDragStart: enableDragAndDrop ? (e) => handleDragStart(entry, e) : void 0,
808
+ onDragEnd: enableDragAndDrop ? (e) => handleDragEnd(entry, e) : void 0,
809
+ children: [
810
+ /* @__PURE__ */ jsx(entry.Component, { ...drawProps, size: gridIconSize }),
811
+ optionDisplayMode === "icon-name" && /* @__PURE__ */ jsx("span", { className: "ntd-picker-cell-name", children: label }),
812
+ multiSelect && isSelected && /* @__PURE__ */ jsx("span", { className: "ntd-picker-cell-check", children: /* @__PURE__ */ jsx(CheckIcon, {}) })
813
+ ]
814
+ },
815
+ entry.name
816
+ );
817
+ if (!showFavorites) {
818
+ return cell;
819
+ }
820
+ const favLabel = isFavorite ? t("iconPicker.removeFromFavorites", "Remove from favorites") : t("iconPicker.addToFavorites", "Add to favorites");
821
+ return /* @__PURE__ */ jsxs("div", { className: "ntd-picker-cell-wrap", children: [
822
+ cell,
823
+ /* @__PURE__ */ jsx(
824
+ "button",
825
+ {
826
+ type: "button",
827
+ tabIndex: -1,
828
+ className: `ntd-picker-cell-fav${isFavorite ? " ntd-picker-cell-fav--active" : ""}`,
829
+ onClick: (e) => handleFavoriteClick(e, entry.name),
830
+ "aria-label": `${favLabel}: ${label}`,
831
+ title: favLabel,
832
+ children: /* @__PURE__ */ jsx(StarIcon, { filled: isFavorite })
833
+ }
834
+ )
835
+ ] }, entry.name);
836
+ })
837
+ }
838
+ )
839
+ ] }, category)),
840
+ virtualized && visibleRange.end < filtered.length && /* @__PURE__ */ jsx(
841
+ "div",
842
+ {
843
+ style: {
844
+ height: Math.ceil((filtered.length - visibleRange.end) / 6) * 48
845
+ }
846
+ }
847
+ )
848
+ ] })
849
+ }
850
+ )
851
+ ] }),
852
+ canStack && view === "stack" && base && /* @__PURE__ */ jsx("div", { className: "ntd-picker-stack", hidden: !!picking, children: /* @__PURE__ */ jsx(
853
+ NiceIconStacker,
854
+ {
855
+ ref: stackerRef,
856
+ icons: resolve,
857
+ layers: shown,
858
+ onLayersChange: commit,
859
+ onAddClick: (target) => {
860
+ setSearch("");
861
+ setActiveCategory(null);
862
+ setPicking({ target: target?.id ?? base.id });
863
+ },
864
+ previewProps: drawProps,
865
+ iconLabel: labelOf,
866
+ showCode: false,
867
+ keepBase: true
868
+ }
869
+ ) })
870
+ ]
871
+ }
872
+ ),
873
+ !picking && /* @__PURE__ */ jsxs("div", { className: "ntd-picker-footer", children: [
874
+ /* @__PURE__ */ jsx("span", { className: "ntd-picker-count", children: showGrid && /* @__PURE__ */ jsxs(Fragment, { children: [
875
+ (filtered.length === 1 ? t("iconPicker.iconCountOne", "{count} icon") : t("iconPicker.iconCountMany", "{count} icons")).replace("{count}", String(filtered.length)),
876
+ multiSelect && selectedValues.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
877
+ " ",
878
+ t("iconPicker.selected", "• {selectedValues} selected").replace(
879
+ "{selectedValues}",
880
+ String(selectedValues.length)
881
+ )
882
+ ] }),
883
+ maxSelections && multiSelect && /* @__PURE__ */ jsxs(Fragment, { children: [
884
+ " ",
885
+ t("iconPicker.max", "(max {maxSelections})").replace(
886
+ "{maxSelections}",
887
+ String(maxSelections)
888
+ )
889
+ ] })
890
+ ] }) }),
891
+ clearable && hasSelection && /* @__PURE__ */ jsx("button", { type: "button", className: "ntd-picker-clear", onClick: handleClear, children: multiSelect ? t("iconPicker.clearAll", "Clear all") : t("iconPicker.clearSelection", "Clear selection") }),
892
+ (multiSelect || view === "stack") && /* @__PURE__ */ jsx(
893
+ "button",
894
+ {
895
+ type: "button",
896
+ className: "ntd-picker-done",
897
+ onClick: () => setIsOpen(false),
898
+ children: t("iconPicker.done", "Done")
899
+ }
900
+ )
901
+ ] })
902
+ ]
903
+ }
904
+ )
905
+ ]
906
+ }
907
+ );
908
+ }
909
+ );
910
+ export {
911
+ NiceIconPicker
912
+ };