@driftime/sanity-plugin-icon 0.3.0 → 0.4.1

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 (55) hide show
  1. package/README.md +253 -61
  2. package/dist/adapters-Bx_MyEX_.js +633 -0
  3. package/dist/adapters-Bx_MyEX_.js.map +1 -0
  4. package/dist/drawing-ByrcBbWB.js +50 -0
  5. package/dist/drawing-ByrcBbWB.js.map +1 -0
  6. package/dist/index.d.ts +84 -32
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +545 -323
  9. package/dist/index.js.map +1 -1
  10. package/dist/names.d.ts +8 -0
  11. package/dist/names.d.ts.map +1 -0
  12. package/dist/names.js +0 -0
  13. package/dist/next-loader.d.ts +64 -0
  14. package/dist/next-loader.d.ts.map +1 -0
  15. package/dist/next-loader.js +103 -0
  16. package/dist/next-loader.js.map +1 -0
  17. package/dist/next.d.ts +43 -0
  18. package/dist/next.d.ts.map +1 -0
  19. package/dist/next.js +52 -0
  20. package/dist/next.js.map +1 -0
  21. package/dist/nodes-DAzj56Xn.js +139 -0
  22. package/dist/nodes-DAzj56Xn.js.map +1 -0
  23. package/dist/read-worker.d.ts +1 -0
  24. package/dist/read-worker.js +23 -0
  25. package/dist/read-worker.js.map +1 -0
  26. package/dist/registry-B0Dxiwhh.js +68 -0
  27. package/dist/registry-B0Dxiwhh.js.map +1 -0
  28. package/dist/registry-BEEpqK3z.js +13 -0
  29. package/dist/registry-BEEpqK3z.js.map +1 -0
  30. package/dist/render.d.ts +4 -5
  31. package/dist/render.d.ts.map +1 -1
  32. package/dist/render.js +5 -5
  33. package/dist/render.js.map +1 -1
  34. package/dist/sets-D5_YGFll.js +258 -0
  35. package/dist/sets-D5_YGFll.js.map +1 -0
  36. package/dist/sets-kepuObH8.js +90 -0
  37. package/dist/sets-kepuObH8.js.map +1 -0
  38. package/dist/sets.d.ts +50 -0
  39. package/dist/sets.d.ts.map +1 -0
  40. package/dist/sets.js +14 -0
  41. package/dist/sets.js.map +1 -0
  42. package/dist/types-qlHEYDoN.d.ts +20 -0
  43. package/dist/types-qlHEYDoN.d.ts.map +1 -0
  44. package/dist/utils-COPacfAq.js +48 -0
  45. package/dist/utils-COPacfAq.js.map +1 -0
  46. package/dist/vite-ifICG8y9.d.ts +98 -0
  47. package/dist/vite-ifICG8y9.d.ts.map +1 -0
  48. package/dist/vite.d.ts +2 -0
  49. package/dist/vite.js +88 -0
  50. package/dist/vite.js.map +1 -0
  51. package/package.json +42 -5
  52. package/dist/drawing-BThiR2bN.js +0 -129
  53. package/dist/drawing-BThiR2bN.js.map +0 -1
  54. package/dist/types-_D2jGm25.d.ts +0 -16
  55. package/dist/types-_D2jGm25.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,37 +1,42 @@
1
- import { a as convertCase, i as serializeIconNode, n as parseIconNode, o as isDefined, r as resolveIconNode, t as Drawing } from "./drawing-BThiR2bN.js";
1
+ import { r as pluginName, t as parseIconSet } from "./sets-kepuObH8.js";
2
+ import { n as isDefined, t as convertCase } from "./utils-COPacfAq.js";
3
+ import { a as resolveIconDrawing, o as serializeIconDrawing } from "./nodes-DAzj56Xn.js";
4
+ import { t as Drawing } from "./drawing-ByrcBbWB.js";
5
+ import { t as getIconSetKey } from "./registry-BEEpqK3z.js";
2
6
  import { ChangeIndicator, FieldActionsProvider, FieldActionsResolver, FormField, defineField, definePlugin, defineType, getPublishedId, set, setIfMissing, unset, useFormValue } from "sanity";
3
7
  import { c } from "react/compiler-runtime";
4
8
  import { createElement, useEffect, useRef, useState } from "react";
5
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
10
  import { CloseIcon } from "@sanity/icons/Close";
7
- import { Box, Button, Dialog, Flex, Grid, Spinner, Stack, Text, TextInput } from "@sanity/ui";
11
+ import { Box, Button, Card, Dialog, Flex, Grid, Spinner, Stack, Text, TextInput } from "@sanity/ui";
8
12
  import { SearchIcon } from "@sanity/icons/Search";
13
+ import { WarningOutlineIcon } from "@sanity/icons/WarningOutline";
9
14
  import { Popover } from "@sanity/ui/popover";
15
+ import { iconRegistry } from "@driftime/sanity-plugin-icon/sets";
10
16
  /**
11
- * Checks whether a form node holds members of its own, as an object or an array of objects does.
17
+ * Checks whether a form node has members, as objects and arrays of objects do.
12
18
  *
13
19
  * @param node - The form node to check.
14
- * @returns True if the node holds members.
20
+ * @returns True if the node has members.
15
21
  */
16
22
  function hasMembers(node) {
17
23
  return "members" in node;
18
24
  }
19
25
  /**
20
- * Gathers the validation reported on a form node and on anything inside it.
26
+ * Collects the validation on a form node and everything inside it.
21
27
  *
22
- * @param node - The form node to gather validation from.
23
- * @returns Every validation reported on the node or within it.
28
+ * @param node - The form node.
29
+ * @returns Every validation result on the node or inside it.
24
30
  */
25
31
  function collectNodeValidation(node) {
26
32
  return [...node.validation, ...hasMembers(node) ? collectMemberValidation(node.members) : []];
27
33
  }
28
34
  /**
29
- * Gathers the validation reported anywhere inside a field's members. The form hands a field only what
30
- * is reported at its own path, which suits a frame drawing its members inline but leaves one that
31
- * hides them showing nothing they report.
35
+ * Collects the validation anywhere inside a field's members. The form only passes a field the
36
+ * validation at its own path, so a field that hides its members would otherwise show none of theirs.
32
37
  *
33
- * @param members - The members to gather validation from.
34
- * @returns Every validation reported on a member or within one.
38
+ * @param members - The members.
39
+ * @returns Every validation result on the members or inside them.
35
40
  */
36
41
  function collectMemberValidation(members) {
37
42
  return members.flatMap((member) => member.kind === "field" ? collectNodeValidation(member.field) : member.kind === "fieldSet" ? collectMemberValidation(member.fieldSet.members) : member.kind === "item" ? collectNodeValidation(member.item) : []);
@@ -86,21 +91,18 @@ function Field(t0) {
86
91
  let t7;
87
92
  return $[37] !== t3 || $[38] !== t6 ? (t7 = /* @__PURE__ */ jsxs(Fragment, { children: [t3, t6] }), $[37] = t3, $[38] = t6, $[39] = t7) : t7 = $[39], t7;
88
93
  }
89
- /**
90
- * Properties the plugin's own icons are drawn with. Icons taken from `@sanity/icons` are left alone,
91
- * since they already carry the sizing and stroke the rest of the Studio uses.
92
- */
94
+ /** Default props for the plugin's own icons. Icons from `@sanity/icons` already match the Studio. */
93
95
  const defaultIconProps = {
94
96
  width: "1em",
95
97
  height: "1em",
96
98
  strokeWidth: 1.5
97
99
  };
98
100
  /**
99
- * Creates a Sanity Studio icon component from an SVG icon.
101
+ * Wraps an SVG icon component for use in the Studio.
100
102
  *
101
- * @param icon - The icon component to wrap.
102
- * @param props - Additional props merged over the default properties a Studio icon is drawn with.
103
- * @returns A Studio icon component.
103
+ * @param icon - The icon to wrap.
104
+ * @param props - Props that override the Studio icon defaults.
105
+ * @returns An icon component.
104
106
  */
105
107
  function createSanityIcon(icon, props) {
106
108
  let Icon = icon;
@@ -112,7 +114,7 @@ function createSanityIcon(icon, props) {
112
114
  }
113
115
  return SanityIcon;
114
116
  }
115
- /** Space between rows, in pixels. */
117
+ /** Gap between rows in pixels. */
116
118
  const gridLayoutStyle = {
117
119
  justifyItems: "center",
118
120
  rowGap: 4
@@ -126,12 +128,11 @@ function Cell(t0) {
126
128
  height: 40
127
129
  }, $[9] = t2) : t2 = $[9];
128
130
  let t3;
129
- $[10] === icon.node ? t3 = $[11] : (t3 = /* @__PURE__ */ jsx(Drawing, {
130
- node: icon.node,
131
+ $[10] === icon.drawing ? t3 = $[11] : (t3 = /* @__PURE__ */ jsx(Drawing, {
132
+ drawing: icon.drawing,
131
133
  width: "1.25em",
132
- height: "1.25em",
133
- strokeWidth: 1.5
134
- }), $[10] = icon.node, $[11] = t3);
134
+ height: "1.25em"
135
+ }), $[10] = icon.drawing, $[11] = t3);
135
136
  let t4;
136
137
  $[12] !== icon || $[13] !== onSelect ? (t4 = () => {
137
138
  onSelect(icon);
@@ -160,22 +161,21 @@ function Cell(t0) {
160
161
  }), $[18] = icon.label, $[19] = position, $[20] = props, $[21] = selected, $[22] = t1, $[23] = t3, $[24] = t4, $[25] = t5, $[26] = total, $[27] = t6) : t6 = $[27], t6;
161
162
  }
