sanity-plugin-internationalized-array 3.1.4 → 3.1.6

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/lib/index.js CHANGED
@@ -20,7 +20,32 @@ function _interopNamespaceCompat(e) {
20
20
  }), n.default = e, Object.freeze(n);
21
21
  }
22
22
  var suspend__namespace = /* @__PURE__ */ _interopNamespaceCompat(suspend), equal__default = /* @__PURE__ */ _interopDefaultCompat(equal), get__default = /* @__PURE__ */ _interopDefaultCompat(get);
23
- const namespace = "sanity-plugin-internationalized-array", version = "v1", preload = (fn) => suspend__namespace.preload(() => fn(), [version, namespace]), clear = () => suspend__namespace.clear([version, namespace]), peek = (selectedValue) => suspend__namespace.peek([version, namespace, selectedValue]), MAX_COLUMNS = {
23
+ const namespace = "sanity-plugin-internationalized-array", version = "v1", functionCache = /* @__PURE__ */ new Map(), functionKeyCache = /* @__PURE__ */ new WeakMap(), preloadWithKey = (fn, key) => suspend__namespace.preload(() => fn(), key), clear = () => suspend__namespace.clear([version, namespace]), peek = (selectedValue) => suspend__namespace.peek([version, namespace, selectedValue]), createCacheKey = (selectedValue, workspaceId) => {
24
+ const selectedValueHash = JSON.stringify(selectedValue);
25
+ return workspaceId ? [version, namespace, selectedValueHash, workspaceId] : [version, namespace, selectedValueHash];
26
+ }, getFunctionKey = (fn) => {
27
+ const cachedKey = functionKeyCache.get(fn);
28
+ if (cachedKey)
29
+ return cachedKey;
30
+ const fnStr = fn.toString();
31
+ let hash = 0;
32
+ const maxLength = Math.min(fnStr.length, 100);
33
+ for (let i = 0; i < maxLength; i++) {
34
+ const char = fnStr.charCodeAt(i);
35
+ hash = (hash << 5) - hash + char, hash &= hash;
36
+ }
37
+ const key = `anonymous_${Math.abs(hash)}`;
38
+ return functionKeyCache.set(fn, key), key;
39
+ }, createFunctionCacheKey = (fn, selectedValue, workspaceId) => {
40
+ const functionKey = getFunctionKey(fn), selectedValueHash = JSON.stringify(selectedValue);
41
+ return workspaceId ? `${functionKey}:${selectedValueHash}:${workspaceId}` : `${functionKey}:${selectedValueHash}`;
42
+ }, getFunctionCache = (fn, selectedValue, workspaceId) => {
43
+ const key = createFunctionCacheKey(fn, selectedValue, workspaceId);
44
+ return functionCache.get(key);
45
+ }, setFunctionCache = (fn, selectedValue, languages, workspaceId) => {
46
+ const key = createFunctionCacheKey(fn, selectedValue, workspaceId);
47
+ functionCache.set(key, languages);
48
+ }, MAX_COLUMNS = {
24
49
  codeOnly: 5,
25
50
  titleOnly: 4,
26
51
  titleAndCode: 3
@@ -106,7 +131,43 @@ function AddButtons(props) {
106
131
  }
107
132
  var AddButtons$1 = react.memo(AddButtons);
108
133
  function DocumentAddButtons(props) {
109
- const { filteredLanguages } = useInternationalizedArrayContext(), value = sanity.isSanityDocument(props.value) ? props.value : void 0, toast = ui.useToast(), { onChange } = structure.useDocumentPane(), documentsToTranslation = getDocumentsToTranslate(value, []), handleDocumentButtonClick = react.useCallback(
134
+ const { filteredLanguages } = useInternationalizedArrayContext(), value = sanity.isSanityDocument(props.value) ? props.value : void 0, toast = ui.useToast(), { onChange } = structure.useDocumentPane(), schema = sanity.useSchema(), documentsToTranslation = getDocumentsToTranslate(value, []), getInitialValueForType = react.useCallback(
135
+ (typeName) => {
136
+ var _a;
137
+ if (!typeName) return;
138
+ const match = typeName.match(/^internationalizedArray(.+)Value$/);
139
+ if (!match) return;
140
+ const baseTypeName = match[1].charAt(0).toLowerCase() + match[1].slice(1), arrayBasedTypes = [
141
+ "body",
142
+ "htmlContent",
143
+ "blockContent",
144
+ "portableText"
145
+ ];
146
+ if (arrayBasedTypes.includes(baseTypeName))
147
+ return [];
148
+ try {
149
+ const schemaType = schema.get(typeName);
150
+ if (schemaType) {
151
+ const valueField = (_a = schemaType == null ? void 0 : schemaType.fields) == null ? void 0 : _a.find(
152
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
153
+ (f) => f.name === "value"
154
+ );
155
+ if (valueField) {
156
+ const fieldType = valueField.type;
157
+ if ((fieldType == null ? void 0 : fieldType.jsonType) === "array" || (fieldType == null ? void 0 : fieldType.name) === "array" || (fieldType == null ? void 0 : fieldType.type) === "array" || (fieldType == null ? void 0 : fieldType.of) !== void 0 || arrayBasedTypes.includes(fieldType == null ? void 0 : fieldType.name))
158
+ return [];
159
+ }
160
+ }
161
+ } catch (error) {
162
+ console.warn(
163
+ "Could not determine field type from schema:",
164
+ typeName,
165
+ error
166
+ );
167
+ }
168
+ },
169
+ [schema]
170
+ ), handleDocumentButtonClick = react.useCallback(
110
171
  async (event) => {
111
172
  const languageId = event.currentTarget.value;
112
173
  if (!languageId) {
@@ -132,12 +193,13 @@ function DocumentAddButtons(props) {
132
193
  }
133
194
  const patches = [];
134
195
  for (const toTranslate of removeDuplicates) {
135
- const path = toTranslate.path, ifMissing = sanity.setIfMissing([], path), insertValue = sanity.insert(
196
+ const path = toTranslate.path, initialValue = getInitialValueForType(toTranslate._type), ifMissing = sanity.setIfMissing([], path), insertValue = sanity.insert(
136
197
  [
137
198
  {
138
199
  _key: languageId,
139
200
  _type: toTranslate._type,
140
- value: void 0
201
+ value: initialValue
202
+ // Use the determined initial value instead of undefined
141
203
  }
142
204
  ],
143
205
  "after",
@@ -147,7 +209,7 @@ function DocumentAddButtons(props) {
147
209
  }
148
210
  onChange(sanity.PatchEvent.from(patches.flat()));
149
211
  },
150
- [documentsToTranslation, onChange, toast]
212
+ [documentsToTranslation, getInitialValueForType, onChange, toast]
151
213
  );
152
214
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
153
215
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "semibold", children: "Add translation to internationalized fields" }) }),
@@ -193,10 +255,36 @@ function InternationalizedArrayProvider(props) {
193
255
  const { internationalizedArray: internationalizedArray2 } = props, client = sanity.useClient({ apiVersion: internationalizedArray2.apiVersion }), workspace = sanity.useWorkspace(), { formState } = structure.useDocumentPane(), deferredDocument = react.useDeferredValue(formState == null ? void 0 : formState.value), selectedValue = react.useMemo(
194
256
  () => getSelectedValue(internationalizedArray2.select, deferredDocument),
195
257
  [internationalizedArray2.select, deferredDocument]
258
+ ), workspaceId = react.useMemo(() => {
259
+ if (workspace != null && workspace.name)
260
+ return workspace.name;
261
+ const workspaceKey = {
262
+ name: workspace == null ? void 0 : workspace.name,
263
+ title: workspace == null ? void 0 : workspace.title
264
+ // Add other stable properties as needed
265
+ };
266
+ return JSON.stringify(workspaceKey);
267
+ }, [workspace]), cacheKey = react.useMemo(
268
+ () => createCacheKey(selectedValue, workspaceId),
269
+ [selectedValue, workspaceId]
196
270
  ), languages = Array.isArray(internationalizedArray2.languages) ? internationalizedArray2.languages : suspend.suspend(
197
271
  // eslint-disable-next-line require-await
198
- async () => typeof internationalizedArray2.languages == "function" ? internationalizedArray2.languages(client, selectedValue) : internationalizedArray2.languages,
199
- [version, namespace, selectedValue, workspace],
272
+ async () => {
273
+ if (typeof internationalizedArray2.languages == "function") {
274
+ const result = await internationalizedArray2.languages(
275
+ client,
276
+ selectedValue
277
+ );
278
+ return setFunctionCache(
279
+ internationalizedArray2.languages,
280
+ selectedValue,
281
+ result,
282
+ workspaceId
283
+ ), result;
284
+ }
285
+ return internationalizedArray2.languages;
286
+ },
287
+ cacheKey,
200
288
  { equal: equal__default.default }
201
289
  ), { selectedLanguageIds, options: languageFilterOptions } = languageFilter.useLanguageFilterStudioContext(), filteredLanguages = react.useMemo(() => {
202
290
  const documentType = deferredDocument ? deferredDocument._type : void 0;
@@ -238,10 +326,13 @@ function InternationalizedField(props) {
238
326
  })) : customProps.renderDefault(customProps);
239
327
  }
240
328
  var Preload = react.memo(function(props) {
241
- const client = sanity.useClient({ apiVersion: props.apiVersion });
242
- return Array.isArray(peek({})) || preload(
243
- async () => Array.isArray(props.languages) ? props.languages : props.languages(client, {})
244
- ), null;
329
+ const client = sanity.useClient({ apiVersion: props.apiVersion }), cacheKey = createCacheKey({});
330
+ return Array.isArray(peek({})) || preloadWithKey(async () => {
331
+ if (Array.isArray(props.languages))
332
+ return props.languages;
333
+ const result = await props.languages(client, {});
334
+ return setFunctionCache(props.languages, {}, result), result;
335
+ }, cacheKey), null;
245
336
  });
246
337
  function checkAllLanguagesArePresent(languages, value) {
247
338
  const filteredLanguageIds = languages.map((l) => l.id), languagesInUseIds = value ? value.map((v) => v._key) : [];
@@ -550,24 +641,48 @@ var __defProp$4 = Object.defineProperty, __defProps$3 = Object.defineProperties,
550
641
  ],
551
642
  // @ts-expect-error - fix typings
552
643
  validation: (rule) => rule.custom(async (value, context) => {
553
- if (!value)
644
+ var _a;
645
+ if (!value || value.length === 0 || value.length === 1 && !((_a = value[0]) != null && _a._key))
554
646
  return !0;
555
647
  const selectedValue = getSelectedValue(select, context.document), client = context.getClient({ apiVersion });
556
648
  let contextLanguages = [];
557
649
  const languagesFieldOption = getLanguagesFieldOption(context == null ? void 0 : context.type);
558
- if (Array.isArray(languagesFieldOption) ? contextLanguages = languagesFieldOption : Array.isArray(peek(selectedValue)) ? contextLanguages = peek(selectedValue) || [] : typeof languagesFieldOption == "function" && (contextLanguages = await languagesFieldOption(client, selectedValue)), value && value.length > contextLanguages.length)
650
+ if (Array.isArray(languagesFieldOption))
651
+ contextLanguages = languagesFieldOption;
652
+ else if (Array.isArray(peek(selectedValue)))
653
+ contextLanguages = peek(selectedValue) || [];
654
+ else if (typeof languagesFieldOption == "function") {
655
+ const cachedLanguages = getFunctionCache(
656
+ languagesFieldOption,
657
+ selectedValue
658
+ );
659
+ if (Array.isArray(cachedLanguages))
660
+ contextLanguages = cachedLanguages;
661
+ else {
662
+ const suspendCachedLanguages = peek(selectedValue);
663
+ Array.isArray(suspendCachedLanguages) ? contextLanguages = suspendCachedLanguages : (contextLanguages = await languagesFieldOption(
664
+ client,
665
+ selectedValue
666
+ ), setFunctionCache(
667
+ languagesFieldOption,
668
+ selectedValue,
669
+ contextLanguages
670
+ ));
671
+ }
672
+ }
673
+ if (value && value.length > contextLanguages.length)
559
674
  return `Cannot be more than ${contextLanguages.length === 1 ? "1 item" : `${contextLanguages.length} items`}`;
560
- const nonLanguageKeys = value != null && value.length ? value.filter(
561
- (item) => !contextLanguages.find((language) => item._key === language.id)
562
- ) : [];
675
+ const languageIds = new Set(contextLanguages.map((lang) => lang.id)), nonLanguageKeys = value.filter(
676
+ (item) => (item == null ? void 0 : item._key) && !languageIds.has(item._key)
677
+ );
563
678
  if (nonLanguageKeys.length)
564
679
  return {
565
680
  message: "Array item keys must be valid languages registered to the field type",
566
681
  paths: nonLanguageKeys.map((item) => [{ _key: item._key }])
567
682
  };
568
- const valuesByLanguage = value != null && value.length ? value.filter((item) => !!(item != null && item._key)).reduce((acc, cur) => acc[cur._key] ? __spreadProps$3(__spreadValues$4({}, acc), { [cur._key]: [...acc[cur._key], cur] }) : __spreadProps$3(__spreadValues$4({}, acc), {
569
- [cur._key]: [cur]
570
- }), {}) : {}, duplicateValues = Object.values(valuesByLanguage).filter((item) => (item == null ? void 0 : item.length) > 1).flat();
683
+ const seenKeys = /* @__PURE__ */ new Set(), duplicateValues = [];
684
+ for (const item of value)
685
+ item != null && item._key && (seenKeys.has(item._key) ? duplicateValues.push(item) : seenKeys.add(item._key));
571
686
  return duplicateValues.length ? {
572
687
  message: "There can only be one field per language",
573
688
  paths: duplicateValues.map((item) => [{ _key: item._key }])
@@ -595,6 +710,28 @@ var __defProp$3 = Object.defineProperty, __defProps$2 = Object.defineProperties,
595
710
  function InternationalizedInput(props) {
596
711
  const parentValue = sanity.useFormValue(
597
712
  props.path.slice(0, -1)
713
+ ), originalOnChange = props.inputProps.onChange, wrappedOnChange = react.useCallback(
714
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
715
+ (patches) => {
716
+ var _a;
717
+ if (!Array.isArray(patches))
718
+ return originalOnChange(patches);
719
+ const valueField = (_a = props.value) == null ? void 0 : _a.value;
720
+ if ((valueField == null || Array.isArray(valueField) && valueField.length === 0) && patches.some((patch) => !patch || typeof patch != "object" ? !1 : patch.type === "insert" && patch.path && Array.isArray(patch.path) && patch.path.length > 0 ? patch.path[0] === "value" || typeof patch.path[0] == "number" : !1)) {
721
+ const initPatch = valueField === void 0 ? { type: "setIfMissing", path: ["value"], value: [] } : null, fixedPatches = patches.map((patch) => {
722
+ if (!patch || typeof patch != "object")
723
+ return patch;
724
+ if (patch.type === "insert" && patch.path && Array.isArray(patch.path)) {
725
+ const fixedPath = patch.path[0] === "value" ? patch.path : ["value", ...patch.path];
726
+ return __spreadProps$2(__spreadValues$3({}, patch), { path: fixedPath });
727
+ }
728
+ return patch;
729
+ }), allPatches = initPatch ? [initPatch, ...fixedPatches] : fixedPatches;
730
+ return originalOnChange(allPatches);
731
+ }
732
+ return originalOnChange(patches);
733
+ },
734
+ [props.value, originalOnChange]
598
735
  ), inlineProps = __spreadProps$2(__spreadValues$3({}, props.inputProps), {
599
736
  // This is the magic that makes inline editing work?
600
737
  members: props.inputProps.members.filter(
@@ -602,7 +739,9 @@ function InternationalizedInput(props) {
602
739
  ),
603
740
  // This just overrides the type
604
741
  // Remove this as it shouldn't be necessary?
605
- value: props.value
742
+ value: props.value,
743
+ // Use our wrapped onChange handler
744
+ onChange: wrappedOnChange
606
745
  }), { validation, value, onChange, readOnly } = inlineProps, { languages, languageDisplay, defaultLanguages } = useInternationalizedArrayContext(), languageKeysInUse = react.useMemo(
607
746
  () => {
608
747
  var _a;
@@ -652,7 +791,7 @@ function InternationalizedInput(props) {
652
791
  }
653
792
  ) }),
654
793
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
655
- /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { flex: 1, tone: "inherit", children: props.inputProps.renderInput(props.inputProps) }),
794
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { flex: 1, tone: "inherit", children: props.inputProps.renderInput(inlineProps) }),
656
795
  /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { tone: "inherit", children: isDefault ? /* @__PURE__ */ jsxRuntime.jsx(
657
796
  ui.Tooltip,
658
797
  {