@liiift-studio/sanity-font-manager 2.2.0 → 2.3.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.
- package/README.md +73 -0
- package/dist/index.js +393 -1
- package/dist/index.mjs +390 -1
- package/package.json +1 -1
- package/src/components/KeyValueInput.jsx +95 -0
- package/src/components/KeyValueReferenceInput.jsx +254 -0
- package/src/components/SingleUploaderTool.jsx +1 -1
- package/src/components/VariableInstanceReferencesInput.jsx +190 -0
- package/src/index.js +3 -0
package/README.md
CHANGED
|
@@ -91,6 +91,78 @@ Updates and re-links existing script font variant references on font documents
|
|
|
91
91
|
|
|
92
92
|
Recalculates and patches the `subfamily` field on all fonts linked to a typeface, based on the typeface's defined subfamily groups — without re-uploading any files.
|
|
93
93
|
|
|
94
|
+
### `KeyValueInput`
|
|
95
|
+
|
|
96
|
+
Generic ordered key-value editor where both keys and values are plain strings. Supports add, remove, and reorder (up/down arrows). Values are stored as an array of `{ key, value }` objects.
|
|
97
|
+
|
|
98
|
+
```jsx
|
|
99
|
+
import { KeyValueInput } from '@liiift-studio/sanity-font-manager';
|
|
100
|
+
|
|
101
|
+
{
|
|
102
|
+
name: 'aliases',
|
|
103
|
+
type: 'array',
|
|
104
|
+
of: [{ type: 'object', fields: [{ name: 'key', type: 'string' }, { name: 'value', type: 'string' }] }],
|
|
105
|
+
components: { input: KeyValueInput },
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `KeyValueReferenceInput`
|
|
110
|
+
|
|
111
|
+
Generic key-value editor where keys are plain strings and values are weak Sanity document references. Supports searching by title via a popover picker, add/remove/reorder, and an optional `topActions` slot for action buttons above the list.
|
|
112
|
+
|
|
113
|
+
| Prop | Type | Description |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `fetchReferences` | `async (client, doc) => [{_id, title}]` | Async function that returns candidate references for the picker. Receives the Sanity client and the current document. |
|
|
116
|
+
| `topActions` | `ReactNode` | Optional content rendered above the key-value rows (e.g. autofill buttons). |
|
|
117
|
+
| `referenceType` | `string` | Document type for the created weak references (default: `'font'`). |
|
|
118
|
+
|
|
119
|
+
```jsx
|
|
120
|
+
import { KeyValueReferenceInput } from '@liiift-studio/sanity-font-manager';
|
|
121
|
+
|
|
122
|
+
{
|
|
123
|
+
name: 'instanceMap',
|
|
124
|
+
type: 'array',
|
|
125
|
+
of: [{ type: 'object', fields: [{ name: 'key', type: 'string' }, { name: 'value', type: 'reference', weak: true, to: [{ type: 'font' }] }] }],
|
|
126
|
+
components: { input: KeyValueReferenceInput },
|
|
127
|
+
// Pass props via options or a wrapper component:
|
|
128
|
+
options: {
|
|
129
|
+
fetchReferences: async (client, doc) => client.fetch('*[_type == "font"]{_id, title}'),
|
|
130
|
+
referenceType: 'font',
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `VariableInstanceReferencesInput`
|
|
136
|
+
|
|
137
|
+
Font-specific wrapper around `KeyValueReferenceInput` for mapping variable font instance names to their matching static font documents. Provides:
|
|
138
|
+
|
|
139
|
+
- A picker filtered to fonts sharing the same `typefaceName`, excluding variable fonts
|
|
140
|
+
- **Autofill with Matching** — calls `parseVariableFontInstances` to match instance names to existing font documents by weight/style heuristics
|
|
141
|
+
- **Autofill Keys Only** — populates instance name keys from the font's `variableInstances` metadata without resolving references
|
|
142
|
+
- Autofill buttons are shown only when the document is a variable font with parsed instance data
|
|
143
|
+
- Replace/merge confirmation dialog when pairs already exist
|
|
144
|
+
|
|
145
|
+
```jsx
|
|
146
|
+
import { VariableInstanceReferencesInput } from '@liiift-studio/sanity-font-manager';
|
|
147
|
+
|
|
148
|
+
{
|
|
149
|
+
name: 'variableInstanceReferences',
|
|
150
|
+
title: 'Variable Font Instances',
|
|
151
|
+
type: 'array',
|
|
152
|
+
hidden: ({ parent }) => !parent.variableFont,
|
|
153
|
+
of: [
|
|
154
|
+
{
|
|
155
|
+
type: 'object',
|
|
156
|
+
fields: [
|
|
157
|
+
{ name: 'key', type: 'string', title: 'Instance Name' },
|
|
158
|
+
{ name: 'value', type: 'reference', weak: true, to: [{ type: 'font' }], title: 'Matching Font' },
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
components: { input: VariableInstanceReferencesInput },
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
94
166
|
### `StatusDisplay`
|
|
95
167
|
|
|
96
168
|
Shared status bar used by all components. Shows `Status: [message]` in green on success and red on error, with an optional `action` element slot on the far right (used for the advanced toggle in `SingleUploaderTool`).
|
|
@@ -209,6 +281,7 @@ const client = useSanityClient();
|
|
|
209
281
|
| `glyphCount` | `number` | Total number of glyphs |
|
|
210
282
|
| `opentypeFeatures` | `object` | Available OpenType feature tags |
|
|
211
283
|
| `characterSet` | `object` | Array of Unicode code points covered by the font |
|
|
284
|
+
| `variableInstanceReferences` | `array<object>` | Maps variable font instance names to static font document references — `[{ key: string, value: reference }]` |
|
|
212
285
|
|
|
213
286
|
### Typeface document (`typeface`)
|
|
214
287
|
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,8 @@ __export(index_exports, {
|
|
|
33
33
|
FontScriptUploaderComponent: () => FontScriptUploaderComponent,
|
|
34
34
|
GenerateCollectionsPairsComponent: () => GenerateCollectionsPairsComponent,
|
|
35
35
|
HtmlDescription: () => HtmlDescription,
|
|
36
|
+
KeyValueInput: () => KeyValueInput,
|
|
37
|
+
KeyValueReferenceInput: () => KeyValueReferenceInput,
|
|
36
38
|
PriceInput: () => PriceInput_default,
|
|
37
39
|
RegenerateSubfamiliesComponent: () => RegenerateSubfamiliesComponent,
|
|
38
40
|
SCRIPTS: () => SCRIPTS,
|
|
@@ -42,6 +44,7 @@ __export(index_exports, {
|
|
|
42
44
|
UpdateScriptsComponent: () => UpdateScriptsComponent,
|
|
43
45
|
UploadButton: () => UploadButton_default,
|
|
44
46
|
UploadScriptsComponent: () => UploadScriptsComponent,
|
|
47
|
+
VariableInstanceReferencesInput: () => VariableInstanceReferencesInput,
|
|
45
48
|
addItalicToFontTitle: () => addItalicToFontTitle,
|
|
46
49
|
createFontObject: () => createFontObject,
|
|
47
50
|
determineWeight: () => determineWeight,
|
|
@@ -2911,7 +2914,7 @@ var SingleUploaderTool = (props) => {
|
|
|
2911
2914
|
}
|
|
2912
2915
|
)
|
|
2913
2916
|
}
|
|
2914
|
-
), renderFontSection("ttf"), status === "ready" && (fileInput == null ? void 0 : fileInput.ttf) && /* @__PURE__ */ import_react8.default.createElement(import_ui7.Grid, { columns: [2], gap: 2 }, /* @__PURE__ */ import_react8.default.createElement(
|
|
2917
|
+
), renderFontSection("ttf"), status === "ready" && (fileInput == null ? void 0 : fileInput.ttf) && /* @__PURE__ */ import_react8.default.createElement(import_ui7.Grid, { columns: [1, 2], gap: 2 }, /* @__PURE__ */ import_react8.default.createElement(
|
|
2915
2918
|
import_ui7.Button,
|
|
2916
2919
|
{
|
|
2917
2920
|
mode: "ghost",
|
|
@@ -3575,6 +3578,392 @@ var UploadButton = (0, import_react12.forwardRef)(({ handleUpload }, ref) => {
|
|
|
3575
3578
|
UploadButton.displayName = "UploadButton";
|
|
3576
3579
|
var UploadButton_default = UploadButton;
|
|
3577
3580
|
|
|
3581
|
+
// src/components/KeyValueInput.jsx
|
|
3582
|
+
var import_react13 = __toESM(require("react"));
|
|
3583
|
+
var import_ui11 = require("@sanity/ui");
|
|
3584
|
+
var import_icons3 = require("@sanity/icons");
|
|
3585
|
+
var import_sanity9 = require("sanity");
|
|
3586
|
+
function KeyValueInput({ value = [], onChange }) {
|
|
3587
|
+
const [pairs, setPairs] = (0, import_react13.useState)(value);
|
|
3588
|
+
const handlePairChange = (0, import_react13.useCallback)((index, field, fieldValue) => {
|
|
3589
|
+
const updatedPairs = pairs.map((pair, idx) => idx === index ? { ...pair, [field]: fieldValue } : pair);
|
|
3590
|
+
setPairs(updatedPairs);
|
|
3591
|
+
onChange((0, import_sanity9.set)(updatedPairs));
|
|
3592
|
+
}, [pairs, onChange]);
|
|
3593
|
+
const handleAddPair = (0, import_react13.useCallback)(() => {
|
|
3594
|
+
const newPair = { key: "", value: "", _key: Math.random().toString(36).substr(2, 9) };
|
|
3595
|
+
const updatedPairs = [...pairs, newPair];
|
|
3596
|
+
setPairs(updatedPairs);
|
|
3597
|
+
onChange((0, import_sanity9.set)(updatedPairs));
|
|
3598
|
+
}, [pairs, onChange]);
|
|
3599
|
+
const handleRemovePair = (0, import_react13.useCallback)((index) => {
|
|
3600
|
+
const updatedPairs = pairs.filter((_, idx) => idx !== index);
|
|
3601
|
+
setPairs(updatedPairs);
|
|
3602
|
+
onChange((0, import_sanity9.set)(updatedPairs));
|
|
3603
|
+
}, [pairs, onChange]);
|
|
3604
|
+
const handleMoveUp = (0, import_react13.useCallback)((index) => {
|
|
3605
|
+
if (index === 0) return;
|
|
3606
|
+
const updatedPairs = [...pairs];
|
|
3607
|
+
[updatedPairs[index], updatedPairs[index - 1]] = [updatedPairs[index - 1], updatedPairs[index]];
|
|
3608
|
+
setPairs(updatedPairs);
|
|
3609
|
+
onChange((0, import_sanity9.set)(updatedPairs));
|
|
3610
|
+
}, [pairs, onChange]);
|
|
3611
|
+
const handleMoveDown = (0, import_react13.useCallback)((index) => {
|
|
3612
|
+
if (index === pairs.length - 1) return;
|
|
3613
|
+
const updatedPairs = [...pairs];
|
|
3614
|
+
[updatedPairs[index], updatedPairs[index + 1]] = [updatedPairs[index + 1], updatedPairs[index]];
|
|
3615
|
+
setPairs(updatedPairs);
|
|
3616
|
+
onChange((0, import_sanity9.set)(updatedPairs));
|
|
3617
|
+
}, [pairs, onChange]);
|
|
3618
|
+
return /* @__PURE__ */ import_react13.default.createElement(import_ui11.Stack, { space: 3 }, pairs.map((pair, index) => /* @__PURE__ */ import_react13.default.createElement(import_ui11.Grid, { className: "manualButtonWrap", columns: [2], key: index, gap: 0, style: { position: "relative" } }, /* @__PURE__ */ import_react13.default.createElement("div", { style: { position: "absolute", height: "100%", top: "0", left: "-10px", width: "min-content", transform: "translate(-100%, 0%)" } }, /* @__PURE__ */ import_react13.default.createElement("button", { className: "manualButton manualButtonUp", style: { fontSize: "15px", height: "50%" }, onClick: () => handleMoveUp(index) }, /* @__PURE__ */ import_react13.default.createElement(import_icons3.ArrowUpIcon, null)), /* @__PURE__ */ import_react13.default.createElement("button", { className: "manualButton manualButtonDown", style: { fontSize: "15px", height: "50%" }, onClick: () => handleMoveDown(index) }, /* @__PURE__ */ import_react13.default.createElement(import_icons3.ArrowDownIcon, null))), /* @__PURE__ */ import_react13.default.createElement(
|
|
3619
|
+
import_ui11.TextInput,
|
|
3620
|
+
{
|
|
3621
|
+
value: pair.key,
|
|
3622
|
+
onChange: (e) => handlePairChange(index, "key", e.target.value),
|
|
3623
|
+
placeholder: "Key"
|
|
3624
|
+
}
|
|
3625
|
+
), /* @__PURE__ */ import_react13.default.createElement("div", { style: { marginLeft: "-1px" } }, /* @__PURE__ */ import_react13.default.createElement(
|
|
3626
|
+
import_ui11.TextInput,
|
|
3627
|
+
{
|
|
3628
|
+
value: pair.value,
|
|
3629
|
+
onChange: (e) => handlePairChange(index, "value", e.target.value),
|
|
3630
|
+
placeholder: "Value"
|
|
3631
|
+
}
|
|
3632
|
+
)), /* @__PURE__ */ import_react13.default.createElement(
|
|
3633
|
+
"button",
|
|
3634
|
+
{
|
|
3635
|
+
className: "manualButton",
|
|
3636
|
+
onClick: () => handleRemovePair(index),
|
|
3637
|
+
style: { position: "absolute", top: "0", right: "-10px", transform: "translate(100%, 0%)" }
|
|
3638
|
+
},
|
|
3639
|
+
/* @__PURE__ */ import_react13.default.createElement(import_icons3.TrashIcon, null)
|
|
3640
|
+
))), /* @__PURE__ */ import_react13.default.createElement(import_ui11.Button, { tone: "primary", onClick: handleAddPair, icon: import_icons3.AddIcon, text: "Add Row" }));
|
|
3641
|
+
}
|
|
3642
|
+
|
|
3643
|
+
// src/components/KeyValueReferenceInput.jsx
|
|
3644
|
+
var import_react14 = __toESM(require("react"));
|
|
3645
|
+
var import_ui12 = require("@sanity/ui");
|
|
3646
|
+
var import_icons4 = require("@sanity/icons");
|
|
3647
|
+
var import_sanity10 = require("sanity");
|
|
3648
|
+
var import_nanoid8 = require("nanoid");
|
|
3649
|
+
function KeyValueReferenceInput(props) {
|
|
3650
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
3651
|
+
const { value = [], onChange, schemaType, referenceType, fetchReferences, topActions } = props;
|
|
3652
|
+
const [pairs, setPairs] = (0, import_react14.useState)(value);
|
|
3653
|
+
const [referenceData, setReferenceData] = (0, import_react14.useState)({});
|
|
3654
|
+
const [isDialogOpen, setIsDialogOpen] = (0, import_react14.useState)(false);
|
|
3655
|
+
const [editingIndex, setEditingIndex] = (0, import_react14.useState)(null);
|
|
3656
|
+
const [availableReferences, setAvailableReferences] = (0, import_react14.useState)([]);
|
|
3657
|
+
const sanityClient = useSanityClient();
|
|
3658
|
+
const formDocument = (0, import_sanity10.useFormValue)([]);
|
|
3659
|
+
(0, import_react14.useEffect)(() => {
|
|
3660
|
+
const refIds = pairs.filter((p) => {
|
|
3661
|
+
var _a2;
|
|
3662
|
+
return (_a2 = p.value) == null ? void 0 : _a2._ref;
|
|
3663
|
+
}).map((p) => p.value._ref);
|
|
3664
|
+
if (refIds.length === 0) return;
|
|
3665
|
+
if (!sanityClient) {
|
|
3666
|
+
const fallback = {};
|
|
3667
|
+
refIds.forEach((id) => {
|
|
3668
|
+
fallback[id] = `Reference (${id.substring(0, 6)}...)`;
|
|
3669
|
+
});
|
|
3670
|
+
setReferenceData(fallback);
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3673
|
+
sanityClient.fetch(`*[_id in $ids]{_id, title}`, { ids: refIds }).then((result) => {
|
|
3674
|
+
const map = {};
|
|
3675
|
+
result.forEach((item) => {
|
|
3676
|
+
map[item._id] = item.title;
|
|
3677
|
+
});
|
|
3678
|
+
setReferenceData(map);
|
|
3679
|
+
}).catch((err) => {
|
|
3680
|
+
console.error("Error fetching reference data:", err);
|
|
3681
|
+
const fallback = {};
|
|
3682
|
+
refIds.forEach((id) => {
|
|
3683
|
+
fallback[id] = `Reference (${id.substring(0, 6)}...)`;
|
|
3684
|
+
});
|
|
3685
|
+
setReferenceData(fallback);
|
|
3686
|
+
});
|
|
3687
|
+
}, [pairs, sanityClient]);
|
|
3688
|
+
const handlePairChange = (0, import_react14.useCallback)((index, field, fieldValue) => {
|
|
3689
|
+
const updatedPairs = pairs.map((pair, idx) => idx === index ? { ...pair, [field]: fieldValue } : pair);
|
|
3690
|
+
setPairs(updatedPairs);
|
|
3691
|
+
onChange((0, import_sanity10.set)(updatedPairs));
|
|
3692
|
+
}, [pairs, onChange]);
|
|
3693
|
+
const handleAddPair = (0, import_react14.useCallback)(() => {
|
|
3694
|
+
const updatedPairs = [...pairs, { key: "", value: null, _key: (0, import_nanoid8.nanoid)() }];
|
|
3695
|
+
setPairs(updatedPairs);
|
|
3696
|
+
onChange((0, import_sanity10.set)(updatedPairs));
|
|
3697
|
+
}, [pairs, onChange]);
|
|
3698
|
+
const handleRemovePair = (0, import_react14.useCallback)((index) => {
|
|
3699
|
+
const updatedPairs = pairs.filter((_, idx) => idx !== index);
|
|
3700
|
+
setPairs(updatedPairs);
|
|
3701
|
+
onChange((0, import_sanity10.set)(updatedPairs));
|
|
3702
|
+
}, [pairs, onChange]);
|
|
3703
|
+
const handleMoveUp = (0, import_react14.useCallback)((index) => {
|
|
3704
|
+
if (index === 0) return;
|
|
3705
|
+
const updatedPairs = [...pairs];
|
|
3706
|
+
[updatedPairs[index], updatedPairs[index - 1]] = [updatedPairs[index - 1], updatedPairs[index]];
|
|
3707
|
+
setPairs(updatedPairs);
|
|
3708
|
+
onChange((0, import_sanity10.set)(updatedPairs));
|
|
3709
|
+
}, [pairs, onChange]);
|
|
3710
|
+
const handleMoveDown = (0, import_react14.useCallback)((index) => {
|
|
3711
|
+
if (index === pairs.length - 1) return;
|
|
3712
|
+
const updatedPairs = [...pairs];
|
|
3713
|
+
[updatedPairs[index], updatedPairs[index + 1]] = [updatedPairs[index + 1], updatedPairs[index]];
|
|
3714
|
+
setPairs(updatedPairs);
|
|
3715
|
+
onChange((0, import_sanity10.set)(updatedPairs));
|
|
3716
|
+
}, [pairs, onChange]);
|
|
3717
|
+
const openReferenceSelector = (0, import_react14.useCallback)(async (index) => {
|
|
3718
|
+
setEditingIndex(index);
|
|
3719
|
+
if (!sanityClient) {
|
|
3720
|
+
console.error("KeyValueReferenceInput: Sanity client not available");
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
try {
|
|
3724
|
+
let refs;
|
|
3725
|
+
if (fetchReferences) {
|
|
3726
|
+
refs = await fetchReferences(sanityClient, formDocument);
|
|
3727
|
+
} else if (referenceType) {
|
|
3728
|
+
refs = await sanityClient.fetch(`*[_type == $type]{_id, title}`, { type: referenceType });
|
|
3729
|
+
} else {
|
|
3730
|
+
console.warn("KeyValueReferenceInput: provide a fetchReferences prop or referenceType");
|
|
3731
|
+
refs = [];
|
|
3732
|
+
}
|
|
3733
|
+
setAvailableReferences(refs);
|
|
3734
|
+
setIsDialogOpen(true);
|
|
3735
|
+
} catch (err) {
|
|
3736
|
+
console.error("Error fetching available references:", err);
|
|
3737
|
+
}
|
|
3738
|
+
}, [sanityClient, fetchReferences, referenceType, formDocument]);
|
|
3739
|
+
const closeDialog = (0, import_react14.useCallback)(() => {
|
|
3740
|
+
setIsDialogOpen(false);
|
|
3741
|
+
setEditingIndex(null);
|
|
3742
|
+
}, []);
|
|
3743
|
+
const handleReferenceSelect = (0, import_react14.useCallback)((reference) => {
|
|
3744
|
+
if (editingIndex === null) return;
|
|
3745
|
+
handlePairChange(editingIndex, "value", { _type: "reference", _ref: reference._id, _weak: true });
|
|
3746
|
+
closeDialog();
|
|
3747
|
+
}, [editingIndex, handlePairChange, closeDialog]);
|
|
3748
|
+
const referenceOptions = availableReferences.map((ref) => ({ value: ref._id, title: ref.title }));
|
|
3749
|
+
const keyField = (_d = (_c = (_b = (_a = schemaType == null ? void 0 : schemaType.options) == null ? void 0 : _a.of) == null ? void 0 : _b[0]) == null ? void 0 : _c.fields) == null ? void 0 : _d.find((f) => f.name === "key");
|
|
3750
|
+
const valueField = (_h = (_g = (_f = (_e = schemaType == null ? void 0 : schemaType.options) == null ? void 0 : _e.of) == null ? void 0 : _f[0]) == null ? void 0 : _g.fields) == null ? void 0 : _h.find((f) => f.name === "value");
|
|
3751
|
+
const keyTitle = (keyField == null ? void 0 : keyField.title) || "Key";
|
|
3752
|
+
const valueTitle = (valueField == null ? void 0 : valueField.title) || "Value";
|
|
3753
|
+
const keyPlaceholder = (keyField == null ? void 0 : keyField.placeholder) || "Enter key";
|
|
3754
|
+
const pickerLabel = referenceType || valueTitle.toLowerCase();
|
|
3755
|
+
return /* @__PURE__ */ import_react14.default.createElement(import_ui12.Stack, { space: 3 }, topActions && /* @__PURE__ */ import_react14.default.createElement(import_ui12.Box, { paddingBottom: 2 }, topActions), /* @__PURE__ */ import_react14.default.createElement(import_ui12.Box, null, /* @__PURE__ */ import_react14.default.createElement(import_ui12.Stack, { space: 2 }, pairs.map((pair, index) => {
|
|
3756
|
+
var _a2;
|
|
3757
|
+
return /* @__PURE__ */ import_react14.default.createElement(import_ui12.Box, { key: index, style: { position: "relative" } }, /* @__PURE__ */ import_react14.default.createElement("div", { style: { position: "absolute", height: "100%", top: "0", left: "-5px", width: "min-content", transform: "translate(-100%, 0%)" } }, /* @__PURE__ */ import_react14.default.createElement("button", { className: "manualButton manualButtonUp", style: { fontSize: "15px", height: "50%" }, onClick: () => handleMoveUp(index) }, /* @__PURE__ */ import_react14.default.createElement(import_icons4.ArrowUpIcon, null)), /* @__PURE__ */ import_react14.default.createElement("button", { className: "manualButton manualButtonDown", style: { fontSize: "15px", height: "50%" }, onClick: () => handleMoveDown(index) }, /* @__PURE__ */ import_react14.default.createElement(import_icons4.ArrowDownIcon, null))), /* @__PURE__ */ import_react14.default.createElement(import_ui12.Flex, { gap: 2, align: "flex-start" }, /* @__PURE__ */ import_react14.default.createElement(import_ui12.Box, { flex: 1 }, /* @__PURE__ */ import_react14.default.createElement(
|
|
3758
|
+
import_ui12.TextInput,
|
|
3759
|
+
{
|
|
3760
|
+
value: pair.key,
|
|
3761
|
+
onChange: (e) => handlePairChange(index, "key", e.target.value),
|
|
3762
|
+
placeholder: keyPlaceholder
|
|
3763
|
+
}
|
|
3764
|
+
)), /* @__PURE__ */ import_react14.default.createElement(import_ui12.Box, { flex: 1, style: { minHeight: "100%" } }, ((_a2 = pair.value) == null ? void 0 : _a2._ref) ? /* @__PURE__ */ import_react14.default.createElement(import_ui12.Card, { className: "referenceCard", radius: 2, tone: "primary", style: { paddingLeft: "1rem", height: "fit-content" } }, /* @__PURE__ */ import_react14.default.createElement(import_ui12.Flex, { align: "center", justify: "space-between" }, /* @__PURE__ */ import_react14.default.createElement(
|
|
3765
|
+
import_ui12.Text,
|
|
3766
|
+
{
|
|
3767
|
+
size: 2,
|
|
3768
|
+
style: { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: "90%" }
|
|
3769
|
+
},
|
|
3770
|
+
referenceData[pair.value._ref] || "Loading..."
|
|
3771
|
+
), /* @__PURE__ */ import_react14.default.createElement(
|
|
3772
|
+
import_ui12.MenuButton,
|
|
3773
|
+
{
|
|
3774
|
+
button: /* @__PURE__ */ import_react14.default.createElement(import_ui12.Button, { icon: import_icons4.EllipsisHorizontalIcon, mode: "bleed", title: "Options" }),
|
|
3775
|
+
id: `ref-options-${index}`,
|
|
3776
|
+
menu: /* @__PURE__ */ import_react14.default.createElement(import_ui12.Menu, null, /* @__PURE__ */ import_react14.default.createElement(import_ui12.MenuItem, { tone: "critical", icon: import_icons4.TrashIcon, text: "Remove", onClick: () => handlePairChange(index, "value", null) }), /* @__PURE__ */ import_react14.default.createElement(import_ui12.MenuItem, { icon: import_icons4.SyncIcon, text: "Replace", onClick: () => openReferenceSelector(index) })),
|
|
3777
|
+
popover: { portal: true, tone: "default", placement: "left" }
|
|
3778
|
+
}
|
|
3779
|
+
))) : /* @__PURE__ */ import_react14.default.createElement(
|
|
3780
|
+
import_ui12.Box,
|
|
3781
|
+
{
|
|
3782
|
+
padding: 2,
|
|
3783
|
+
style: { minHeight: "100%", border: "1px dashed #ccc", borderRadius: "4px", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer" },
|
|
3784
|
+
onClick: () => openReferenceSelector(index)
|
|
3785
|
+
},
|
|
3786
|
+
/* @__PURE__ */ import_react14.default.createElement(import_ui12.Text, { muted: true, size: 2 }, "Click to select a ", pickerLabel)
|
|
3787
|
+
))), /* @__PURE__ */ import_react14.default.createElement(
|
|
3788
|
+
"button",
|
|
3789
|
+
{
|
|
3790
|
+
className: "manualButton",
|
|
3791
|
+
onClick: () => handleRemovePair(index),
|
|
3792
|
+
style: { position: "absolute", top: "0", right: "-7px", transform: "translate(100%, 0%)" }
|
|
3793
|
+
},
|
|
3794
|
+
/* @__PURE__ */ import_react14.default.createElement(import_icons4.TrashIcon, null)
|
|
3795
|
+
));
|
|
3796
|
+
}))), /* @__PURE__ */ import_react14.default.createElement(import_ui12.Button, { tone: "primary", mode: "ghost", onClick: handleAddPair, icon: import_icons4.AddIcon, text: `Add ${keyTitle}` }), isDialogOpen && /* @__PURE__ */ import_react14.default.createElement(
|
|
3797
|
+
import_ui12.Dialog,
|
|
3798
|
+
{
|
|
3799
|
+
header: `Select a ${pickerLabel}`,
|
|
3800
|
+
id: "reference-selector-dialog",
|
|
3801
|
+
onClose: closeDialog,
|
|
3802
|
+
width: 1
|
|
3803
|
+
},
|
|
3804
|
+
/* @__PURE__ */ import_react14.default.createElement(import_ui12.Box, { padding: 4 }, /* @__PURE__ */ import_react14.default.createElement(
|
|
3805
|
+
import_ui12.Autocomplete,
|
|
3806
|
+
{
|
|
3807
|
+
id: "reference-autocomplete",
|
|
3808
|
+
options: referenceOptions,
|
|
3809
|
+
placeholder: `Search ${pickerLabel}s...`,
|
|
3810
|
+
renderOption: (option) => /* @__PURE__ */ import_react14.default.createElement(import_ui12.Card, { key: option.value, padding: 3, radius: 2, tone: "default", style: { cursor: "pointer" } }, /* @__PURE__ */ import_react14.default.createElement(import_ui12.Text, { size: 2 }, option.title)),
|
|
3811
|
+
renderValue: (val) => {
|
|
3812
|
+
var _a2;
|
|
3813
|
+
return ((_a2 = referenceOptions.find((o) => o.value === val)) == null ? void 0 : _a2.title) || "";
|
|
3814
|
+
},
|
|
3815
|
+
onChange: (newValue) => {
|
|
3816
|
+
const selected = availableReferences.find((r) => r._id === newValue);
|
|
3817
|
+
if (selected) handleReferenceSelect(selected);
|
|
3818
|
+
},
|
|
3819
|
+
openButton: true,
|
|
3820
|
+
fontSize: 2
|
|
3821
|
+
}
|
|
3822
|
+
))
|
|
3823
|
+
));
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
// src/components/VariableInstanceReferencesInput.jsx
|
|
3827
|
+
var import_react15 = __toESM(require("react"));
|
|
3828
|
+
var import_ui13 = require("@sanity/ui");
|
|
3829
|
+
var import_icons5 = require("@sanity/icons");
|
|
3830
|
+
var import_sanity11 = require("sanity");
|
|
3831
|
+
var import_nanoid9 = require("nanoid");
|
|
3832
|
+
function VariableInstanceReferencesInput(props) {
|
|
3833
|
+
const { value = [], onChange } = props;
|
|
3834
|
+
const [isAutofilling, setIsAutofilling] = (0, import_react15.useState)(false);
|
|
3835
|
+
const [showConfirmDialog, setShowConfirmDialog] = (0, import_react15.useState)(false);
|
|
3836
|
+
const [pendingAction, setPendingAction] = (0, import_react15.useState)(null);
|
|
3837
|
+
const formDocument = (0, import_sanity11.useFormValue)([]);
|
|
3838
|
+
const sanityClient = useSanityClient();
|
|
3839
|
+
const fetchReferences = (0, import_react15.useCallback)(async (client, doc) => {
|
|
3840
|
+
const typefaceName = doc == null ? void 0 : doc.typefaceName;
|
|
3841
|
+
if (!typefaceName) {
|
|
3842
|
+
return client.fetch(`*[_type == 'font' && variableFont != true]{_id, title}`, {});
|
|
3843
|
+
}
|
|
3844
|
+
return client.fetch(
|
|
3845
|
+
`*[_type == 'font' && typefaceName == $typefaceName && variableFont != true]{_id, title}`,
|
|
3846
|
+
{ typefaceName }
|
|
3847
|
+
);
|
|
3848
|
+
}, []);
|
|
3849
|
+
const performAutofillWithMatching = (0, import_react15.useCallback)(async (mode) => {
|
|
3850
|
+
setIsAutofilling(true);
|
|
3851
|
+
try {
|
|
3852
|
+
if (!(formDocument == null ? void 0 : formDocument.variableInstances)) {
|
|
3853
|
+
console.warn("Cannot autofill: no variableInstances data on this document");
|
|
3854
|
+
return;
|
|
3855
|
+
}
|
|
3856
|
+
const mappings = await parseVariableFontInstances(formDocument, sanityClient);
|
|
3857
|
+
if (mappings.length === 0) {
|
|
3858
|
+
console.warn("No variable instances could be parsed from this font");
|
|
3859
|
+
return;
|
|
3860
|
+
}
|
|
3861
|
+
const updatedPairs = mode === "replace" ? mappings : [...value, ...mappings.filter((m) => !value.some((p) => p.key === m.key))];
|
|
3862
|
+
onChange((0, import_sanity11.set)(updatedPairs));
|
|
3863
|
+
} catch (err) {
|
|
3864
|
+
console.error("Error during autofill with matching:", err);
|
|
3865
|
+
} finally {
|
|
3866
|
+
setIsAutofilling(false);
|
|
3867
|
+
}
|
|
3868
|
+
}, [formDocument, sanityClient, value, onChange]);
|
|
3869
|
+
const performAutofillKeysOnly = (0, import_react15.useCallback)(async (mode) => {
|
|
3870
|
+
setIsAutofilling(true);
|
|
3871
|
+
try {
|
|
3872
|
+
if (!(formDocument == null ? void 0 : formDocument.variableInstances)) {
|
|
3873
|
+
console.warn("Cannot autofill: no variableInstances data on this document");
|
|
3874
|
+
return;
|
|
3875
|
+
}
|
|
3876
|
+
let instances;
|
|
3877
|
+
try {
|
|
3878
|
+
instances = JSON.parse(formDocument.variableInstances);
|
|
3879
|
+
} catch {
|
|
3880
|
+
console.error("Invalid variableInstances JSON on this document");
|
|
3881
|
+
return;
|
|
3882
|
+
}
|
|
3883
|
+
const keys = Object.keys(instances);
|
|
3884
|
+
if (keys.length === 0) {
|
|
3885
|
+
console.warn("No variable instances found in JSON");
|
|
3886
|
+
return;
|
|
3887
|
+
}
|
|
3888
|
+
const keyOnlyPairs = keys.map((key) => ({ key, value: null, _key: (0, import_nanoid9.nanoid)() }));
|
|
3889
|
+
const updatedPairs = mode === "replace" ? keyOnlyPairs : [...value, ...keyOnlyPairs.filter((p) => !value.some((existing) => existing.key === p.key))];
|
|
3890
|
+
onChange((0, import_sanity11.set)(updatedPairs));
|
|
3891
|
+
} catch (err) {
|
|
3892
|
+
console.error("Error during keys-only autofill:", err);
|
|
3893
|
+
} finally {
|
|
3894
|
+
setIsAutofilling(false);
|
|
3895
|
+
}
|
|
3896
|
+
}, [formDocument, value, onChange]);
|
|
3897
|
+
const handleAutofillWithMatching = (0, import_react15.useCallback)(() => {
|
|
3898
|
+
if (value.length > 0) {
|
|
3899
|
+
setPendingAction("matching");
|
|
3900
|
+
setShowConfirmDialog(true);
|
|
3901
|
+
return;
|
|
3902
|
+
}
|
|
3903
|
+
performAutofillWithMatching("replace");
|
|
3904
|
+
}, [value, performAutofillWithMatching]);
|
|
3905
|
+
const handleAutofillKeysOnly = (0, import_react15.useCallback)(() => {
|
|
3906
|
+
if (value.length > 0) {
|
|
3907
|
+
setPendingAction("keysOnly");
|
|
3908
|
+
setShowConfirmDialog(true);
|
|
3909
|
+
return;
|
|
3910
|
+
}
|
|
3911
|
+
performAutofillKeysOnly("replace");
|
|
3912
|
+
}, [value, performAutofillKeysOnly]);
|
|
3913
|
+
const handleConfirmChoice = (0, import_react15.useCallback)(async (choice) => {
|
|
3914
|
+
setShowConfirmDialog(false);
|
|
3915
|
+
if (pendingAction === "matching") await performAutofillWithMatching(choice);
|
|
3916
|
+
else if (pendingAction === "keysOnly") await performAutofillKeysOnly(choice);
|
|
3917
|
+
setPendingAction(null);
|
|
3918
|
+
}, [pendingAction, performAutofillWithMatching, performAutofillKeysOnly]);
|
|
3919
|
+
const handleConfirmCancel = (0, import_react15.useCallback)(() => {
|
|
3920
|
+
setShowConfirmDialog(false);
|
|
3921
|
+
setPendingAction(null);
|
|
3922
|
+
}, []);
|
|
3923
|
+
const showAutofill = !!((formDocument == null ? void 0 : formDocument.variableFont) && (formDocument == null ? void 0 : formDocument.variableInstances));
|
|
3924
|
+
const topActions = showAutofill ? /* @__PURE__ */ import_react15.default.createElement(import_ui13.Flex, { gap: 2 }, /* @__PURE__ */ import_react15.default.createElement(
|
|
3925
|
+
import_ui13.Button,
|
|
3926
|
+
{
|
|
3927
|
+
tone: "primary",
|
|
3928
|
+
mode: "ghost",
|
|
3929
|
+
onClick: handleAutofillWithMatching,
|
|
3930
|
+
icon: import_icons5.SyncIcon,
|
|
3931
|
+
text: "Autofill with Matching",
|
|
3932
|
+
disabled: isAutofilling,
|
|
3933
|
+
loading: isAutofilling
|
|
3934
|
+
}
|
|
3935
|
+
), /* @__PURE__ */ import_react15.default.createElement(
|
|
3936
|
+
import_ui13.Button,
|
|
3937
|
+
{
|
|
3938
|
+
tone: "default",
|
|
3939
|
+
mode: "ghost",
|
|
3940
|
+
onClick: handleAutofillKeysOnly,
|
|
3941
|
+
icon: import_icons5.DocumentTextIcon,
|
|
3942
|
+
text: "Autofill Keys Only",
|
|
3943
|
+
disabled: isAutofilling,
|
|
3944
|
+
loading: isAutofilling
|
|
3945
|
+
}
|
|
3946
|
+
)) : null;
|
|
3947
|
+
return /* @__PURE__ */ import_react15.default.createElement(import_react15.default.Fragment, null, /* @__PURE__ */ import_react15.default.createElement(
|
|
3948
|
+
KeyValueReferenceInput,
|
|
3949
|
+
{
|
|
3950
|
+
...props,
|
|
3951
|
+
referenceType: "font",
|
|
3952
|
+
fetchReferences,
|
|
3953
|
+
topActions
|
|
3954
|
+
}
|
|
3955
|
+
), showConfirmDialog && /* @__PURE__ */ import_react15.default.createElement(
|
|
3956
|
+
import_ui13.Dialog,
|
|
3957
|
+
{
|
|
3958
|
+
header: "Existing entries found",
|
|
3959
|
+
id: "autofill-confirm-dialog",
|
|
3960
|
+
onClose: handleConfirmCancel,
|
|
3961
|
+
width: 1
|
|
3962
|
+
},
|
|
3963
|
+
/* @__PURE__ */ import_react15.default.createElement(import_ui13.Box, { padding: 4 }, /* @__PURE__ */ import_react15.default.createElement(import_ui13.Stack, { space: 4 }, /* @__PURE__ */ import_react15.default.createElement(import_ui13.Text, null, "You already have ", value.length, " ", value.length === 1 ? "entry" : "entries", ". How would you like to proceed?"), /* @__PURE__ */ import_react15.default.createElement(import_ui13.Flex, { gap: 2, justify: "flex-end" }, /* @__PURE__ */ import_react15.default.createElement(import_ui13.Button, { text: "Cancel", mode: "ghost", onClick: handleConfirmCancel }), /* @__PURE__ */ import_react15.default.createElement(import_ui13.Button, { text: "Merge (Add New)", tone: "primary", mode: "ghost", onClick: () => handleConfirmChoice("merge") }), /* @__PURE__ */ import_react15.default.createElement(import_ui13.Button, { text: "Replace All", tone: "critical", onClick: () => handleConfirmChoice("replace") }))))
|
|
3964
|
+
));
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3578
3967
|
// src/utils/getEmptyFontKit.js
|
|
3579
3968
|
var fontkit7 = __toESM(require("fontkit"));
|
|
3580
3969
|
var import_slugify4 = __toESM(require("slugify"));
|
|
@@ -3658,6 +4047,8 @@ var readFontFile2 = (file) => {
|
|
|
3658
4047
|
FontScriptUploaderComponent,
|
|
3659
4048
|
GenerateCollectionsPairsComponent,
|
|
3660
4049
|
HtmlDescription,
|
|
4050
|
+
KeyValueInput,
|
|
4051
|
+
KeyValueReferenceInput,
|
|
3661
4052
|
PriceInput,
|
|
3662
4053
|
RegenerateSubfamiliesComponent,
|
|
3663
4054
|
SCRIPTS,
|
|
@@ -3667,6 +4058,7 @@ var readFontFile2 = (file) => {
|
|
|
3667
4058
|
UpdateScriptsComponent,
|
|
3668
4059
|
UploadButton,
|
|
3669
4060
|
UploadScriptsComponent,
|
|
4061
|
+
VariableInstanceReferencesInput,
|
|
3670
4062
|
addItalicToFontTitle,
|
|
3671
4063
|
createFontObject,
|
|
3672
4064
|
determineWeight,
|