162
163
  function Library(t0) {
163
- let $ = c(29), activeCell, activeIndex, icons, onClearSearch, onHover, onSelect, props, rows, search, selected;
164
+ let $ = c(44), activeCell, activeIndex, icons, onClearSearch, onHover, onSelect, props, rows, search, selected;
164
165
  $[0] === t0 ? (activeCell = $[1], activeIndex = $[2], icons = $[3], onClearSearch = $[4], onHover = $[5], onSelect = $[6], props = $[7], rows = $[8], search = $[9], selected = $[10]) : ({icons, rows, selected, activeIndex, activeCell, search, onSelect, onHover, onClearSearch, ...props} = t0, $[0] = t0, $[1] = activeCell, $[2] = activeIndex, $[3] = icons, $[4] = onClearSearch, $[5] = onHover, $[6] = onSelect, $[7] = props, $[8] = rows, $[9] = search, $[10] = selected);
165
- let { totalRows, firstRow, lastRow } = rows, t1;
166
+ let { totalRows, firstRow, lastRow } = rows, T0, t1, t2, t3, t4, t5, t6;
166
167
  if ($[11] !== activeCell || $[12] !== activeIndex || $[13] !== firstRow || $[14] !== icons || $[15] !== lastRow || $[16] !== onClearSearch || $[17] !== onHover || $[18] !== onSelect || $[19] !== props || $[20] !== search || $[21] !== selected || $[22] !== totalRows) {
167
- let visible = icons.slice(firstRow * 8, lastRow * 8), t2;
168
- $[24] === Symbol.for("react.memo_cache_sentinel") ? (t2 = {
168
+ let visible = icons.slice(firstRow * 8, lastRow * 8);
169
+ T0 = Box, t1 = "listbox", t2 = "Icon library", $[30] === icons ? t3 = $[31] : (t3 = isDefined(icons) ? {
169
170
  height: 348,
170
171
  overflowY: "auto"
171
- }, $[24] = t2) : t2 = $[24];
172
- let t3;
173
- $[25] !== icons.length || $[26] !== onClearSearch || $[27] !== search ? (t3 = icons.length === 0 && /* @__PURE__ */ jsxs(Flex, {
172
+ } : void 0, $[30] = icons, $[31] = t3), t4 = props, $[32] !== icons || $[33] !== onClearSearch || $[34] !== search ? (t5 = !isDefined(icons) && /* @__PURE__ */ jsxs(Flex, {
174
173
  align: "center",
175
174
  justify: "center",
176
175
  direction: "column",
177
176
  gap: 3,
178
- style: { height: 348 },
177
+ paddingTop: 5,
178
+ paddingBottom: 6,
179
179
  children: [/* @__PURE__ */ jsx(Text, {
180
180
  size: 1,
181
181
  muted: !0,
@@ -186,35 +186,91 @@ function Library(t0) {
186
186
  text: "Clear search",
187
187
  onClick: onClearSearch
188
188
  })]
189
- }), $[25] = icons.length, $[26] = onClearSearch, $[27] = search, $[28] = t3) : t3 = $[28], t1 = /* @__PURE__ */ jsxs(Box, {
190
- role: "listbox",
191
- "aria-label": "Icon library",
192
- style: t2,
193
- ...props,
194
- children: [t3, icons.length > 0 && /* @__PURE__ */ jsx(Grid, {
195
- gridTemplateColumns: 8,
196
- style: {
197
- ...gridLayoutStyle,
198
- paddingTop: firstRow * 44,
199
- paddingBottom: (totalRows - lastRow) * 44
200
- },
201
- children: visible.map((icon, offset) => {
202
- let index = firstRow * 8 + offset;
203
- return /* @__PURE__ */ jsx(Cell, {
204
- ref: index === activeIndex ? activeCell : void 0,
205
- icon,
206
- selected: icon.name === selected,
207
- focusable: index === activeIndex,
208
- position: index + 1,
209
- total: icons.length,
210
- onSelect,
211
- onHover
212
- }, icon.name);
213
- })
214
- })]
215
- }), $[11] = activeCell, $[12] = activeIndex, $[13] = firstRow, $[14] = icons, $[15] = lastRow, $[16] = onClearSearch, $[17] = onHover, $[18] = onSelect, $[19] = props, $[20] = search, $[21] = selected, $[22] = totalRows, $[23] = t1;
216
- } else t1 = $[23];
217
- return t1;
189
+ }), $[32] = icons, $[33] = onClearSearch, $[34] = search, $[35] = t5) : t5 = $[35], t6 = isDefined(icons) && /* @__PURE__ */ jsx(Grid, {
190
+ gridTemplateColumns: 8,
191
+ style: {
192
+ ...gridLayoutStyle,
193
+ paddingTop: firstRow * 44,
194
+ paddingBottom: (totalRows - lastRow) * 44
195
+ },
196
+ children: visible.map((icon, offset) => {
197
+ let index = firstRow * 8 + offset;
198
+ return /* @__PURE__ */ jsx(Cell, {
199
+ ref: index === activeIndex ? activeCell : void 0,
200
+ icon,
201
+ selected: icon.name === selected,
202
+ focusable: index === activeIndex,
203
+ position: index + 1,
204
+ total: icons.length,
205
+ onSelect,
206
+ onHover
207
+ }, icon.name);
208
+ })
209
+ }), $[11] = activeCell, $[12] = activeIndex, $[13] = firstRow, $[14] = icons, $[15] = lastRow, $[16] = onClearSearch, $[17] = onHover, $[18] = onSelect, $[19] = props, $[20] = search, $[21] = selected, $[22] = totalRows, $[23] = T0, $[24] = t1, $[25] = t2, $[26] = t3, $[27] = t4, $[28] = t5, $[29] = t6;
210
+ } else T0 = $[23], t1 = $[24], t2 = $[25], t3 = $[26], t4 = $[27], t5 = $[28], t6 = $[29];
211
+ let t7;
212
+ return $[36] !== T0 || $[37] !== t1 || $[38] !== t2 || $[39] !== t3 || $[40] !== t4 || $[41] !== t5 || $[42] !== t6 ? (t7 = /* @__PURE__ */ jsxs(T0, {
213
+ role: t1,
214
+ "aria-label": t2,
215
+ style: t3,
216
+ ...t4,
217
+ children: [t5, t6]
218
+ }), $[36] = T0, $[37] = t1, $[38] = t2, $[39] = t3, $[40] = t4, $[41] = t5, $[42] = t6, $[43] = t7) : t7 = $[43], t7;
219
+ }
220
+ function CodeText(t0) {
221
+ let $ = c(10), props, text;
222
+ $[0] === t0 ? (props = $[1], text = $[2]) : ({text, ...props} = t0, $[0] = t0, $[1] = props, $[2] = text);
223
+ let t1, t2;
224
+ if ($[3] !== props || $[4] !== text) {
225
+ let parts = text.split("`");
226
+ t1 = props, t2 = parts.map((part, index) => {
227
+ let start = parts.slice(0, index).join("`").length;
228
+ return index % 2 == 1 ? /* @__PURE__ */ jsx("code", { children: part }, start) : part;
229
+ }), $[3] = props, $[4] = text, $[5] = t1, $[6] = t2;
230
+ } else t1 = $[5], t2 = $[6];
231
+ let t3;
232
+ return $[7] !== t1 || $[8] !== t2 ? (t3 = /* @__PURE__ */ jsx("span", {
233
+ ...t1,
234
+ children: t2
235
+ }), $[7] = t1, $[8] = t2, $[9] = t3) : t3 = $[9], t3;
236
+ }
237
+ function Problem(t0) {
238
+ let $ = c(14), problem, props;
239
+ $[0] === t0 ? (problem = $[1], props = $[2]) : ({problem, ...props} = t0, $[0] = t0, $[1] = problem, $[2] = props);
240
+ let t1;
241
+ $[3] === Symbol.for("react.memo_cache_sentinel") ? (t1 = /* @__PURE__ */ jsx(Text, {
242
+ size: 1,
243
+ children: /* @__PURE__ */ jsx(WarningOutlineIcon, {})
244
+ }), $[3] = t1) : t1 = $[3];
245
+ let t2;
246
+ $[4] === problem.title ? t2 = $[5] : (t2 = /* @__PURE__ */ jsx(Text, {
247
+ size: 1,
248
+ weight: "medium",
249
+ children: /* @__PURE__ */ jsx(CodeText, { text: problem.title })
250
+ }), $[4] = problem.title, $[5] = t2);
251
+ let t3;
252
+ $[6] === problem.description ? t3 = $[7] : (t3 = /* @__PURE__ */ jsx(Text, {
253
+ size: 1,
254
+ muted: !0,
255
+ children: /* @__PURE__ */ jsx(CodeText, { text: problem.description })
256
+ }), $[6] = problem.description, $[7] = t3);
257
+ let t4;
258
+ $[8] !== t2 || $[9] !== t3 ? (t4 = /* @__PURE__ */ jsxs(Flex, {
259
+ gap: 3,
260
+ children: [t1, /* @__PURE__ */ jsxs(Stack, {
261
+ gap: 3,
262
+ children: [t2, t3]
263
+ })]
264
+ }), $[8] = t2, $[9] = t3, $[10] = t4) : t4 = $[10];
265
+ let t5;
266
+ return $[11] !== props || $[12] !== t4 ? (t5 = /* @__PURE__ */ jsx(Card, {
267
+ tone: "caution",
268
+ padding: 4,
269
+ radius: 2,
270
+ border: !0,
271
+ ...props,
272
+ children: t4
273
+ }), $[11] = props, $[12] = t4, $[13] = t5) : t5 = $[13], t5;
218
274
  }
