@nice2dev/icons 1.3.8 → 1.3.10

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 (44) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/dist/cjs/NiceIconPicker.js +310 -92
  3. package/dist/cjs/NiceIconStacker.js +137 -120
  4. package/dist/cjs/NiceIconValue.js +44 -0
  5. package/dist/cjs/client.js +11 -0
  6. package/dist/cjs/iconHtml.js +104 -0
  7. package/dist/cjs/iconStack.js +74 -1
  8. package/dist/cjs/iconValue.js +473 -0
  9. package/dist/cjs/icons/actions.js +39 -8
  10. package/dist/cjs/icons/files.js +23 -2
  11. package/dist/cjs/icons/fintech.js +12 -5
  12. package/dist/cjs/icons/index.js +1 -0
  13. package/dist/cjs/icons/media.js +7 -4
  14. package/dist/cjs/icons/realestate.js +8 -7
  15. package/dist/cjs/icons/shapes.js +55 -4
  16. package/dist/cjs/index.js +12 -0
  17. package/dist/cjs/nicetodev-icons.css +1 -1
  18. package/dist/esm/NiceIconPicker.js +313 -95
  19. package/dist/esm/NiceIconStacker.js +140 -123
  20. package/dist/esm/NiceIconValue.js +41 -0
  21. package/dist/esm/client.js +4 -1
  22. package/dist/esm/iconHtml.js +100 -0
  23. package/dist/esm/iconStack.js +72 -2
  24. package/dist/esm/iconValue.js +470 -0
  25. package/dist/esm/icons/actions.js +39 -9
  26. package/dist/esm/icons/files.js +23 -2
  27. package/dist/esm/icons/fintech.js +12 -5
  28. package/dist/esm/icons/index.js +1 -1
  29. package/dist/esm/icons/media.js +8 -5
  30. package/dist/esm/icons/realestate.js +8 -7
  31. package/dist/esm/icons/shapes.js +55 -4
  32. package/dist/esm/index.js +5 -2
  33. package/dist/esm/nicetodev-icons.css +1 -1
  34. package/dist/types/NiceIconPicker.d.ts +72 -20
  35. package/dist/types/NiceIconStacker.d.ts +30 -9
  36. package/dist/types/NiceIconValue.d.ts +40 -0
  37. package/dist/types/client.d.ts +8 -2
  38. package/dist/types/iconHtml.d.ts +22 -0
  39. package/dist/types/iconStack.d.ts +18 -0
  40. package/dist/types/iconValue.d.ts +22 -0
  41. package/dist/types/icons/actions.d.ts +1 -0
  42. package/dist/types/icons/index.d.ts +1 -1
  43. package/dist/types/index.d.ts +8 -2
  44. package/package.json +2 -2
@@ -3,19 +3,46 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var uiCore = require('@nice2dev/ui-core');
5
5
  var React = require('react');
6
+ var iconHtml = require('./iconHtml.js');
6
7
  var actions = require('./icons/actions.js');
7
8
  var files = require('./icons/files.js');
8
9
  var media = require('./icons/media.js');
9
10
  var navigation = require('./icons/navigation.js');
10
11
  var ui = require('./icons/ui.js');
12
+ var iconStack = require('./iconStack.js');
13
+ var iconValue = require('./iconValue.js');
14
+ var NiceIconStacker = require('./NiceIconStacker.js');
11
15
 
12
16
  const MAX_HISTORY_ITEMS = 20;
17
+ /** `useLayoutEffect` in the browser, `useEffect` on the server (no warning there). */
18
+ const useIsoLayoutEffect = typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect;
13
19
  /* ─── Small icons: the package's own (until 1.2.0 these were pasted SVGs) ─── */
14
20
  const Chevron = () => jsxRuntime.jsx(navigation.NtdChevronDown, { size: 12, variant: "outline" });
15
21
  const SearchIcon = () => jsxRuntime.jsx(actions.NtdSearch, { size: 14, variant: "outline" });
16
22
  const StarIcon = ({ filled }) => (jsxRuntime.jsx(ui.NtdStar, { size: 12, variant: filled ? 'solid' : 'outline' }));
17
23
  const ClockIcon = () => jsxRuntime.jsx(ui.NtdClock, { size: 12, variant: "outline" });
18
24
  const CheckIcon = () => jsxRuntime.jsx(actions.NtdCheck, { size: 10, variant: "outline" });
25
+ const NO_NAMES = [];
26
+ const NO_LAYERS = [];
27
+ /** The accent the picker draws with: the page's background, white without a theme. */
28
+ const PAGE_ACCENT = 'var(--nice-bg-primary, white)';
29
+ /** An entry's name as shown: its label, else the export name without `Ntd`. */
30
+ const entryLabel = (entry) => { var _a; return (_a = entry.label) !== null && _a !== void 0 ? _a : iconStack.iconDisplayName(entry.name); };
31
+ /** The part of the window an element's popup can show in: the window, cut by every ancestor that clips or scrolls. */
32
+ function visibleBounds(el) {
33
+ const bounds = { top: 0, bottom: window.innerHeight, left: 0, right: window.innerWidth };
34
+ for (let p = el.parentElement; p && p !== document.body; p = p.parentElement) {
35
+ const style = window.getComputedStyle(p);
36
+ if (style.overflowX !== 'visible' || style.overflowY !== 'visible') {
37
+ const r = p.getBoundingClientRect();
38
+ bounds.top = Math.max(bounds.top, r.top);
39
+ bounds.bottom = Math.min(bounds.bottom, r.bottom);
40
+ bounds.left = Math.max(bounds.left, r.left);
41
+ bounds.right = Math.min(bounds.right, r.right);
42
+ }
43
+ }
44
+ return bounds;
45
+ }
19
46
  /* ─── localStorage helpers ─── */