219
275
  function Recent(t0) {
220
276
  let $ = c(26), icons, onForget, onHover, onSelect, props, selected;
@@ -238,7 +294,7 @@ function Recent(t0) {
238
294
  padding: 2,
239
295
  text: "Clear",
240
296
  onClick: onForget,
241
- "aria-label": "Forget the recently used icons"
297
+ "aria-label": "Clear recent icons"
242
298
  })]
243
299
  }), $[8] = onForget, $[9] = t2);
244
300
  let t3;
@@ -270,38 +326,202 @@ function Recent(t0) {
270
326
  }), $[22] = props, $[23] = t2, $[24] = t4, $[25] = t5) : t5 = $[25], t5;
271
327
  }
272
328
  /**
273
- * Gap between a tooltip and the cell it points at, given as margins because that is the only lever
274
- * `@sanity/ui` offers for it. Top only, since a tooltip always sits above.
329
+ * Normalises text for search, so hyphenated names and multi-word keywords match the same way.
330
+ *
331
+ * @param value - The text.
332
+ * @returns The normalised text.
333
+ */
334
+ function normalizeTerms(value) {
335
+ return value.toLowerCase().replaceAll(/[\s-]+/gu, " ").trim();
336
+ }
337
+ /**
338
+ * Splits text into the parts a search matches and the parts it doesn't, matching the way search does: ignoring case,
339
+ * and treating spaces and hyphens alike.
340
+ *
341
+ * @param text - The text to split.
342
+ * @param query - The normalised search.
343
+ * @returns The parts in order, each with where it starts in the text and whether it matches.
344
+ */
345
+ function splitMatches(text, query) {
346
+ if (!isDefined(query)) return [{
347
+ text,
348
+ start: 0,
349
+ match: !1
350
+ }];
351
+ let pattern = query.split(" ").map((word) => word.replaceAll(/[$()*+.?[\\\]^{|}]/gu, String.raw`\$&`)).join(String.raw`[\s-]+`), parts = text.split(RegExp(`(${pattern})`, "giu"));
352
+ return parts.map((part, index) => ({
353
+ text: part,
354
+ start: parts.slice(0, index).join("").length,
355
+ match: index % 2 == 1
356
+ }));
357
+ }
358
+ /**
359
+ * Narrows the library to the named icons, in the order given. Unknown names are skipped, so a list keeps working
360
+ * across library releases.
361
+ *
362
+ * @param library - Every icon in the library.
363
+ * @param names - The icons to offer, or undefined for all of them.
364
+ * @returns The icons to offer.
365
+ */
366
+ function selectIcons(library, names) {
367
+ if (!isDefined(names)) return library;
368
+ let byName = new Map(library.map((icon) => [icon.name, icon]));
369
+ return [...new Set(names)].flatMap((name) => {
370
+ let icon = byName.get(name);
371
+ return isDefined(icon) ? [icon] : [];
372
+ });
373
+ }
374
+ /**
375
+ * Lists identifiers as inline code, separated by commas.
376
+ *
377
+ * @param identifiers - The identifiers.
378
+ * @returns The list.
379
+ */
380
+ function formatList(identifiers) {
381
+ return identifiers.map((identifier) => `\`${identifier}\``).join(", ");
382
+ }
383
+ /**
384
+ * Describes why a library the project hasn't installed can't be shown, and what to add.
385
+ *
386
+ * @param label - Readable name of the library.
387
+ * @param packageName - Package the library is read from, or an example of one for a family of packages.
388
+ * @param family - Whether the library is a family of packages, one per style.
389
+ * @param listed - Whether the project's `package.json` lists the package.
390
+ * @param sitePackage - Another package of the library the project lists, or undefined when there's none.
391
+ * @returns The problem.
392
+ */
393
+ function describeMissingLibrary(label, packageName, family, listed, sitePackage) {
394
+ return listed ? {
395
+ title: `${label} isn't installed yet`,
396
+ description: `\`${packageName}\` is in \`package.json\` but not in \`node_modules\`. Reinstall the project's dependencies.`
397
+ } : isDefined(sitePackage) ? {
398
+ title: `${label} needs \`${packageName}\``,
399
+ description: `The Studio reads ${label} from \`${packageName}\`, not \`${sitePackage}\`. Add it as a dev dependency.`
400
+ } : {
401
+ title: `${label} isn't installed`,
402
+ description: family ? `Install a ${label} style package, such as \`${packageName}\`.` : `Install \`${packageName}\` in the project.`
403
+ };
404
+ }
405
+ /**
406
+ * Loads one library style from the sets the build step converted.
407
+ *
408
+ * @param library - Identifier of the library, or undefined when none is set.
409
+ * @param style - Identifier of the style, or undefined for the library's default.
410
+ * @returns The icons, or why they can't be shown.
411
+ */
412
+ async function readLibrary(library, style) {
413
+ if (!isDefined(library)) return { problem: {
414
+ title: "No icon library set",
415
+ description: "Set `library` in the icon plugin's configuration or in the field's options."
416
+ } };
417
+ if (!iconRegistry.ready) return { problem: {
418
+ title: "Icons aren't set up",
419
+ description: "Add `withIcons()` to the Studio's build configuration, then restart the Studio."
420
+ } };
421
+ let info = iconRegistry.libraries[library];
422
+ if (!isDefined(info)) return { problem: {
423
+ title: "Unknown icon library",
424
+ description: `There's no icon library called \`${library}\`. Use one of: ${formatList(Object.keys(iconRegistry.libraries))}.`
425
+ } };
426
+ let { label, packageName, packagePrefix, styles, listed, installed, sitePackage } = info;
427
+ if (!installed) return { problem: describeMissingLibrary(label, packageName, isDefined(packagePrefix), listed, sitePackage) };
428
+ let resolvedStyle = style ?? styles[0] ?? "", loader = iconRegistry.sets[getIconSetKey(library, resolvedStyle)];
429
+ if (!isDefined(loader)) return { problem: isDefined(packagePrefix) ? {
430
+ title: `${label} style \`${resolvedStyle}\` isn't installed`,
431
+ description: `Install \`${packagePrefix}${resolvedStyle}\` in the project.`
432
+ } : {
433
+ title: `Unknown ${label} style`,
434
+ description: `${label} has no \`${resolvedStyle}\` style. Use one of: ${formatList(styles)}.`
435
+ } };
436
+ let module = await loader(), set = parseIconSet(module.default);
437
+ return isDefined(set) ? { icons: set.icons.map(({ name, keywords, node, root }) => ({
438
+ name,
439
+ label: convertCase(name, "title"),
440
+ tags: keywords.join(", "),
441
+ drawing: {
442
+ root: root ?? set.root,
443
+ node
444
+ },
445
+ terms: normalizeTerms([name, ...keywords].join(" "))
446
+ })) } : { problem: {
447
+ title: `Couldn't load ${label}`,
448
+ description: "Restart the Studio and try again."
449
+ } };
450
+ }
451
+ /** Library requests shared by every field in the session, keyed by library and style, since the data never changes. */
452
+ const libraryRequests = /* @__PURE__ */ new Map();
453
+ /**
454
+ * Loads a library style, reusing a request already in progress.
455
+ *
456
+ * @param library - Identifier of the library, or undefined when none is set.
457
+ * @param style - Identifier of the style, or undefined for the library's default.
458
+ * @returns The icons, or why they can't be shown.
459
+ */
460
+ async function requestLibrary(library, style) {
461
+ let key = getIconSetKey(library ?? "", style ?? ""), request = libraryRequests.get(key);
462
+ return isDefined(request) || (request = readLibrary(library, style), libraryRequests.set(key, request)), await request;
463
+ }
464
+ /**
465
+ * Gap between the tooltip and its cell, set through margins because `@sanity/ui` offers no other way.
466
+ * Only the top is set, since the tooltip always appears above the cell.
275
467
  */
276
468
  const tooltipMargins = [
277
469
  -4,
278
470
  0,
279
471
  0,
280
472
  0
281
- ];
473
+ ], matchStyle = {
474
+ background: "none",
475
+ color: "inherit",
476
+ textDecorationLine: "underline",
477
+ textUnderlineOffset: "0.2em"
478
+ };
479
+ function Highlight(t0) {
480
+ let $ = c(10), props, query, text;
481
+ $[0] === t0 ? (props = $[1], query = $[2], text = $[3]) : ({text, query, ...props} = t0, $[0] = t0, $[1] = props, $[2] = query, $[3] = text);
482
+ let t1;
483
+ $[4] !== query || $[5] !== text ? (t1 = splitMatches(text, query).map(_temp$1), $[4] = query, $[5] = text, $[6] = t1) : t1 = $[6];
484
+ let t2;
485
+ return $[7] !== props || $[8] !== t1 ? (t2 = /* @__PURE__ */ jsx("span", {
486
+ ...props,
487
+ children: t1
488
+ }), $[7] = props, $[8] = t1, $[9] = t2) : t2 = $[9], t2;
489
+ }
490
+ function _temp$1(part) {
491
+ return part.match ? /* @__PURE__ */ jsx("mark", {
492
+ style: matchStyle,
493
+ children: part.text
494
+ }, part.start) : part.text;
495
+ }
282
496
  function Tooltip(t0) {
283
- let $ = c(13), hovered, props;
284
- $[0] === t0 ? (hovered = $[1], props = $[2]) : ({hovered, ...props} = t0, $[0] = t0, $[1] = hovered, $[2] = props);
497
+ let $ = c(15), hovered, props, query;
498
+ $[0] === t0 ? (hovered = $[1], props = $[2], query = $[3]) : ({hovered, query, ...props} = t0, $[0] = t0, $[1] = hovered, $[2] = props, $[3] = query);
285
499
  let t1;
286
- $[3] === hovered ? t1 = $[4] : (t1 = isDefined(hovered), $[3] = hovered, $[4] = t1);
500
+ $[4] === hovered ? t1 = $[5] : (t1 = isDefined(hovered), $[4] = hovered, $[5] = t1);
287
501
  let t2 = hovered?.element ?? null, t3;
288
- $[5] === Symbol.for("react.memo_cache_sentinel") ? (t3 = { pointerEvents: "none" }, $[5] = t3) : t3 = $[5];
502
+ $[6] === Symbol.for("react.memo_cache_sentinel") ? (t3 = { pointerEvents: "none" }, $[6] = t3) : t3 = $[6];
289
503
  let t4;
290
- $[6] === hovered ? t4 = $[7] : (t4 = isDefined(hovered) && /* @__PURE__ */ jsxs(Stack, {
504
+ $[7] !== hovered || $[8] !== query ? (t4 = isDefined(hovered) && /* @__PURE__ */ jsxs(Stack, {
291
505
  gap: 3,
292
506
  style: { maxWidth: "16rem" },
293
507
  children: [/* @__PURE__ */ jsx(Text, {
294
508
  size: 1,
295
509
  weight: "medium",
296
- children: hovered.icon.label
510
+ children: /* @__PURE__ */ jsx(Highlight, {
511
+ text: hovered.icon.label,
512
+ query
513
+ })
297
514
  }), isDefined(hovered.icon.tags) && /* @__PURE__ */ jsx(Text, {
298
515
  size: 1,
299
516
  muted: !0,
300
- children: hovered.icon.tags
517
+ children: /* @__PURE__ */ jsx(Highlight, {
518
+ text: hovered.icon.tags,
519
+ query
520
+ })
301
521
  })]
302
- }), $[6] = hovered, $[7] = t4);
522
+ }), $[7] = hovered, $[8] = query, $[9] = t4) : t4 = $[9];
303
523
  let t5;
304
- return $[8] !== props || $[9] !== t1 || $[10] !== t2 || $[11] !== t4 ? (t5 = /* @__PURE__ */ jsx(Popover, {
524
+ return $[10] !== props || $[11] !== t1 || $[12] !== t2 || $[13] !== t4 ? (t5 = /* @__PURE__ */ jsx(Popover, {
305
525
  open: t1,
306
526
  referenceElement: t2,
307
527
  placement: "top",
@@ -311,104 +531,60 @@ function Tooltip(t0) {
311
531
  style: t3,
312
532
  content: t4,
313
533
  ...props
314
- }), $[8] = props, $[9] = t1, $[10] = t2, $[11] = t4, $[12] = t5) : t5 = $[12], t5;
315
- }
316
- /**
317
- * Flattens text into the form a search compares against, so a hyphenated icon name and a multi-word
318
- * tag are matched the same way.
319
- *
320
- * @param value - The text to flatten.
321
- * @returns The text in comparable form.
322
- */
323
- function normalizeTerms(value) {
324
- return value.toLowerCase().replaceAll(/[\s-]+/gu, " ").trim();
325
- }
326
- /**
327
- * Narrows the library to a chosen set of icons, in the order they were named. A name the library does
328
- * not recognise is skipped, so a set outlives the release it was written against.
329
- *
330
- * @param library - Every icon the library offers.
331
- * @param names - Names of the icons to offer, or nothing to offer them all.
332
- * @returns The icons on offer.
333
- */
334
- function selectIcons(library, names) {
335
- if (!isDefined(names)) return library;
336
- let byName = new Map(library.map((icon) => [icon.name, icon]));
337
- return [...new Set(names)].flatMap((name) => {
338
- let icon = byName.get(name);
339
- return isDefined(icon) ? [icon] : [];
340
- });
534
+ }), $[10] = props, $[11] = t1, $[12] = t2, $[13] = t4, $[14] = t5) : t5 = $[14], t5;
341
535
  }
342
536
  /**
343
- * Reads the icon library, pairing each drawing with the terms it can be found by. Both files come
344
- * from the same release as the icons themselves, so the drawings and their terms never disagree.
537
+ * Loads an icon library style. Only an open picker calls this, so a Studio that never opens one never loads it.
345
538
  *
346
- * @returns Every icon the library offers.
539
+ * @param library - Identifier of the library, or undefined when none is set.
540
+ * @param style - Identifier of the style, or undefined for the library's default.
541
+ * @returns The icons or why they can't be shown, or undefined while loading.
347
542
  */
348
- async function readLibrary() {
349
- let [drawings, tags] = await Promise.all([import("lucide-static/icon-nodes.json"), import("lucide-static/tags.json")]), nodes = drawings.default, terms = tags.default;
350
- return Object.keys(nodes).flatMap((name) => {
351
- let node = parseIconNode(nodes[name]);
352
- if (!isDefined(node)) return [];
353
- let iconTags = terms[name] ?? [];
354
- return [{
355
- name,
356
- label: convertCase(name, "sentence"),
357
- tags: iconTags.join(", "),
358
- node,
359
- terms: normalizeTerms([name, ...iconTags].join(" "))
360
- }];
361
- });
362
- }
363
- /**
364
- * The library once asked for, held so every field in a Studio session shares the one request. It is a
365
- * megabyte of data that never changes within a release, so asking twice only ever costs.
366
- */
367
- let libraryRequest;
368
- /**
369
- * Reads the icon library, waiting on whatever request is already under way.
370
- *
371
- * @returns Every icon the library offers.
372
- */
373
- async function requestLibrary() {
374
- return libraryRequest ??= readLibrary(), await libraryRequest;
375
- }
376
- /**
377
- * Reads the icon library. Callers mount only while a picker is open, so a Studio that never opens one
378
- * never pays what it costs to read.
379
- *
380
- * @returns Every icon the library offers, or undefined while it is still being read.
381
- */
382
- function useIconLibrary() {
383
- let $ = c(2), [library, setLibrary] = useState(), t0, t1;
384
- return $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = () => {
543
+ function useIconLibrary(library, style) {
544
+ let $ = c(4), [result, setResult] = useState(), t0, t1;
545
+ return $[0] !== library || $[1] !== style ? (t0 = () => {
385
546
  let active = !0;
386
547
  return (async function load() {
387
- let loaded = await requestLibrary();
388
- active && setLibrary(loaded);
548
+ let loaded = await requestLibrary(library, style);
549
+ active && setResult(loaded);
389
550
  })(), () => {
390
551
  active = !1;
391
552
  };
392
- }, t1 = [], $[0] = t0, $[1] = t1) : (t0 = $[0], t1 = $[1]), useEffect(t0, t1), library;
553
+ }, t1 = [library, style], $[0] = library, $[1] = style, $[2] = t0, $[3] = t1) : (t0 = $[2], t1 = $[3]), useEffect(t0, t1), result;
393
554
  }
394
555
  /**
395
- * Tracks which icon the cursor rests on, holding a tooltip back until it has settled somewhere.
556
+ * Tracks the icon under the cursor, showing its tooltip after a short delay. Tooltips are held back while the grid
557
+ * scrolls, and the icon left under the cursor gets its tooltip once scrolling stops.
396
558
  *
397
- * @param scrolling - Whether the grid is currently moving under the cursor.
398
- * @returns The icon being hovered, alongside ways to follow the cursor and to dismiss it.
559
+ * @param scrolling - Whether the grid is scrolling.
560
+ * @returns The hovered icon, and functions to update and clear it.
399
561
  */
400
562
  function useIconTooltip(scrolling) {
401
- let $ = c(11), [hovered, setHovered] = useState(), hoverStart = useRef(void 0), t0, t1;
563
+ let $ = c(14), [hovered, setHovered] = useState(), hoverStart = useRef(void 0), scrolledOnto = useRef(void 0), t0, t1;
402
564
  $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = () => () => {
403
565
  isDefined(hoverStart.current) && clearTimeout(hoverStart.current);
404
566
  }, t1 = [], $[0] = t0, $[1] = t1) : (t0 = $[0], t1 = $[1]), useEffect(t0, t1);
405
- let t2;
406
- $[2] === hovered ? t2 = $[3] : (t2 = function clearHover() {
567
+ let t2, t3;
568
+ $[2] === scrolling ? (t2 = $[3], t3 = $[4]) : (t2 = () => {
569
+ let landed = scrolledOnto.current;
570
+ !scrolling && isDefined(landed) && (scrolledOnto.current = void 0, landed.element.isConnected && landed.element.matches(":hover") && (hoverStart.current = setTimeout(() => {
571
+ setHovered(landed);
572
+ }, 200)));
573
+ }, t3 = [scrolling], $[2] = scrolling, $[3] = t2, $[4] = t3), useEffect(t2, t3);
574
+ let t4;
575
+ $[5] === hovered ? t4 = $[6] : (t4 = function clearHover() {
407
576
  isDefined(hoverStart.current) && clearTimeout(hoverStart.current), isDefined(hovered) && setHovered(void 0);
408
- }, $[2] = hovered, $[3] = t2);
409
- let clearHover = t2, t3;
410
- $[4] !== hovered || $[5] !== scrolling ? (t3 = function handleHover(icon, element) {
411
- if (!(scrolling || element === hovered?.element)) {
577
+ }, $[5] = hovered, $[6] = t4);
578
+ let clearHover = t4, t5;
579
+ $[7] !== hovered || $[8] !== scrolling ? (t5 = function handleHover(icon, element) {
580
+ if (scrolling) {
581
+ scrolledOnto.current = {
582
+ icon,
583
+ element
584
+ };
585
+ return;
586
+ }
587
+ if (element !== hovered?.element) {
412
588
  if (isDefined(hoverStart.current) && clearTimeout(hoverStart.current), isDefined(hovered)) {
413
589
  setHovered({
414
590
  icon,
@@ -423,77 +599,93 @@ function useIconTooltip(scrolling) {
423
599
  });
424
600
  }, 200);
425
601
  }
426
- }, $[4] = hovered, $[5] = scrolling, $[6] = t3) : t3 = $[6];
427
- let handleHover = t3, t4;
428
- return $[7] !== clearHover || $[8] !== handleHover || $[9] !== hovered ? (t4 = {
602
+ }, $[7] = hovered, $[8] = scrolling, $[9] = t5) : t5 = $[9];
603
+ let handleHover = t5, t6;
604
+ return $[10] !== clearHover || $[11] !== handleHover || $[12] !== hovered ? (t6 = {
429
605
  hovered,
430
606
  handleHover,
431
607
  clearHover
432
- }, $[7] = clearHover, $[8] = handleHover, $[9] = hovered, $[10] = t4) : t4 = $[10], t4;
608
+ }, $[10] = clearHover, $[11] = handleHover, $[12] = hovered, $[13] = t6) : t6 = $[13], t6;
433
609
  }
434
- /** Where the icons an author reached for last are kept, in the browser rather than the dataset. */
435
- const storageKey = "driftime:sanity-plugin-icon:recent";
436
610
  /**
437
- * Reads the icons an author reached for last.
611
+ * Builds the storage key for one library style.
438
612
  *
439
- * @returns Names of the remembered icons, most recent first.
613
+ * @param scope - The library and style, such as `phosphor/bold`.
614
+ * @returns The storage key.
440
615
  */
441
- function readRecent() {
616
+ function getStorageKey(scope) {
617
+ return `driftime:sanity-plugin-icon:recent:${scope}`;
618
+ }
619
+ /**
620
+ * Reads the recently chosen icons.
621
+ *
622
+ * @param scope - The library and style, such as `phosphor/bold`.
623
+ * @returns The icon names, most recent first.
624
+ */
625
+ function readRecent(scope) {
442
626
  try {
443
- let stored = JSON.parse(globalThis.localStorage.getItem(storageKey) ?? "[]");
627
+ let stored = JSON.parse(globalThis.localStorage.getItem(getStorageKey(scope)) ?? "[]");
444
628
  return Array.isArray(stored) ? stored.filter((entry) => typeof entry == "string") : [];
445
629
  } catch {
446
630
  return [];
447
631
  }
448
632
  }
449
633
  /**
450
- * Remembers an icon as the one reached for most recently.
634
+ * Adds an icon to the front of the recent list.
451
635
  *
452
- * @param name - Name of the chosen icon.
453
- * @returns Names of the remembered icons, most recent first.
636
+ * @param scope - The library and style, such as `phosphor/bold`.
637
+ * @param name - The chosen icon's name.
638
+ * @returns The updated icon names, most recent first.
454
639
  */
455
- function writeRecent(name) {
456
- let updated = [name, ...readRecent().filter((entry) => entry !== name)].slice(0, 8);
640
+ function writeRecent(scope, name) {
641
+ let updated = [name, ...readRecent(scope).filter((entry) => entry !== name)].slice(0, 8);
457
642
  try {
458
- globalThis.localStorage.setItem(storageKey, JSON.stringify(updated));
643
+ globalThis.localStorage.setItem(getStorageKey(scope), JSON.stringify(updated));
459
644
  } catch {}
460
645
  return updated;
461
646
  }
462
- /** Forgets every icon reached for so far. */
463
- function clearRecent() {
647
+ /**
648
+ * Clears the recent icons.
649
+ *
650
+ * @param scope - The library and style, such as `phosphor/bold`.
651
+ */
652
+ function clearRecent(scope) {
464
653
  try {
465
- globalThis.localStorage.removeItem(storageKey);
654
+ globalThis.localStorage.removeItem(getStorageKey(scope));
466
655
  } catch {}
467
656
  }
468
657
  /**
469
- * Tracks the icons an author reached for last, keeping what is remembered and what is drawn in step.
470
- * The stored list is read as the picker mounts, so another tab choosing an icon is picked up too.
658
+ * Tracks the icons an author chose most recently from one library style. The list is read when the picker opens, so
659
+ * choices made in another tab are included.
471
660
  *
472
- * @returns The remembered names, most recent first, alongside ways to add to and empty them.
661
+ * @param scope - The library and style, such as `phosphor/bold`.
662
+ * @returns The recent icon names, most recent first, and functions to add to and clear them.
473
663
  */
474
- function useRecentIcons() {
475
- let $ = c(4), [recent, setRecent] = useState(readRecent), t0;
476
- $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = function remember(name) {
477
- setRecent(writeRecent(name));
478
- }, $[0] = t0) : t0 = $[0];
479
- let remember = t0, t1;
480
- $[1] === Symbol.for("react.memo_cache_sentinel") ? (t1 = function forget() {
481
- clearRecent(), setRecent([]);
482
- }, $[1] = t1) : t1 = $[1];
483
- let forget = t1, t2;
484
- return $[2] === recent ? t2 = $[3] : (t2 = {
664
+ function useRecentIcons(scope) {
665
+ let $ = c(10), t0;
666
+ $[0] === scope ? t0 = $[1] : (t0 = () => readRecent(scope), $[0] = scope, $[1] = t0);
667
+ let [recent, setRecent] = useState(t0), t1;
668
+ $[2] === scope ? t1 = $[3] : (t1 = function remember(name) {
669
+ setRecent(writeRecent(scope, name));
670
+ }, $[2] = scope, $[3] = t1);
671
+ let remember = t1, t2;
672
+ $[4] === scope ? t2 = $[5] : (t2 = function forget() {
673
+ clearRecent(scope), setRecent([]);
674
+ }, $[4] = scope, $[5] = t2);
675
+ let forget = t2, t3;
676
+ return $[6] !== forget || $[7] !== recent || $[8] !== remember ? (t3 = {
485
677
  recent,
486
678
  remember,
487
679
  forget
488
- }, $[2] = recent, $[3] = t2), t2;
680
+ }, $[6] = forget, $[7] = recent, $[8] = remember, $[9] = t3) : t3 = $[9], t3;
489
681
  }
490
682
  /**
491
- * Works out which icon a key press moves to, so the library can be walked without a mouse.
683
+ * Works out which icon a key press moves to.
492
684
  *
493
685
  * @param key - The key pressed.
494
- * @param current - Index of the icon the grid is on.
495
- * @param last - Index of the final icon in the results.
496
- * @returns The index moved to, or undefined when the key does not move.
686
+ * @param current - Index of the current icon.
687
+ * @param last - Index of the last icon.
688
+ * @returns The new index, or undefined when the key doesn't move.
497
689
  */
498
690
  function getNextIndex(key, current, last) {
499
691
  if (key === "ArrowRight") return current + 1;
@@ -504,12 +696,12 @@ function getNextIndex(key, current, last) {
504
696
  if (key === "End") return last;
505
697
  }
506
698
  /**
507
- * Works out which rows the grid builds for a given scroll position, so only the icons near the
508
- * viewport are mounted. The rows above and below stand in as padding, keeping the scrollbar honest.
699
+ * Works out which rows to render at a scroll position, so only icons near the viewport are mounted.
700
+ * Padding replaces the other rows, so the scrollbar stays the right size.
509
701
  *
510
- * @param scrollRow - Row the scrolling area has reached.
702
+ * @param scrollRow - The row scrolled to.
511
703
  * @param count - Number of icons in the results.
512
- * @returns The row the grid starts building at, the row it stops at, and the row count in total.
704
+ * @returns The first and last rows to render, and the total number of rows.
513
705
  */
514
706
  function getVisibleRows(scrollRow, count) {
515
707
  let totalRows = Math.ceil(count / 8), firstRow = Math.max(0, scrollRow - 8);
@@ -520,32 +712,32 @@ function getVisibleRows(scrollRow, count) {
520
712
  };
521
713
  }
522
714
  /**
523
- * Works out where the scrolling area sits to show a given icon in the middle, for opening on the
524
- * icon already chosen.
715
+ * Works out the scroll position that centres an icon, for opening on the selected icon.
525
716
  *
526
- * @param index - Index of the icon to show.
527
- * @returns The position to scroll to.
717
+ * @param index - Index of the icon.
718
+ * @returns The scroll position.
528
719
  */
529
720
  function getCenteredScrollTop(index) {
530
721
  return Math.max(0, Math.floor(index / 8) * 44 - 174);
531
722
  }
532
723
  /**
533
- * Works out where the scrolling area moves to bring a given icon into view, staying put when the
534
- * icon already shows. Taken from the index rather than the icon's own cell, since Home and End can
535
- * land outside the rows that are mounted.
724
+ * Works out the scroll position that brings an icon into view, keeping the current one if it's
725
+ * already visible. It uses the index because Home and End can move to rows that aren't mounted.
536
726
  *
537
- * @param index - Index of the icon to reveal.
538
- * @param scrollTop - Position the scrolling area has reached.
539
- * @returns The position to scroll to.
727
+ * @param index - Index of the icon.
728
+ * @param scrollTop - The current scroll position.
729
+ * @returns The scroll position.
540
730
  */
541
731
  function getRevealedScrollTop(index, scrollTop) {
542
732
  let top = Math.floor(index / 8) * 44;
543
733
  return top < scrollTop ? top : top + 44 > scrollTop + 348 ? top + 44 - 348 : scrollTop;
544
734
  }
545
735
  function Picker(t0) {
546
- let $ = c(35), allowed, onSelect, props, selected;
547
- $[0] === t0 ? (allowed = $[1], onSelect = $[2], props = $[3], selected = $[4]) : ({allowed, selected, onSelect, ...props} = t0, $[0] = t0, $[1] = allowed, $[2] = onSelect, $[3] = props, $[4] = selected);
548
- let library = useIconLibrary(), icons = selectIcons(library ?? [], allowed), { recent, remember, forget } = useRecentIcons(), [search, setSearch] = useState(""), [scrollRow, setScrollRow] = useState(0), [movedIndex, setMovedIndex] = useState(), [scrolling, setScrolling] = useState(!1), scroller = useRef(null), activeCell = useRef(null), focusIndex = useRef(void 0), scrollFrame = useRef(void 0), scrollStop = useRef(void 0), query = normalizeTerms(search), searching = isDefined(query), results = icons.filter((icon) => !searching || icon.terms.includes(query)), byName = new Map(icons.map(_temp)), recentIcons = searching ? [] : recent.flatMap((entry) => {
736
+ let $ = c(39), allowed, iconStyle, libraryName, onSelect, props, selected;
737
+ $[0] === t0 ? (allowed = $[1], iconStyle = $[2], libraryName = $[3], onSelect = $[4], props = $[5], selected = $[6]) : ({library: libraryName, iconStyle, allowed, selected, onSelect, ...props} = t0, $[0] = t0, $[1] = allowed, $[2] = iconStyle, $[3] = libraryName, $[4] = onSelect, $[5] = props, $[6] = selected);
738
+ let result = useIconLibrary(libraryName, iconStyle), library = result?.icons, icons = selectIcons(library ?? [], allowed), t1 = libraryName ?? "", t2 = iconStyle ?? "default", t3;
739
+ $[7] !== t1 || $[8] !== t2 ? (t3 = getIconSetKey(t1, t2), $[7] = t1, $[8] = t2, $[9] = t3) : t3 = $[9];
740
+ let { recent, remember, forget } = useRecentIcons(t3), [search, setSearch] = useState(""), [scrollRow, setScrollRow] = useState(0), [movedIndex, setMovedIndex] = useState(), [scrolling, setScrolling] = useState(!1), scroller = useRef(null), activeCell = useRef(null), focusIndex = useRef(void 0), scrollFrame = useRef(void 0), scrollStop = useRef(void 0), query = normalizeTerms(search), searching = isDefined(query), results = icons.filter((icon) => !searching || icon.terms.includes(query)), byName = new Map(icons.map(_temp)), recentIcons = searching ? [] : recent.flatMap((entry) => {
549
741
  let icon_1 = byName.get(entry);
550
742
  return isDefined(icon_1) ? [icon_1] : [];
551
743
  }), { hovered, handleHover, clearHover } = useIconTooltip(scrolling), rows = getVisibleRows(scrollRow, results.length), selectedIndex = searching || !isDefined(selected) ? -1 : icons.findIndex((icon_2) => icon_2.name === selected), activeIndex = movedIndex ?? Math.max(0, selectedIndex);
@@ -553,23 +745,23 @@ function Picker(t0) {
553
745
  let element = scroller.current;
554
746
  isDefined(element) && selectedIndex !== -1 && (element.scrollTop = getCenteredScrollTop(selectedIndex));
555
747
  }, [scroller, selectedIndex]);
556
- let t1, t2;
557
- $[5] === Symbol.for("react.memo_cache_sentinel") ? (t1 = () => () => {
748
+ let t4, t5;
749
+ $[10] === Symbol.for("react.memo_cache_sentinel") ? (t4 = () => () => {
558
750
  isDefined(scrollFrame.current) && cancelAnimationFrame(scrollFrame.current), isDefined(scrollStop.current) && clearTimeout(scrollStop.current);
559
- }, t2 = [], $[5] = t1, $[6] = t2) : (t1 = $[5], t2 = $[6]), useEffect(t1, t2);
560
- let t3, t4;
561
- $[7] === activeIndex ? (t3 = $[8], t4 = $[9]) : (t3 = () => {
751
+ }, t5 = [], $[10] = t4, $[11] = t5) : (t4 = $[10], t5 = $[11]), useEffect(t4, t5);
752
+ let t6, t7;
753
+ $[12] === activeIndex ? (t6 = $[13], t7 = $[14]) : (t6 = () => {
562
754
  focusIndex.current === activeIndex && (focusIndex.current = void 0, isDefined(activeCell.current) && activeCell.current.focus());
563
- }, t4 = [activeIndex], $[7] = activeIndex, $[8] = t3, $[9] = t4), useEffect(t3, t4);
564
- let t5;
565
- $[10] !== onSelect || $[11] !== remember ? (t5 = function handleSelect(icon_3) {
755
+ }, t7 = [activeIndex], $[12] = activeIndex, $[13] = t6, $[14] = t7), useEffect(t6, t7);
756
+ let t8;
757
+ $[15] !== onSelect || $[16] !== remember ? (t8 = function handleSelect(icon_3) {
566
758
  remember(icon_3.name), onSelect(icon_3);
567
- }, $[10] = onSelect, $[11] = remember, $[12] = t5) : t5 = $[12];
568
- let handleSelect = t5, t6;
569
- $[13] === Symbol.for("react.memo_cache_sentinel") ? (t6 = function clearSearch() {
759
+ }, $[15] = onSelect, $[16] = remember, $[17] = t8) : t8 = $[17];
760
+ let handleSelect = t8, t9;
761
+ $[18] === Symbol.for("react.memo_cache_sentinel") ? (t9 = function clearSearch() {
570
762
  setSearch(""), setMovedIndex(void 0);
571
- }, $[13] = t6) : t6 = $[13];
572
- let clearSearch = t6, handleSearch = function handleSearch(event) {
763
+ }, $[18] = t9) : t9 = $[18];
764
+ let clearSearch = t9, handleSearch = function handleSearch(event) {
573
765
  setSearch(event.currentTarget.value), setMovedIndex(void 0), isDefined(scroller.current) && (scroller.current.scrollTop = 0);
574
766
  }, selectActive = function selectActive() {
575
767
  let icon_4 = results[activeIndex];
@@ -602,17 +794,32 @@ function Picker(t0) {
602
794
  focusIndex.current = index, setMovedIndex(index);
603
795
  let element_1 = scroller.current;
604
796
  isDefined(element_1) && (element_1.scrollTop = getRevealedScrollTop(index, element_1.scrollTop));
605
- }, t7;
606
- $[14] === Symbol.for("react.memo_cache_sentinel") ? (t7 = /* @__PURE__ */ jsx(SearchIcon, {}), $[14] = t7) : t7 = $[14];
607
- let t8;
608
- $[15] === library ? t8 = $[16] : (t8 = !isDefined(library) && /* @__PURE__ */ jsx(Flex, {
797
+ }, T0 = Dialog, T1 = Stack, t14 = !isDefined(result?.problem) && /* @__PURE__ */ jsx(TextInput, {
798
+ type: "search",
799
+ icon: /* @__PURE__ */ jsx(SearchIcon, {}),
800
+ placeholder: "Search by name or keyword",
801
+ value: search,
802
+ onChange: handleSearch,
803
+ onKeyDown: handleSearchKeyDown,
804
+ disabled: !isDefined(library) || !isDefined(icons),
805
+ "aria-label": "Search the icon library"
806
+ }), t15;
807
+ $[19] === result ? t15 = $[20] : (t15 = !isDefined(result) && /* @__PURE__ */ jsx(Flex, {
609
808
  align: "center",
610
809
  justify: "center",
611
810
  style: { height: 348 },
612
811
  children: /* @__PURE__ */ jsx(Spinner, { muted: !0 })
613
- }), $[15] = library, $[16] = t8);
614
- let t9;
615
- $[17] !== activeIndex || $[18] !== clearHover || $[19] !== clearSearch || $[20] !== handleGridKeyDown || $[21] !== handleHover || $[22] !== handleScroll || $[23] !== handleSelect || $[24] !== icons || $[25] !== library || $[26] !== results || $[27] !== rows || $[28] !== scroller || $[29] !== search || $[30] !== selected ? (t9 = isDefined(library) && /* @__PURE__ */ jsxs(Stack, {
812
+ }), $[19] = result, $[20] = t15);
813
+ let t16;
814
+ $[21] === result ? t16 = $[22] : (t16 = isDefined(result?.problem) && /* @__PURE__ */ jsx(Problem, { problem: result.problem }), $[21] = result, $[22] = t16);
815
+ let t17 = isDefined(recentIcons) && /* @__PURE__ */ jsx(Recent, {
816
+ icons: recentIcons,
817
+ selected,
818
+ onSelect: handleSelect,
819
+ onHover: handleHover,
820
+ onForget: forget,
821
+ onMouseLeave: clearHover
822
+ }), t18 = isDefined(library) && /* @__PURE__ */ jsxs(Stack, {
616
823
  gap: 3,
617
824
  children: [
618
825
  /* @__PURE__ */ jsx(Box, {
@@ -628,17 +835,18 @@ function Picker(t0) {
628
835
  ]
629
836
  })
630
837
  }),
631
- icons.length === 0 && /* @__PURE__ */ jsx(Flex, {
838
+ !isDefined(icons) && /* @__PURE__ */ jsx(Flex, {
632
839
  align: "center",
633
840
  justify: "center",
634
- style: { height: 348 },
841
+ paddingTop: 5,
842
+ paddingBottom: 6,
635
843
  children: /* @__PURE__ */ jsx(Text, {
636
844
  size: 1,
637
845
  muted: !0,
638
- children: "No icons are available to choose from."
846
+ children: "No icons available."
639
847
  })
640
848
  }),
641
- icons.length > 0 && /* @__PURE__ */ jsx(Library, {
849
+ isDefined(icons) && /* @__PURE__ */ jsx(Library, {
642
850
  ref: scroller,
643
851
  icons: results,
644
852
  rows,
@@ -654,39 +862,31 @@ function Picker(t0) {
654
862
  onKeyDown: handleGridKeyDown
655
863
  })
656
864
  ]
657
- }), $[17] = activeIndex, $[18] = clearHover, $[19] = clearSearch, $[20] = handleGridKeyDown, $[21] = handleHover, $[22] = handleScroll, $[23] = handleSelect, $[24] = icons, $[25] = library, $[26] = results, $[27] = rows, $[28] = scroller, $[29] = search, $[30] = selected, $[31] = t9) : t9 = $[31];
658
- let t10 = hovered?.icon.name, t11;
659
- return $[32] !== hovered || $[33] !== t10 ? (t11 = /* @__PURE__ */ jsx(Tooltip, { hovered }, t10), $[32] = hovered, $[33] = t10, $[34] = t11) : t11 = $[34], /* @__PURE__ */ jsx(Dialog, {
865
+ }), t19 = hovered?.icon.name, t20;
866
+ $[23] !== hovered || $[24] !== query || $[25] !== t19 ? (t20 = /* @__PURE__ */ jsx(Tooltip, {
867
+ hovered,
868
+ query
869
+ }, t19), $[23] = hovered, $[24] = query, $[25] = t19, $[26] = t20) : t20 = $[26];
870
+ let t21;
871
+ $[27] !== T1 || $[28] !== t14 || $[29] !== t15 || $[30] !== t16 || $[31] !== t17 || $[32] !== t18 || $[33] !== t20 ? (t21 = /* @__PURE__ */ jsxs(T1, {
872
+ gap: 4,
873
+ padding: 4,
874
+ children: [
875
+ t14,
876
+ t15,
877
+ t16,
878
+ t17,
879
+ t18,
880
+ t20
881
+ ]
882
+ }), $[27] = T1, $[28] = t14, $[29] = t15, $[30] = t16, $[31] = t17, $[32] = t18, $[33] = t20, $[34] = t21) : t21 = $[34];
883
+ let t22;
884
+ return $[35] !== T0 || $[36] !== props || $[37] !== t21 ? (t22 = /* @__PURE__ */ jsx(T0, {
660
885
  header: "Select icon",
661
886
  width: 1,
662
887
  ...props,
663
- children: /* @__PURE__ */ jsxs(Stack, {
664
- gap: 4,
665
- padding: 4,
666
- children: [
667
- /* @__PURE__ */ jsx(TextInput, {
668
- icon: t7,
669
- placeholder: "Search by name or by what the icon depicts",
670
- value: search,
671
- onChange: handleSearch,
672
- onKeyDown: handleSearchKeyDown,
673
- disabled: !isDefined(library) || icons.length === 0,
674
- "aria-label": "Search the icon library"
675
- }),
676
- t8,
677
- isDefined(recentIcons) && /* @__PURE__ */ jsx(Recent, {
678
- icons: recentIcons,
679
- selected,
680
- onSelect: handleSelect,
681
- onHover: handleHover,
682
- onForget: forget,
683
- onMouseLeave: clearHover
684
- }),
685
- t9,
686
- t11
687
- ]
688
- })
689
- });
888
+ children: t21
889
+ }), $[35] = T0, $[36] = props, $[37] = t21, $[38] = t22) : t22 = $[38], t22;
690
890
  }
691
891
  function _temp(icon_0) {
692
892
  return [icon_0.name, icon_0];
@@ -723,46 +923,54 @@ function SquareDashedIcon(props) {
723
923
  }), $[12] = props, $[13] = t12), t12;
724
924
  }
725
925
  /**
726
- * Settles what an icon field offers. The list says what is on and replaces whatever the plugin was
727
- * given, so one line tells you the whole answer.
926
+ * Resolves the library, style, and icons a field offers. A field that names a library replaces all three of the
927
+ * plugin's, and one that doesn't uses the plugin's.
728
928
  *
729
- * @param options - Options the field itself was given.
730
- * @param config - Configuration the plugin was given.
731
- * @returns The settings the field runs on.
929
+ * @param options - The field's options.
930
+ * @param config - The plugin configuration.
931
+ * @returns The field's settings, whose library is undefined when neither the field nor the plugin names one.
732
932
  */
733
933
  function resolveIconOptions(options, config) {
734
- return { icons: options?.icons ?? config.icons };
934
+ let { library, style, icons } = (isDefined(options?.library) ? options : config) ?? {};
935
+ return {
936
+ library,
937
+ style,
938
+ icons
939
+ };
735
940
  }
736
- /** Type name of the icon object. */
941
+ /** Schema type name of the icon object. */
737
942
  const iconTypeName = "icon";
738
943
  /**
739
- * Creates the input an icon field is drawn with, holding the configuration the plugin was given so a
740
- * field naming its own icons replaces them rather than adding to them.
944
+ * Creates the icon field input, bound to the plugin's configuration. A field's own icons replace the
945
+ * plugin's rather than adding to them.
741
946
  *
742
- * @param config - Configuration every field falls back to.
947
+ * @param config - The plugin configuration.
743
948
  * @returns The input component.
744
949
  */
745
950
  function createInput(config) {
746
951
  function Input(t0) {
747
- let $ = c(31), { id, schemaType, value, onChange, readOnly } = t0, { name, node } = value ?? {}, [open, setOpen] = useState(!1), t1;
952
+ let $ = c(35), { id, schemaType, value, onChange, readOnly } = t0, { library: storedLibrary, name, node } = value ?? {}, [open, setOpen] = useState(!1), t1;
748
953
  $[0] === schemaType.options ? t1 = $[1] : (t1 = resolveIconOptions(schemaType.options, config), $[0] = schemaType.options, $[1] = t1);
749
- let { icons } = t1, selectedNode = resolveIconNode(node), selectedLabel = isDefined(name) ? convertCase(name, "sentence") : void 0, t2;
750
- $[2] !== onChange || $[3] !== setOpen ? (t2 = function handleSelect(icon) {
954
+ let { library, style, icons } = t1, selectedDrawing = resolveIconDrawing(node), selectedName = storedLibrary === library ? name : void 0, selectedLabel = isDefined(name) ? convertCase(name, "title") : void 0, t2;
955
+ $[2] !== library || $[3] !== onChange || $[4] !== setOpen || $[5] !== style ? (t2 = function handleSelect(icon) {
751
956
  onChange([
752
957
  setIfMissing({ _type: iconTypeName }),
958
+ isDefined(library) ? set(library, ["library"]) : unset(["library"]),
959
+ isDefined(style) ? set(style, ["style"]) : unset(["style"]),
753
960
  set(icon.name, ["name"]),
754
- set(serializeIconNode(icon.node), ["node"])
961
+ set(serializeIconDrawing(icon.drawing), ["node"])
755
962
  ]), setOpen(!1);
756
- }, $[2] = onChange, $[3] = setOpen, $[4] = t2) : t2 = $[4];
757
- let handleSelect = t2, T0 = Flex, T1 = Button, t6 = isDefined(selectedNode) ? /* @__PURE__ */ jsx(Drawing, {
758
- node: selectedNode,
759
- ...defaultIconProps
963
+ }, $[2] = library, $[3] = onChange, $[4] = setOpen, $[5] = style, $[6] = t2) : t2 = $[6];
964
+ let handleSelect = t2, T0 = Flex, T1 = Button, t6 = isDefined(selectedDrawing) ? /* @__PURE__ */ jsx(Drawing, {
965
+ drawing: selectedDrawing,
966
+ width: "1em",
967
+ height: "1em"
760
968
  }) : /* @__PURE__ */ jsx(SquareDashedIcon, { ...defaultIconProps }), t7 = selectedLabel ?? "Select icon", t8;
761
- $[5] === setOpen ? t8 = $[6] : (t8 = () => {
969
+ $[7] === setOpen ? t8 = $[8] : (t8 = () => {
762
970
  setOpen(!0);
763
- }, $[5] = setOpen, $[6] = t8);
971
+ }, $[7] = setOpen, $[8] = t8);
764
972
  let t9 = isDefined(selectedLabel) ? `${selectedLabel} currently selected` : "Select icon", t10;
765
- $[7] !== T1 || $[8] !== id || $[9] !== readOnly || $[10] !== t6 || $[11] !== t7 || $[12] !== t8 || $[13] !== t9 ? (t10 = /* @__PURE__ */ jsx(T1, {
973
+ $[9] !== T1 || $[10] !== id || $[11] !== readOnly || $[12] !== t6 || $[13] !== t7 || $[14] !== t8 || $[15] !== t9 ? (t10 = /* @__PURE__ */ jsx(T1, {
766
974
  id,
767
975
  type: "button",
768
976
  mode: "ghost",
@@ -771,9 +979,9 @@ function createInput(config) {
771
979
  disabled: readOnly,
772
980
  onClick: t8,
773
981
  "aria-label": t9
774
- }), $[7] = T1, $[8] = id, $[9] = readOnly, $[10] = t6, $[11] = t7, $[12] = t8, $[13] = t9, $[14] = t10) : t10 = $[14];
982
+ }), $[9] = T1, $[10] = id, $[11] = readOnly, $[12] = t6, $[13] = t7, $[14] = t8, $[15] = t9, $[16] = t10) : t10 = $[16];
775
983
  let t11;
776
- $[15] !== onChange || $[16] !== readOnly || $[17] !== value ? (t11 = isDefined(value) && /* @__PURE__ */ jsx(Button, {
984
+ $[17] !== onChange || $[18] !== readOnly || $[19] !== value ? (t11 = isDefined(value) && /* @__PURE__ */ jsx(Button, {
777
985
  type: "button",
778
986
  mode: "ghost",
779
987
  tone: "critical",
@@ -783,13 +991,15 @@ function createInput(config) {
783
991
  onClick: () => {
784
992
  onChange(unset());
785
993
  },
786
- "aria-label": "Clear the currently selected icon"
787
- }), $[15] = onChange, $[16] = readOnly, $[17] = value, $[18] = t11) : t11 = $[18];
994
+ "aria-label": "Clear the selected icon"
995
+ }), $[17] = onChange, $[18] = readOnly, $[19] = value, $[20] = t11) : t11 = $[20];
788
996
  let t12;
789
- $[19] !== handleSelect || $[20] !== icons || $[21] !== id || $[22] !== name || $[23] !== open || $[24] !== setOpen ? (t12 = open && /* @__PURE__ */ jsx(Picker, {
997
+ $[21] !== handleSelect || $[22] !== icons || $[23] !== id || $[24] !== library || $[25] !== open || $[26] !== selectedName || $[27] !== setOpen || $[28] !== style ? (t12 = open && /* @__PURE__ */ jsx(Picker, {
790
998
  id: `${id}-library`,
999
+ library,
1000
+ iconStyle: style,
791
1001
  allowed: icons,
792
- selected: name,
1002
+ selected: selectedName,
793
1003
  onSelect: handleSelect,
794
1004
  onClose: () => {
795
1005
  setOpen(!1);
@@ -797,54 +1007,53 @@ function createInput(config) {
797
1007
  onClickOutside: () => {
798
1008
  setOpen(!1);
799
1009
  }
800
- }), $[19] = handleSelect, $[20] = icons, $[21] = id, $[22] = name, $[23] = open, $[24] = setOpen, $[25] = t12) : t12 = $[25];
1010
+ }), $[21] = handleSelect, $[22] = icons, $[23] = id, $[24] = library, $[25] = open, $[26] = selectedName, $[27] = setOpen, $[28] = style, $[29] = t12) : t12 = $[29];
801
1011
  let t13;
802
- return $[26] !== T0 || $[27] !== t10 || $[28] !== t11 || $[29] !== t12 ? (t13 = /* @__PURE__ */ jsxs(T0, {
1012
+ return $[30] !== T0 || $[31] !== t10 || $[32] !== t11 || $[33] !== t12 ? (t13 = /* @__PURE__ */ jsxs(T0, {
803
1013
  gap: 2,
804
1014
  children: [
805
1015
  t10,
806
1016
  t11,
807
1017
  t12
808
1018
  ]
809
- }), $[26] = T0, $[27] = t10, $[28] = t11, $[29] = t12, $[30] = t13) : t13 = $[30], t13;
1019
+ }), $[30] = T0, $[31] = t10, $[32] = t11, $[33] = t12, $[34] = t13) : t13 = $[34], t13;
810
1020
  }
811
1021
  return Input;
812
1022
  }
813
1023
  /**
814
- * Builds the media a stored icon previews with, so a document lists the drawing an author chose
815
- * rather than one placeholder standing in for every icon alike.
1024
+ * Creates preview media for a stored icon, so each document shows its own icon instead of a placeholder.
816
1025
  *
817
1026
  * @param value - The stored icon.
818
1027
  * @returns The media component, or undefined when nothing readable was stored.
819
1028
  * @public
820
1029
  */
821
1030
  function createIconPreview(value) {
822
- let node = resolveIconNode(value?.node);
823
- if (isDefined(node)) return function IconPreview() {
1031
+ let drawing = resolveIconDrawing(value?.node);
1032
+ if (isDefined(drawing)) return function IconPreview() {
824
1033
  return createElement(Drawing, {
825
- node,
826
- ...defaultIconProps
1034
+ drawing,
1035
+ width: "1em",
1036
+ height: "1em"
827
1037
  });
828
1038
  };
829
1039
  }
830
1040
  /**
831
- * Creates the object type an icon is stored as, offering the icons it is given to every field that
832
- * does not name its own.
1041
+ * Creates the icon object type, offering the plugin's icons to fields that don't set their own.
833
1042
  *
834
- * @param config - Configuration every field falls back to.
835
- * @returns An object type definition for a stored icon.
1043
+ * @param config - The plugin configuration.
1044
+ * @returns The icon type.
836
1045
  */
837
1046
  function createIconType(config) {
838
1047
  return defineType({
839
1048
  name: iconTypeName,
840
1049
  type: "object",
841
1050
  icon: createSanityIcon(SquareDashedIcon),
842
- description: "Icon chosen from the Lucide library.",
1051
+ description: "Icon chosen from an icon library.",
843
1052
  components: {
844
1053
  field: Field,
845
1054
  input: createInput(config)
846
1055
  },
847
- validation: (rule) => rule.custom((value) => !isDefined(value?.name) || isDefined(value.node) ? !0 : "Select the icon again so its drawing is stored."),
1056
+ validation: (rule) => rule.custom((value) => !isDefined(value?.name) || isDefined(value.node) ? !0 : "Select the icon again to save its drawing."),
848
1057
  preview: {
849
1058
  select: {
850
1059
  name: "name",
@@ -853,7 +1062,7 @@ function createIconType(config) {
853
1062
  prepare(selection) {
854
1063
  let { name, node } = selection;
855
1064
  return {
856
- title: isDefined(name) ? convertCase(name, "sentence") : "No icon",
1065
+ title: isDefined(name) ? convertCase(name, "title") : "No icon",
857
1066
  media: createIconPreview({
858
1067
  _type: iconTypeName,
859
1068
  name,
@@ -862,30 +1071,43 @@ function createIconType(config) {
862
1071
  };
863
1072
  }
864
1073
  },
865
- fields: [defineField({
866
- name: "name",
867
- type: "string",
868
- description: "Name of the chosen icon in the library it was taken from."
869
- }), defineField({
870
- name: "node",
871
- type: "text",
872
- description: "Shapes the icon is drawn from, written when the icon is chosen."
873
- })]
1074
+ fields: [
1075
+ defineField({
1076
+ name: "library",
1077
+ type: "string",
1078
+ description: "Identifier of the library the icon came from."
1079
+ }),
1080
+ defineField({
1081
+ name: "style",
1082
+ type: "string",
1083
+ description: "Identifier of the library style the icon came from."
1084
+ }),
1085
+ defineField({
1086
+ name: "name",
1087
+ type: "string",
1088
+ description: "Name of the icon in its library."
1089
+ }),
1090
+ defineField({
1091
+ name: "node",
1092
+ type: "text",
1093
+ description: "Shapes the icon is drawn from, saved when it's chosen."
1094
+ })
1095
+ ]
874
1096
  });
875
1097
  }
876
1098
  const plugin = definePlugin((config) => ({
877
- name: "@driftime/sanity-plugin-icon",
1099
+ name: pluginName,
878
1100
  schema: { types: [createIconType(config)] }
879
1101
  }));
880
1102
  /**
881
- * Creates the icon field type for Sanity Studio, offering the Lucide library through a searchable
882
- * picker and storing the chosen drawing ready to render.
1103
+ * Adds an icon field type to Sanity Studio, with a searchable picker for the installed icon libraries that
1104
+ * stores the chosen icon's drawing.
883
1105
  *
884
- * @param config - Plugin configuration.
885
- * @returns Sanity plugin definition.
1106
+ * @param config - The plugin configuration.
1107
+ * @returns The plugin.
886
1108
  * @public
887
1109
  */
888
- function iconPlugin(config = {}) {
1110
+ function iconPlugin(config) {
889
1111
  return plugin(config);
890
1112
  }
891
1113
  export { createIconPreview, iconPlugin };