20
47
  function loadFromStorage(key, fallback) {
21
48
  if (typeof window === 'undefined') {
@@ -46,40 +73,43 @@ function saveToStorage(key, value) {
46
73
  /**
47
74
  * A fully-featured icon picker dropdown component.
48
75
  *
49
- * Provides a trigger button that opens a searchable, categorized icon grid.
50
- * Supports controlled selection, keyboard navigation (Escape to close),
51
- * click-outside dismissal, and an imperative handle for programmatic control.
76
+ * Provides a trigger button that opens a searchable, categorized icon grid. The value binds both
77
+ * ways — `value` / `defaultValue` + `onChange` (1.3.9) — as the icon's export name, a stack as
78
+ * short code (`Folder(Star@tr)`), JSON or HTML (`valueFormat`). With `stackable` the panel gets
79
+ * an "Overlays" tab where icons are stacked on the chosen one. Names show without `Ntd`; the
80
+ * trigger shows the icon alone or the icon and its name (`displayMode`). Keyboard: arrows in the
81
+ * grid, Escape to close.
52
82
  *
53
83
  * **Styling:** Uses CSS variables for theming (see `NiceIconPicker.css`).
54
84
  * Override `--ntd-picker-bg`, `--ntd-picker-accent`, etc. on a parent element.
55
85
  *
56
86
  * @example
57
87
  * ```tsx
58
- * import { NiceIconPicker } from '@nicetodev/icons';
59
- *
60
- * function IconSelectField({ icons }) {
61
- * const [selected, setSelected] = useState<string | null>(null);
88
+ * import { NiceIconPicker } from '@nice2dev/icons';
62
89
  *
63
- * return (
64
- * <NiceIconPicker
65
- * icons={icons}
66
- * value={selected}
67
- * onChange={(name) => setSelected(name)}
68
- * variant="solid"
69
- * placeholder="Pick icon..."
70
- * />
71
- * );
90
+ * function IconField({ icons, value, onChange }) {
91
+ * // stores `NtdFolder`, or `Folder(Star@tr)` with a star stacked in its corner
92
+ * return <NiceIconPicker icons={icons} value={value} onChange={onChange} stackable />;
72
93
  * }
73
94
  * ```
74
95
  */
75
- const NiceIconPicker = /* @__PURE__ */ React.forwardRef(function NiceIconPicker({ icons, value = null, values = [], onChange, onChangeMulti, multiSelect = false, maxSelections, placeholder, variant = 'solid', 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) {
96
+ const NiceIconPicker = /* @__PURE__ */ React.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) {
76
97
  var _a;
77
98
  const tablistRef = React.useRef(null);
78
99
  uiCore.useRovingFocus(tablistRef, { orientation: 'horizontal', wrap: true, activateOnMove: true });
100
+ const viewsRef = React.useRef(null);
101
+ uiCore.useRovingFocus(viewsRef, { orientation: 'horizontal', activateOnMove: true });
79
102
  const { t } = uiCore.useNiceTranslation();
80
103
  const [isOpen, setIsOpen] = React.useState(false);
81
104
  const [search, setSearch] = React.useState('');
82
105
  const [activeCategory, setActiveCategory] = React.useState(null);
106
+ // the panel's view: the icon grid or the stack; `picking` = the grid chooses an icon to stack
107
+ const [view, setView] = React.useState('icons');
108
+ const [picking, setPicking] = React.useState(null);
109
+ const panelId = React.useId();
110
+ const valueFormat = valueFormatProp !== null && valueFormatProp !== void 0 ? valueFormatProp : 'name';
111
+ const canStack = stackable && !multiSelect;
112
+ const drawProps = React.useMemo(() => ({ accentColor: PAGE_ACCENT, ...iconProps, variant }), [iconProps, variant]);
83
113
  // Virtualization state
84
114
  const [visibleRange, setVisibleRange] = React.useState({ start: 0, end: virtualizedItemCount });
85
115
  const gridWrapRef = React.useRef(null);
@@ -88,35 +118,125 @@ const NiceIconPicker = /* @__PURE__ */ React.forwardRef(function NiceIconPicker(
88
118
  const historyKey = `${storageKeyPrefix}-history`;
89
119
  const [favorites, setFavorites] = React.useState(() => loadFromStorage(favoritesKey, []));
90
120
  const [history, setHistory] = React.useState(() => loadFromStorage(historyKey, []));
91
- // Multi-select state (internal when uncontrolled)
92
- const [internalValues, setInternalValues] = React.useState([]);
93
- const selectedValues = React.useMemo(() => (multiSelect ? (values.length > 0 ? values : internalValues) : value ? [value] : []), [multiSelect, values, internalValues, value]);
121
+ // The value binds both ways (1.3.9): controlled by `value`, or kept here from `defaultValue`.
122
+ const [value, setValue] = uiCore.useControllableState({
123
+ value: valueProp,
124
+ defaultValue,
125
+ fallbackValue: null,
126
+ componentName: 'NiceIconPicker',
127
+ });
128
+ const [values, setValues] = uiCore.useControllableState({
129
+ value: valuesProp,
130
+ defaultValue: defaultValues,
131
+ fallbackValue: NO_NAMES,
132
+ componentName: 'NiceIconPicker',
133
+ });
94
134
  const rootRef = React.useRef(null);
95
135
  const searchInputRef = React.useRef(null);
136
+ const stackerRef = React.useRef(null);
137
+ const serializerRef = React.useRef(null);
138
+ /* ─── Icons by name: exact, or without the Ntd a stored value may lack ─── */
139
+ const byName = React.useMemo(() => new Map(icons.map((e) => [e.name, e])), [icons]);
140
+ const entryOf = React.useCallback((n) => { var _a; return (n ? ((_a = byName.get(n)) !== null && _a !== void 0 ? _a : byName.get(`Ntd${n}`)) : undefined); }, [byName]);
141
+ const labelOf = React.useCallback((n) => {
142
+ const entry = entryOf(n);
143
+ return entry ? entryLabel(entry) : iconStack.iconDisplayName(n);
144
+ }, [entryOf]);
145
+ const resolve = React.useCallback((n) => { var _a; return (_a = entryOf(n)) === null || _a === void 0 ? void 0 : _a.Component; }, [entryOf]);
146
+ /* ─── The single value as a stack — a plain icon is a stack of one ─── */
147
+ // The last value emitted with its layers: read back, the text would number the layers anew
148
+ // (it keeps no ids) and the stacker would lose the layer it has selected.
149
+ const [emitted, setEmitted] = React.useState(null);
150
+ const model = React.useMemo(() => {
151
+ if (!value) {
152
+ return NO_LAYERS;
153
+ }
154
+ if ((emitted === null || emitted === void 0 ? void 0 : emitted.value) === value) {
155
+ return emitted.layers;
156
+ }
157
+ // a name as is first: an older value may hold one of the code's signs
158
+ const exact = value.includes('<') ? undefined : entryOf(value);
159
+ if (exact) {
160
+ return [{ id: '1', icon: exact.name }];
161
+ }
162
+ const layers = iconValue.parseIconValue(value);
163
+ if (!layers || !entryOf(layers[0].icon)) {
164
+ return NO_LAYERS;
165
+ }
166
+ // the code names icons without `Ntd`; the stacker and the grid know them by entry name
167
+ return layers.map((l) => {
168
+ const entry = entryOf(l.icon);
169
+ return entry && entry.name !== l.icon ? { ...l, icon: entry.name } : l;
170
+ });
171
+ }, [value, emitted, entryOf]);
172
+ // A stack waiting to be drawn into HTML: shown at once, emitted once drawn (below).
173
+ const [pending, setPending] = React.useState(null);
174
+ const shown = pending !== null && pending !== void 0 ? pending : model;
175
+ const base = shown[0];
176
+ /** The name a stack's code gives an icon: without `Ntd`, unless that is another icon's name. */
177
+ const shortName = React.useCallback((n) => {
178
+ const short = iconStack.iconDisplayName(n);
179
+ return short !== n && !byName.has(short) ? short : n;
180
+ }, [byName]);
181
+ const onChangeRef = React.useRef(onChange);
182
+ onChangeRef.current = onChange;
183
+ const emit = React.useCallback((next, layers) => {
184
+ var _a, _b, _c;
185
+ setEmitted(next === null ? null : { value: next, layers });
186
+ setValue(next);
187
+ (_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);
188
+ }, [setValue, entryOf]);
189
+ /** Sets the single value to a stack: written as `valueFormat` holds it, HTML once drawn. */
190
+ const commit = React.useCallback((layers) => {
191
+ if (layers.length === 0) {
192
+ setPending(null);
193
+ emit(null, NO_LAYERS);
194
+ }
195
+ else if (valueFormat === 'html') {
196
+ setPending(layers);
197
+ }
198
+ else {
199
+ emit(iconValue.formatIconValue(layers, valueFormat, { shortName }), layers);
200
+ }
201
+ }, [valueFormat, emit, shortName]);
202
+ // The HTML is the icon as drawn: the hidden copy below renders the pending stack, and its
203
+ // markup — ids renamed after the drawing — is the new value, emitted before the paint.
204
+ useIsoLayoutEffect(() => {
205
+ var _a;
206
+ if (!pending) {
207
+ return;
208
+ }
209
+ const svg = (_a = serializerRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('svg');
210
+ setPending(null);
211
+ if (svg) {
212
+ emit(iconHtml.scopeIconHtmlIds(svg.outerHTML), pending);
213
+ }
214
+ }, [pending, emit]);
215
+ const selectedValues = React.useMemo(() => (multiSelect ? values : base ? [base.icon] : NO_NAMES), [multiSelect, values, base]);
96
216
  /* ─── Favorites helpers ─── */
97
- const addFavorite = React.useCallback((name) => {
217
+ const addFavorite = React.useCallback((favorite) => {
98
218
  setFavorites((prev) => {
99
- if (prev.includes(name)) {
219
+ if (prev.includes(favorite)) {
100
220
  return prev;
101
221
  }
102
- const next = [...prev, name];
222
+ const next = [...prev, favorite];
103
223
  saveToStorage(favoritesKey, next);
104
224
  return next;
105
225
  });
106
226
  }, [favoritesKey]);
107
- const removeFavorite = React.useCallback((name) => {
227
+ const removeFavorite = React.useCallback((favorite) => {
108
228
  setFavorites((prev) => {
109
- const next = prev.filter((n) => n !== name);
229
+ const next = prev.filter((n) => n !== favorite);
110
230
  saveToStorage(favoritesKey, next);
111
231
  return next;
112
232
  });
113
233
  }, [favoritesKey]);
114
- const toggleFavorite = React.useCallback((name) => {
115
- if (favorites.includes(name)) {
116
- removeFavorite(name);
234
+ const toggleFavorite = React.useCallback((favorite) => {
235
+ if (favorites.includes(favorite)) {
236
+ removeFavorite(favorite);
117
237
  }
118
238
  else {
119
- addFavorite(name);
239
+ addFavorite(favorite);
120
240
  }
121
241
  }, [favorites, addFavorite, removeFavorite]);
122
242
  const clearFavorites = React.useCallback(() => {
@@ -124,10 +244,10 @@ const NiceIconPicker = /* @__PURE__ */ React.forwardRef(function NiceIconPicker(
124
244
  saveToStorage(favoritesKey, []);
125
245
  }, [favoritesKey]);
126
246
  /* ─── History helpers ─── */
127
- const addToHistory = React.useCallback((name) => {
247
+ const addToHistory = React.useCallback((used) => {
128
248
  setHistory((prev) => {
129
- const filtered = prev.filter((n) => n !== name);
130
- const next = [name, ...filtered].slice(0, MAX_HISTORY_ITEMS);
249
+ const filtered = prev.filter((n) => n !== used);
250
+ const next = [used, ...filtered].slice(0, MAX_HISTORY_ITEMS);
131
251
  saveToStorage(historyKey, next);
132
252
  return next;
133
253
  });
@@ -164,12 +284,14 @@ const NiceIconPicker = /* @__PURE__ */ React.forwardRef(function NiceIconPicker(
164
284
  }
165
285
  return result;
166
286
  }, [icons, categoriesProp]);
167
- /* ─── Filter icons ─── */
287
+ /* ─── Filter icons: by the name shown, the export name or the category ─── */
168
288
  const filtered = React.useMemo(() => {
169
289
  let items = icons;
170
- if (search.trim()) {
171
- const q = search.toLowerCase();
172
- items = items.filter((i) => i.name.toLowerCase().includes(q) || i.category.toLowerCase().includes(q));
290
+ const q = search.trim().toLowerCase();
291
+ if (q) {
292
+ items = items.filter((i) => entryLabel(i).toLowerCase().includes(q) ||
293
+ i.name.toLowerCase().includes(q) ||
294
+ i.category.toLowerCase().includes(q));
173
295
  }
174
296
  if (activeCategory === '__favorites__') {
175
297
  items = items.filter((i) => favorites.includes(i.name));
@@ -207,13 +329,8 @@ const NiceIconPicker = /* @__PURE__ */ React.forwardRef(function NiceIconPicker(
207
329
  }
208
330
  return map;
209
331
  }, [filtered, virtualized, virtualizedFiltered]);
210
- /* ─── Selected entries ─── */
211
- const selectedEntries = React.useMemo(() => {
212
- return selectedValues
213
- .map((v) => icons.find((i) => i.name === v))
214
- .filter((e) => e !== undefined);
215
- }, [icons, selectedValues]);
216
- const selectedEntry = (_a = selectedEntries[0]) !== null && _a !== void 0 ? _a : null;
332
+ /* ─── Selected entries (multi-select) ─── */
333
+ const selectedEntries = React.useMemo(() => values.map((v) => entryOf(v)).filter((e) => e !== undefined), [values, entryOf]);
217
334
  /* ─── Click outside ─── */
218
335
  React.useEffect(() => {
219
336
  if (!isOpen) {
@@ -232,6 +349,8 @@ const NiceIconPicker = /* @__PURE__ */ React.forwardRef(function NiceIconPicker(
232
349
  if (!isOpen) {
233
350
  setSearch('');
234
351
  setActiveCategory(null);
352
+ setView('icons');
353
+ setPicking(null);
235
354
  setVisibleRange({ start: 0, end: virtualizedItemCount });
236
355
  }
237
356
  }, [isOpen, virtualizedItemCount]);
@@ -239,14 +358,27 @@ const NiceIconPicker = /* @__PURE__ */ React.forwardRef(function NiceIconPicker(
239
358
  // The panel is a dialog on the overlay stack: focus on the search field when it opens, Escape
240
359
  // / B close only it (a document listener before) and focus returns to the trigger. The icon
241
360
  // grid is an APG listbox moving in two dimensions — the cells were reachable by Tab only.
361
+ // While the grid picks an icon to stack, Escape goes back to the stack instead (1.3.9).
242
362
  const dropdownRef = React.useRef(null);
243
363
  uiCore.useOverlayFocus(dropdownRef, {
244
364
  open: isOpen,
245
365
  modal: false,
246
366
  initialFocus: searchInputRef,
247
- onClose: () => setIsOpen(false),
367
+ onClose: () => (picking ? setPicking(null) : setIsOpen(false)),
248
368
  });
249
369
  uiCore.useRovingFocus(gridWrapRef, { orientation: 'grid', itemSelector: '.ntd-picker-cell' });
370
+ // into the grid to pick an icon to stack: focus the search; back: the layer it went into
371
+ const wasPicking = React.useRef(false);
372
+ React.useEffect(() => {
373
+ var _a, _b, _c;
374
+ if (picking) {
375
+ (_a = searchInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
376
+ }
377
+ else if (wasPicking.current) {
378
+ (_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();
379
+ }
380
+ wasPicking.current = !!picking;
381
+ }, [picking]);
250
382
  /* ─── Virtualization scroll handler ─── */
251
383
  React.useEffect(() => {
252
384
  if (!virtualized || !isOpen || !gridWrapRef.current) {
@@ -274,63 +406,83 @@ const NiceIconPicker = /* @__PURE__ */ React.forwardRef(function NiceIconPicker(
274
406
  }, [virtualized, isOpen, filtered.length]);
275
407
  /* ─── Handlers ─── */
276
408
  const handleSelect = React.useCallback((entry) => {
409
+ var _a;
410
+ // an icon to stack: into the layer the stacker has selected, then back to the stack
411
+ if (picking) {
412
+ (_a = stackerRef.current) === null || _a === void 0 ? void 0 : _a.add({ icon: entry.name });
413
+ setPicking(null);
414
+ setSearch('');
415
+ return;
416
+ }
277
417
  // Add to history
278
418
  if (showHistory) {
279
419
  addToHistory(entry.name);
280
420
  }
281
421
  if (multiSelect) {
282
- const isSelected = selectedValues.includes(entry.name);
422
+ const isSelected = values.includes(entry.name);
283
423
  let newValues;
284
424
  if (isSelected) {
285
- newValues = selectedValues.filter((v) => v !== entry.name);
425
+ newValues = values.filter((v) => v !== entry.name);
286
426
  }
287
427
  else {
288
- if (maxSelections && selectedValues.length >= maxSelections) {
428
+ if (maxSelections && values.length >= maxSelections) {
289
429
  return; // Max selections reached
290
430
  }
291
- newValues = [...selectedValues, entry.name];
431
+ newValues = [...values, entry.name];
292
432
  }
433
+ setValues(newValues);
293
434
  if (onChangeMulti) {
294
435
  const entries = newValues
295
- .map((v) => icons.find((i) => i.name === v))
436
+ .map((v) => entryOf(v))
296
437
  .filter((e) => e !== undefined);
297
438
  onChangeMulti(newValues, entries);
298
439
  }
299
- setInternalValues(newValues);
300
440
  // Don't close in multi-select mode
301
441
  }
302
442
  else {
303
- onChange === null || onChange === void 0 ? void 0 : onChange(entry.name, entry);
443
+ // the HTML records the variant it was drawn in; swapping the icon keeps its stack
444
+ const own = valueFormat === 'html' ? { variant } : {};
445
+ commit(canStack && base
446
+ ? [{ ...base, icon: entry.name, ...own }, ...shown.slice(1)]
447
+ : [{ id: '1', icon: entry.name, ...own }]);
304
448
  setIsOpen(false);
305
449
  }
306
450
  }, [
451
+ picking,
452
+ variant,
307
453
  multiSelect,
308
- selectedValues,
454
+ values,
455
+ setValues,
309
456
  maxSelections,
310
- icons,
311
- onChange,
457
+ entryOf,
312
458
  onChangeMulti,
313
459
  showHistory,
314
460
  addToHistory,
461
+ valueFormat,
462
+ canStack,
463
+ base,
464
+ shown,
465
+ commit,
315
466
  ]);
316
467
  const handleClear = React.useCallback((e) => {
317
468
  e.stopPropagation();
318
469
  if (multiSelect) {
470
+ setValues(NO_NAMES);
319
471
  onChangeMulti === null || onChangeMulti === void 0 ? void 0 : onChangeMulti([], []);
320
- setInternalValues([]);
321
472
  }
322
473
  else {
323
- onChange === null || onChange === void 0 ? void 0 : onChange(null, null);
474
+ commit(NO_LAYERS);
475
+ setView('icons');
324
476
  }
325
- }, [multiSelect, onChange, onChangeMulti]);
477
+ }, [multiSelect, setValues, onChangeMulti, commit]);
326
478
  const handleToggle = React.useCallback(() => {
327
479
  if (!disabled) {
328
480
  setIsOpen((o) => !o);
329
481
  }
330
482
  }, [disabled]);
331
- const handleFavoriteClick = React.useCallback((e, name) => {
483
+ const handleFavoriteClick = React.useCallback((e, favorite) => {
332
484
  e.stopPropagation();
333
- toggleFavorite(name);
485
+ toggleFavorite(favorite);
334
486
  }, [toggleFavorite]);
335
487
  /* ─── Drag & Drop handlers ─── */
336
488
  const handleDragStart = React.useCallback((entry, e) => {
@@ -338,8 +490,7 @@ const NiceIconPicker = /* @__PURE__ */ React.forwardRef(function NiceIconPicker(
338
490
  return;
339
491
  }
340
492
  // Set various data formats for maximum compatibility
341
- const name = entry.name;
342
- const readName = readableName(name);
493
+ const readName = entryLabel(entry);
343
494
  // Plain text - icon name
344
495
  e.dataTransfer.setData('text/plain', readName);
345
496
  // JSON data with full icon info
@@ -351,14 +502,9 @@ const NiceIconPicker = /* @__PURE__ */ React.forwardRef(function NiceIconPicker(
351
502
  };
352
503
  e.dataTransfer.setData('application/json', JSON.stringify(iconData));
353
504
  e.dataTransfer.setData('text/x-ntd-icon', JSON.stringify(iconData));
354
- // Try to render SVG to data URL for image dragging
505
+ // The cell's own drawing as an image
355
506
  try {
356
- const tempContainer = document.createElement('div');
357
- tempContainer.style.position = 'absolute';
358
- tempContainer.style.left = '-9999px';
359
- document.body.appendChild(tempContainer);
360
- // Use dynamic import workaround - render to string
361
- const svgElement = document.querySelector(`[data-name="${readName}"] svg`);
507
+ const svgElement = e.currentTarget.querySelector('svg');
362
508
  if (svgElement) {
363
509
  const svgClone = svgElement.cloneNode(true);
364
510
  svgClone.setAttribute('width', '64');
@@ -371,7 +517,6 @@ const NiceIconPicker = /* @__PURE__ */ React.forwardRef(function NiceIconPicker(
371
517
  img.src = URL.createObjectURL(svgBlob);
372
518
  e.dataTransfer.setDragImage(img, 32, 32);
373
519
  }
374
- document.body.removeChild(tempContainer);
375
520
  }
376
521
  catch (_a) {
377
522
  // Fallback - use default drag image
@@ -382,33 +527,106 @@ const NiceIconPicker = /* @__PURE__ */ React.forwardRef(function NiceIconPicker(
382
527
  const handleDragEnd = React.useCallback((entry, e) => {
383
528
  onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(entry, e);
384
529
  }, [onDragEnd]);
385
- const readableName = (name) => name.replace(/^Ntd/, '');
386
530
  const triggerLabel = React.useMemo(() => {
387
- if (multiSelect && selectedEntries.length > 0) {
531
+ if (multiSelect) {
388
532
  if (selectedEntries.length === 1) {
389
- return readableName(selectedEntries[0].name);
533
+ return entryLabel(selectedEntries[0]);
534
+ }
535
+ if (selectedEntries.length > 1) {
536
+ return t('iconPicker.iconsSelected', '{count} icons selected').replace('{count}', String(selectedEntries.length));
390
537
  }
391
- return t('iconPicker.iconsSelected', '{count} icons selected').replace('{count}', String(selectedEntries.length));
392
- }
393
- return selectedEntry
394
- ? readableName(selectedEntry.name)
395
- : (placeholder !== null && placeholder !== void 0 ? placeholder : t('iconPicker.selectIcon', 'Select icon…'));
396
- }, [multiSelect, selectedEntries, selectedEntry, placeholder, t]);
397
- return (jsxRuntime.jsxs("div", { ref: rootRef, className: `ntd-picker${isOpen ? ' ntd-picker--open' : ''}${disabled ? ' ntd-picker--disabled' : ''}${multiSelect ? ' ntd-picker--multi' : ''}${className ? ` ${className}` : ''}`, children: [jsxRuntime.jsxs("button", { type: "button", className: "ntd-picker-trigger", onClick: handleToggle, disabled: disabled, "aria-haspopup": "listbox", "aria-expanded": isOpen, children: [jsxRuntime.jsx("span", { className: "ntd-picker-trigger-icon", children: selectedEntry ? (jsxRuntime.jsx(selectedEntry.Component, { size: triggerIconSize, variant: variant })) : multiSelect && selectedEntries.length > 1 ? (jsxRuntime.jsx("span", { className: "ntd-picker-multi-badge", children: selectedEntries.length })) : (jsxRuntime.jsx(files.NtdGrid, { size: 16, variant: "outline" })) }), jsxRuntime.jsx("span", { className: `ntd-picker-trigger-label${!selectedEntry && selectedEntries.length === 0 ? ' ntd-picker-trigger-placeholder' : ''}`, children: triggerLabel }), jsxRuntime.jsx("span", { className: "ntd-picker-trigger-chevron", children: jsxRuntime.jsx(Chevron, {}) })] }), isOpen && (jsxRuntime.jsxs("div", { ref: dropdownRef, className: "ntd-picker-dropdown", role: "dialog", "aria-label": t('iconPicker.selectIcon', 'Select icon…'), children: [jsxRuntime.jsxs("div", { className: "ntd-picker-search", children: [jsxRuntime.jsx("span", { className: "ntd-picker-search-icon", children: jsxRuntime.jsx(SearchIcon, {}) }), jsxRuntime.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) && (jsxRuntime.jsxs("div", { className: "ntd-picker-tabs", ref: tablistRef, role: "tablist", children: [jsxRuntime.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 && (jsxRuntime.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: [jsxRuntime.jsx(StarIcon, { filled: true }), " ", t('iconPicker.favorites', 'Favorites'), favorites.length > 0 && (jsxRuntime.jsx("span", { className: "ntd-picker-tab-count", children: favorites.length }))] })), showHistory && history.length > 0 && (jsxRuntime.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: [jsxRuntime.jsx(ClockIcon, {}), " ", t('iconPicker.recent', 'Recent')] })), allCategories.map((cat) => (jsxRuntime.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)))] })), jsxRuntime.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 ? (jsxRuntime.jsxs("div", { className: "ntd-picker-empty", children: [jsxRuntime.jsx("div", { className: "ntd-picker-empty-icon", children: jsxRuntime.jsx(media.NtdInbox, { size: 28, variant: "outline" }) }), activeCategory === '__favorites__'
398
- ? t('iconPicker.noFavoritesYetClickThe', 'No favorites yet. Click the star icon to add.')
399
- : activeCategory === '__history__'
400
- ? t('iconPicker.noRecentIcons', 'No recent icons.')
401
- : t('iconPicker.noIconsFound', 'No icons found')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [virtualized && visibleRange.start > 0 && (jsxRuntime.jsx("div", { style: { height: Math.floor(visibleRange.start / 6) * 48 } })), Array.from(grouped.entries()).map(([category, entries]) => (jsxRuntime.jsxs("div", { children: [!activeCategory && !(activeCategory === null || activeCategory === void 0 ? void 0 : activeCategory.startsWith('__')) && (jsxRuntime.jsx("div", { className: "ntd-picker-category-label", children: category })), jsxRuntime.jsx("div", { className: "ntd-picker-grid", children: entries.map((entry) => {
402
- const isSelected = selectedValues.includes(entry.name);
403
- const isFavorite = favorites.includes(entry.name);
404
- return (jsxRuntime.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": readableName(entry.name), role: "option", "aria-selected": isSelected, title: readableName(entry.name), draggable: enableDragAndDrop, onDragStart: enableDragAndDrop ? (e) => handleDragStart(entry, e) : undefined, onDragEnd: enableDragAndDrop ? (e) => handleDragEnd(entry, e) : undefined, children: [jsxRuntime.jsx(entry.Component, { size: gridIconSize, variant: variant }), multiSelect && isSelected && (jsxRuntime.jsx("span", { className: "ntd-picker-cell-check", children: jsxRuntime.jsx(CheckIcon, {}) })), showFavorites && (jsxRuntime.jsx("button", { type: "button", className: `ntd-picker-cell-fav${isFavorite ? ' ntd-picker-cell-fav--active' : ''}`, onClick: (e) => handleFavoriteClick(e, entry.name), title: isFavorite
405
- ? t('iconPicker.removeFromFavorites', 'Remove from favorites')
406
- : t('iconPicker.addToFavorites', 'Add to favorites'), children: jsxRuntime.jsx(StarIcon, { filled: isFavorite }) }))] }, entry.name));
407
- }) })] }, category))), virtualized && visibleRange.end < filtered.length && (jsxRuntime.jsx("div", { style: { height: Math.ceil((filtered.length - visibleRange.end) / 6) * 48 } }))] })) }), jsxRuntime.jsxs("div", { className: "ntd-picker-footer", children: [jsxRuntime.jsxs("span", { className: "ntd-picker-count", children: [(filtered.length === 1
408
- ? t('iconPicker.iconCountOne', '{count} icon')
409
- : t('iconPicker.iconCountMany', '{count} icons')).replace('{count}', String(filtered.length)), multiSelect && selectedValues.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [' ', t('iconPicker.selected', '• {selectedValues} selected').replace('{selectedValues}', String(selectedValues.length))] })), maxSelections && multiSelect && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [' ', t('iconPicker.max', '(max {maxSelections})').replace('{maxSelections}', String(maxSelections))] }))] }), clearable && selectedValues.length > 0 && (jsxRuntime.jsx("button", { type: "button", className: "ntd-picker-clear", onClick: handleClear, children: multiSelect
538
+ }
539
+ else if (base) {
540
+ return labelOf(base.icon);
541
+ }
542
+ return placeholder !== null && placeholder !== void 0 ? placeholder : t('iconPicker.selectIcon', 'Select icon…');
543
+ }, [multiSelect, selectedEntries, base, labelOf, placeholder, t]);
544
+ const hasSelection = multiSelect ? selectedEntries.length > 0 : !!base;
545
+ const iconOnly = displayMode === 'icon';
546
+ // the field's label first, then what is chosen — the icon-only trigger has no text of its own
547
+ const triggerName = ariaLabel
548
+ ? `${ariaLabel}: ${triggerLabel}`
549
+ : iconOnly
550
+ ? triggerLabel
551
+ : undefined;
552
+ const overlays = Math.max(0, shown.length - 1);
553
+ const showGrid = view === 'icons' || !!picking;
554
+ // Where the panel opens (1.3.9): above the trigger when there is more room there, against its
555
+ // right edge near the window's, never taller than the room it has — the stack view is tall.
556
+ // The room is what the window and every clipping or scrolling ancestor leave visible.
557
+ const [placement, setPlacement] = React.useState({
558
+ up: false,
559
+ end: false,
560
+ });
561
+ useIsoLayoutEffect(() => {
562
+ const root = rootRef.current;
563
+ if (!isOpen || !root || typeof window === 'undefined') {
564
+ return;
565
+ }
566
+ const r = root.getBoundingClientRect();
567
+ const bounds = visibleBounds(root);
568
+ const want = showGrid ? 460 : 620;
569
+ const below = bounds.bottom - r.bottom - 12;
570
+ const above = r.top - bounds.top - 12;
571
+ const up = below < want && above > below;
572
+ const room = up ? above : below;
573
+ setPlacement({
574
+ up,
575
+ end: r.left + 380 > bounds.right - 8 && r.right - bounds.left >= 380,
576
+ maxHeight: room < want ? Math.max(240, Math.floor(room)) : undefined,
577
+ });
578
+ }, [isOpen, showGrid]);
579
+ const pickingInto = picking ? shown.find((l) => l.id === picking.target) : undefined;
580
+ const SingleMulti = (_a = selectedEntries[0]) === null || _a === void 0 ? void 0 : _a.Component;
581
+ return (jsxRuntime.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: [jsxRuntime.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: [jsxRuntime.jsx("span", { className: "ntd-picker-trigger-icon", children: !multiSelect && base ? (iconStack.renderIconStack(shown, resolve, {
582
+ baseProps: { ...drawProps, size: triggerIconSize },
583
+ })) : multiSelect && selectedEntries.length > 1 ? (jsxRuntime.jsx("span", { className: "ntd-picker-multi-badge", children: selectedEntries.length })) : SingleMulti ? (jsxRuntime.jsx(SingleMulti, { ...drawProps, size: triggerIconSize })) : (jsxRuntime.jsx(files.NtdGrid, { size: 16, variant: "outline" })) }), !iconOnly && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: `ntd-picker-trigger-label${hasSelection ? '' : ' ntd-picker-trigger-placeholder'}`, children: triggerLabel }), jsxRuntime.jsx("span", { className: "ntd-picker-trigger-chevron", children: jsxRuntime.jsx(Chevron, {}) })] }))] }), name &&
584
+ (multiSelect ? (values.map((v) => jsxRuntime.jsx("input", { type: "hidden", name: name, value: v }, v))) : (jsxRuntime.jsx("input", { type: "hidden", name: name, value: value !== null && value !== void 0 ? value : '' }))), pending && (jsxRuntime.jsx("span", { ref: serializerRef, className: "ntd-picker-serializer", hidden: true, children: iconStack.renderIconStack(pending, resolve, {
585
+ baseProps: {
586
+ ...drawProps,
587
+ width: '1em',
588
+ height: '1em',
589
+ title: labelOf(pending[0].icon),
590
+ ...iconHtml.iconHtmlAttributes(pending),
591
+ },
592
+ }) })), isOpen && (jsxRuntime.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 &&
593
+ (picking ? (jsxRuntime.jsxs("div", { className: "ntd-picker-banner", children: [jsxRuntime.jsx("button", { type: "button", className: "ntd-picker-back", onClick: () => setPicking(null), "aria-label": t('iconPicker.back', 'Back'), title: t('iconPicker.back', 'Back'), children: jsxRuntime.jsx(navigation.NtdArrowLeft, { size: 14, variant: "outline" }) }), jsxRuntime.jsx("span", { children: t('iconPicker.pickOverlay', 'Pick an icon to put on {icon}').replace('{icon}', pickingInto ? labelOf(pickingInto.icon) : base ? labelOf(base.icon) : '') })] })) : (jsxRuntime.jsxs("div", { className: "ntd-picker-views", ref: viewsRef, role: "tablist", "aria-label": t('iconPicker.selectIcon', 'Select icon…'), children: [jsxRuntime.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') }), jsxRuntime.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 && jsxRuntime.jsx("span", { className: "ntd-picker-tab-count", children: overlays })] })] }))), jsxRuntime.jsxs("div", { className: "ntd-picker-panel", id: `${panelId}-panel`, ...(canStack && !picking
594
+ ? {
595
+ role: 'tabpanel',
596
+ 'aria-labelledby': `${panelId}-${view}`,
597
+ }
598
+ : {}), children: [showGrid && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "ntd-picker-search", children: [jsxRuntime.jsx("span", { className: "ntd-picker-search-icon", children: jsxRuntime.jsx(SearchIcon, {}) }), jsxRuntime.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) && (jsxRuntime.jsxs("div", { className: "ntd-picker-tabs", ref: tablistRef, role: "tablist", children: [jsxRuntime.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 && (jsxRuntime.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: [jsxRuntime.jsx(StarIcon, { filled: true }), " ", t('iconPicker.favorites', 'Favorites'), favorites.length > 0 && (jsxRuntime.jsx("span", { className: "ntd-picker-tab-count", children: favorites.length }))] })), showHistory && history.length > 0 && (jsxRuntime.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: [jsxRuntime.jsx(ClockIcon, {}), " ", t('iconPicker.recent', 'Recent')] })), allCategories.map((cat) => (jsxRuntime.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)))] })), jsxRuntime.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 ? (jsxRuntime.jsxs("div", { className: "ntd-picker-empty", children: [jsxRuntime.jsx("div", { className: "ntd-picker-empty-icon", children: jsxRuntime.jsx(media.NtdInbox, { size: 28, variant: "outline" }) }), activeCategory === '__favorites__'
599
+ ? t('iconPicker.noFavoritesYetClickThe', 'No favorites yet. Click the star icon to add.')
600
+ : activeCategory === '__history__'
601
+ ? t('iconPicker.noRecentIcons', 'No recent icons.')
602
+ : t('iconPicker.noIconsFound', 'No icons found')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [virtualized && visibleRange.start > 0 && (jsxRuntime.jsx("div", { style: { height: Math.floor(visibleRange.start / 6) * 48 } })), Array.from(grouped.entries()).map(([category, entries]) => (jsxRuntime.jsxs("div", { children: [!activeCategory && (jsxRuntime.jsx("div", { className: "ntd-picker-category-label", children: category })), jsxRuntime.jsx("div", { className: `ntd-picker-grid${optionDisplayMode === 'icon-name' ? ' ntd-picker-grid--names' : ''}`, children: entries.map((entry) => {
603
+ const isSelected = !picking && selectedValues.includes(entry.name);
604
+ const isFavorite = favorites.includes(entry.name);
605
+ const label = entryLabel(entry);
606
+ const cell = (jsxRuntime.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
607
+ ? (e) => handleDragStart(entry, e)
608
+ : undefined, onDragEnd: enableDragAndDrop ? (e) => handleDragEnd(entry, e) : undefined, children: [jsxRuntime.jsx(entry.Component, { ...drawProps, size: gridIconSize }), optionDisplayMode === 'icon-name' && (jsxRuntime.jsx("span", { className: "ntd-picker-cell-name", children: label })), multiSelect && isSelected && (jsxRuntime.jsx("span", { className: "ntd-picker-cell-check", children: jsxRuntime.jsx(CheckIcon, {}) }))] }, entry.name));
609
+ if (!showFavorites) {
610
+ return cell;
611
+ }
612
+ // the star beside the cell, not in it — a button in a button is
613
+ // invalid HTML (1.3.9)
614
+ const favLabel = isFavorite
615
+ ? t('iconPicker.removeFromFavorites', 'Remove from favorites')
616
+ : t('iconPicker.addToFavorites', 'Add to favorites');
617
+ return (jsxRuntime.jsxs("div", { className: "ntd-picker-cell-wrap", children: [cell, jsxRuntime.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: jsxRuntime.jsx(StarIcon, { filled: isFavorite }) })] }, entry.name));
618
+ }) })] }, category))), virtualized && visibleRange.end < filtered.length && (jsxRuntime.jsx("div", { style: {
619
+ height: Math.ceil((filtered.length - visibleRange.end) / 6) * 48,
620
+ } }))] })) })] })), canStack && view === 'stack' && base && (jsxRuntime.jsx("div", { className: "ntd-picker-stack", hidden: !!picking, children: jsxRuntime.jsx(NiceIconStacker.NiceIconStacker, { ref: stackerRef, icons: resolve, layers: shown, onLayersChange: commit, onAddClick: (target) => {
621
+ var _a;
622
+ setSearch('');
623
+ setActiveCategory(null);
624
+ setPicking({ target: (_a = target === null || target === void 0 ? void 0 : target.id) !== null && _a !== void 0 ? _a : base.id });
625
+ }, previewProps: drawProps, iconLabel: labelOf, showCode: false, keepBase: true }) }))] }), !picking && (jsxRuntime.jsxs("div", { className: "ntd-picker-footer", children: [jsxRuntime.jsx("span", { className: "ntd-picker-count", children: showGrid && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [(filtered.length === 1
626
+ ? t('iconPicker.iconCountOne', '{count} icon')
627
+ : t('iconPicker.iconCountMany', '{count} icons')).replace('{count}', String(filtered.length)), multiSelect && selectedValues.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [' ', t('iconPicker.selected', '• {selectedValues} selected').replace('{selectedValues}', String(selectedValues.length))] })), maxSelections && multiSelect && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [' ', t('iconPicker.max', '(max {maxSelections})').replace('{maxSelections}', String(maxSelections))] }))] })) }), clearable && hasSelection && (jsxRuntime.jsx("button", { type: "button", className: "ntd-picker-clear", onClick: handleClear, children: multiSelect
410
628
  ? t('iconPicker.clearAll', 'Clear all')
411
- : t('iconPicker.clearSelection', 'Clear selection') })), multiSelect && (jsxRuntime.jsx("button", { type: "button", className: "ntd-picker-done", onClick: () => setIsOpen(false), children: t('iconPicker.done', 'Done') }))] })] }))] }));
629
+ : t('iconPicker.clearSelection', 'Clear selection') })), (multiSelect || view === 'stack') && (jsxRuntime.jsx("button", { type: "button", className: "ntd-picker-done", onClick: () => setIsOpen(false), children: t('iconPicker.done', 'Done') }))] }))] }))] }));
412
630
  });
413
631
 
414
632
  exports.NiceIconPicker = NiceIconPicker;