@liiift-studio/sanity-font-manager 2.5.5 → 2.5.7

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/dist/index.js CHANGED
@@ -7648,6 +7648,25 @@ var require_unbrotli = __commonJS({
7648
7648
  }
7649
7649
  });
7650
7650
 
7651
+ // src/utils/setupDecompressors.js
7652
+ var import_unbrotli;
7653
+ var init_setupDecompressors = __esm({
7654
+ "src/utils/setupDecompressors.js"() {
7655
+ init_pako_esm();
7656
+ import_unbrotli = __toESM(require_unbrotli());
7657
+ globalThis.pako = pako;
7658
+ if (!globalThis.unbrotli) {
7659
+ try {
7660
+ const brotli = require_unbrotli();
7661
+ if (typeof brotli === "function") {
7662
+ globalThis.unbrotli = brotli;
7663
+ }
7664
+ } catch {
7665
+ }
7666
+ }
7667
+ }
7668
+ });
7669
+
7651
7670
  // node_modules/lib-font/src/utils/shim-fetch.js
7652
7671
  var fetchFunction;
7653
7672
  var init_shim_fetch = __esm({
@@ -12433,31 +12452,38 @@ async function parseFont(buffer, filename) {
12433
12452
  throw new Error(`Font file exceeds ${MAX_FONT_FILE_SIZE / 1024 / 1024}MB limit: ${filename} (${(buffer.byteLength / 1024 / 1024).toFixed(1)}MB)`);
12434
12453
  }
12435
12454
  return new Promise((resolve, reject) => {
12455
+ let settled = false;
12456
+ const settle = (fn) => (...args) => {
12457
+ if (!settled) {
12458
+ settled = true;
12459
+ clearTimeout(timer);
12460
+ fn(...args);
12461
+ }
12462
+ };
12463
+ const timer = setTimeout(() => {
12464
+ settle(reject)(new Error(`Parsing timed out for ${filename} (${PARSE_TIMEOUT_MS / 1e3}s). The file may be corrupted or in an unsupported format.`));
12465
+ }, PARSE_TIMEOUT_MS);
12436
12466
  const font2 = new Font("font", { skipStyleSheet: true });
12437
- font2.onload = (evt) => resolve(evt.detail.font);
12438
- font2.onerror = (evt) => {
12467
+ font2.onload = settle((evt) => resolve(evt.detail.font));
12468
+ font2.onerror = settle((evt) => {
12439
12469
  var _a;
12440
12470
  const msg = ((_a = evt.detail) == null ? void 0 : _a.message) || `Failed to parse ${filename}`;
12441
12471
  reject(new Error(msg));
12442
- };
12472
+ });
12443
12473
  try {
12444
12474
  font2.fromDataBuffer(buffer, filename);
12445
12475
  } catch (err2) {
12446
- reject(new Error(`${filename}: ${err2.message}`));
12476
+ settle(reject)(new Error(`${filename}: ${err2.message}`));
12447
12477
  }
12448
12478
  });
12449
12479
  }
12450
- var import_unbrotli2, MAX_FONT_FILE_SIZE;
12480
+ var MAX_FONT_FILE_SIZE, PARSE_TIMEOUT_MS;
12451
12481
  var init_parseFont = __esm({
12452
12482
  "src/utils/parseFont.js"() {
12453
- init_pako_esm();
12454
- import_unbrotli2 = __toESM(require_unbrotli());
12483
+ init_setupDecompressors();
12455
12484
  init_lib_font();
12456
- globalThis.pako = pako;
12457
- if (!globalThis.unbrotli) {
12458
- console.warn("[parseFont] globalThis.unbrotli not set after vendor import \u2014 WOFF2 parsing may fail. TTF/OTF files will still work.");
12459
- }
12460
12485
  MAX_FONT_FILE_SIZE = 50 * 1024 * 1024;
12486
+ PARSE_TIMEOUT_MS = 3e4;
12461
12487
  }
12462
12488
  });
12463
12489
 
@@ -16818,6 +16844,7 @@ __export(index_exports, {
16818
16844
  createFontDecisions: () => createFontDecisions,
16819
16845
  createFontObject: () => createFontObject,
16820
16846
  createInitialExecutionState: () => createInitialExecutionState,
16847
+ createOpenTypeField: () => createOpenTypeField,
16821
16848
  createStylesField: () => createStylesField,
16822
16849
  determineWeight: () => determineWeight,
16823
16850
  escapeCssFontName: () => escapeCssFontName,
@@ -16871,11 +16898,7 @@ __export(index_exports, {
16871
16898
  useSanityClient: () => useSanityClient
16872
16899
  });
16873
16900
  module.exports = __toCommonJS(index_exports);
16874
-
16875
- // src/utils/setupDecompressors.js
16876
- init_pako_esm();
16877
- var import_unbrotli = __toESM(require_unbrotli());
16878
- globalThis.pako = pako;
16901
+ init_setupDecompressors();
16879
16902
 
16880
16903
  // src/components/BatchUploadFonts.jsx
16881
16904
  var import_react14 = __toESM(require("react"));
@@ -22430,6 +22453,36 @@ var openTypeField = {
22430
22453
  }
22431
22454
  ]
22432
22455
  };
22456
+ function createOpenTypeField({
22457
+ customText = false,
22458
+ customTextType = "string"
22459
+ } = {}) {
22460
+ if (!customText) return { ...openTypeField };
22461
+ const fields = openTypeField.fields.map((field2) => {
22462
+ if (field2.type !== "object" || !field2.fields) return field2;
22463
+ const updatedSubfields = field2.fields.map((subfield) => {
22464
+ if (subfield.name !== "customText") return subfield;
22465
+ const updated = { ...subfield, hidden: false };
22466
+ if (customTextType === "code") {
22467
+ updated.type = "code";
22468
+ updated.options = {
22469
+ language: "html",
22470
+ languageAlternatives: [
22471
+ { title: "HTML", value: "html" }
22472
+ ],
22473
+ withFilename: false
22474
+ };
22475
+ updated.description = 'Use the field below to input custom text to highlight the feature. Wrap featured characters in <span class="bold">CHARACTER</span>.';
22476
+ }
22477
+ return updated;
22478
+ });
22479
+ return { ...field2, fields: updatedSubfields };
22480
+ });
22481
+ return {
22482
+ ...openTypeField,
22483
+ fields
22484
+ };
22485
+ }
22433
22486
 
22434
22487
  // src/schema/styleCountField.js
22435
22488
  var styleCountField = {
@@ -23047,6 +23100,7 @@ init_generateKeywords();
23047
23100
  createFontDecisions,
23048
23101
  createFontObject,
23049
23102
  createInitialExecutionState,
23103
+ createOpenTypeField,
23050
23104
  createStylesField,
23051
23105
  determineWeight,
23052
23106
  escapeCssFontName,
package/dist/index.mjs CHANGED
@@ -7653,6 +7653,25 @@ var require_unbrotli = __commonJS({
7653
7653
  }
7654
7654
  });
7655
7655
 
7656
+ // src/utils/setupDecompressors.js
7657
+ var import_unbrotli;
7658
+ var init_setupDecompressors = __esm({
7659
+ "src/utils/setupDecompressors.js"() {
7660
+ init_pako_esm();
7661
+ import_unbrotli = __toESM(require_unbrotli());
7662
+ globalThis.pako = pako;
7663
+ if (!globalThis.unbrotli) {
7664
+ try {
7665
+ const brotli = require_unbrotli();
7666
+ if (typeof brotli === "function") {
7667
+ globalThis.unbrotli = brotli;
7668
+ }
7669
+ } catch {
7670
+ }
7671
+ }
7672
+ }
7673
+ });
7674
+
7656
7675
  // node_modules/lib-font/src/utils/shim-fetch.js
7657
7676
  var fetchFunction;
7658
7677
  var init_shim_fetch = __esm({
@@ -12438,31 +12457,38 @@ async function parseFont(buffer, filename) {
12438
12457
  throw new Error(`Font file exceeds ${MAX_FONT_FILE_SIZE / 1024 / 1024}MB limit: ${filename} (${(buffer.byteLength / 1024 / 1024).toFixed(1)}MB)`);
12439
12458
  }
12440
12459
  return new Promise((resolve, reject) => {
12460
+ let settled = false;
12461
+ const settle = (fn) => (...args) => {
12462
+ if (!settled) {
12463
+ settled = true;
12464
+ clearTimeout(timer);
12465
+ fn(...args);
12466
+ }
12467
+ };
12468
+ const timer = setTimeout(() => {
12469
+ settle(reject)(new Error(`Parsing timed out for ${filename} (${PARSE_TIMEOUT_MS / 1e3}s). The file may be corrupted or in an unsupported format.`));
12470
+ }, PARSE_TIMEOUT_MS);
12441
12471
  const font2 = new Font("font", { skipStyleSheet: true });
12442
- font2.onload = (evt) => resolve(evt.detail.font);
12443
- font2.onerror = (evt) => {
12472
+ font2.onload = settle((evt) => resolve(evt.detail.font));
12473
+ font2.onerror = settle((evt) => {
12444
12474
  var _a;
12445
12475
  const msg = ((_a = evt.detail) == null ? void 0 : _a.message) || `Failed to parse ${filename}`;
12446
12476
  reject(new Error(msg));
12447
- };
12477
+ });
12448
12478
  try {
12449
12479
  font2.fromDataBuffer(buffer, filename);
12450
12480
  } catch (err2) {
12451
- reject(new Error(`${filename}: ${err2.message}`));
12481
+ settle(reject)(new Error(`${filename}: ${err2.message}`));
12452
12482
  }
12453
12483
  });
12454
12484
  }
12455
- var import_unbrotli2, MAX_FONT_FILE_SIZE;
12485
+ var MAX_FONT_FILE_SIZE, PARSE_TIMEOUT_MS;
12456
12486
  var init_parseFont = __esm({
12457
12487
  "src/utils/parseFont.js"() {
12458
- init_pako_esm();
12459
- import_unbrotli2 = __toESM(require_unbrotli());
12488
+ init_setupDecompressors();
12460
12489
  init_lib_font();
12461
- globalThis.pako = pako;
12462
- if (!globalThis.unbrotli) {
12463
- console.warn("[parseFont] globalThis.unbrotli not set after vendor import \u2014 WOFF2 parsing may fail. TTF/OTF files will still work.");
12464
- }
12465
12490
  MAX_FONT_FILE_SIZE = 50 * 1024 * 1024;
12491
+ PARSE_TIMEOUT_MS = 3e4;
12466
12492
  }
12467
12493
  });
12468
12494
 
@@ -16770,10 +16796,8 @@ var init_UploadModal = __esm({
16770
16796
  }
16771
16797
  });
16772
16798
 
16773
- // src/utils/setupDecompressors.js
16774
- init_pako_esm();
16775
- var import_unbrotli = __toESM(require_unbrotli());
16776
- globalThis.pako = pako;
16799
+ // src/index.js
16800
+ init_setupDecompressors();
16777
16801
 
16778
16802
  // src/components/BatchUploadFonts.jsx
16779
16803
  import React13, { useCallback as useCallback7, useState as useState9, useMemo as useMemo9, useRef as useRef4, useEffect as useEffect6, lazy as lazy2, Suspense } from "react";
@@ -22328,6 +22352,36 @@ var openTypeField = {
22328
22352
  }
22329
22353
  ]
22330
22354
  };
22355
+ function createOpenTypeField({
22356
+ customText = false,
22357
+ customTextType = "string"
22358
+ } = {}) {
22359
+ if (!customText) return { ...openTypeField };
22360
+ const fields = openTypeField.fields.map((field2) => {
22361
+ if (field2.type !== "object" || !field2.fields) return field2;
22362
+ const updatedSubfields = field2.fields.map((subfield) => {
22363
+ if (subfield.name !== "customText") return subfield;
22364
+ const updated = { ...subfield, hidden: false };
22365
+ if (customTextType === "code") {
22366
+ updated.type = "code";
22367
+ updated.options = {
22368
+ language: "html",
22369
+ languageAlternatives: [
22370
+ { title: "HTML", value: "html" }
22371
+ ],
22372
+ withFilename: false
22373
+ };
22374
+ updated.description = 'Use the field below to input custom text to highlight the feature. Wrap featured characters in <span class="bold">CHARACTER</span>.';
22375
+ }
22376
+ return updated;
22377
+ });
22378
+ return { ...field2, fields: updatedSubfields };
22379
+ });
22380
+ return {
22381
+ ...openTypeField,
22382
+ fields
22383
+ };
22384
+ }
22331
22385
 
22332
22386
  // src/schema/styleCountField.js
22333
22387
  var styleCountField = {
@@ -22944,6 +22998,7 @@ export {
22944
22998
  createFontDecisions,
22945
22999
  createFontObject,
22946
23000
  createInitialExecutionState,
23001
+ createOpenTypeField,
22947
23002
  createStylesField,
22948
23003
  determineWeight,
22949
23004
  escapeCssFontName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liiift-studio/sanity-font-manager",
3
- "version": "2.5.5",
3
+ "version": "2.5.7",
4
4
  "description": "Sanity Studio plugin — full font management suite with batch upload, format conversion, metadata extraction, CSS generation, collection/pair generation, and script variant support. Supports Sanity v3, v4, and v5.",
5
5
  "license": "MIT",
6
6
  "author": "Liiift Studio",
package/src/index.js CHANGED
@@ -106,7 +106,7 @@ export {
106
106
  } from './utils/fontHelpers.js';
107
107
 
108
108
  // Schema field definitions
109
- export { openTypeField } from './schema/openTypeField.js';
109
+ export { openTypeField, createOpenTypeField } from './schema/openTypeField.js';
110
110
  export { styleCountField } from './schema/styleCountField.js';
111
111
  export { stylisticSetField } from './schema/stylisticSetField.js';
112
112
  export { createStylesField } from './schema/stylesField.js';
@@ -1943,3 +1943,53 @@ export const openTypeField = {
1943
1943
 
1944
1944
  ],
1945
1945
  };
1946
+
1947
+ /**
1948
+ * Factory function to create a customised OpenType field.
1949
+ * The static `openTypeField` export remains unchanged for backwards compatibility.
1950
+ *
1951
+ * @param {object} [options]
1952
+ * @param {boolean} [options.customText=false] - Unhide the customText field on each feature
1953
+ * @param {'string'|'code'} [options.customTextType='string'] - Type for customText field.
1954
+ * Use 'code' for an HTML code editor (requires @sanity/code-input plugin).
1955
+ * @returns {object} Sanity field definition
1956
+ */
1957
+ export function createOpenTypeField({
1958
+ customText = false,
1959
+ customTextType = 'string',
1960
+ } = {}) {
1961
+ if (!customText) return { ...openTypeField };
1962
+
1963
+ // Deep clone fields and unhide/retype customText on each feature object
1964
+ const fields = openTypeField.fields.map(field => {
1965
+ // Skip the top-level 'features' checkbox array
1966
+ if (field.type !== 'object' || !field.fields) return field;
1967
+
1968
+ const updatedSubfields = field.fields.map(subfield => {
1969
+ if (subfield.name !== 'customText') return subfield;
1970
+
1971
+ const updated = { ...subfield, hidden: false };
1972
+
1973
+ if (customTextType === 'code') {
1974
+ updated.type = 'code';
1975
+ updated.options = {
1976
+ language: 'html',
1977
+ languageAlternatives: [
1978
+ { title: 'HTML', value: 'html' },
1979
+ ],
1980
+ withFilename: false,
1981
+ };
1982
+ updated.description = 'Use the field below to input custom text to highlight the feature. Wrap featured characters in <span class="bold">CHARACTER</span>.';
1983
+ }
1984
+
1985
+ return updated;
1986
+ });
1987
+
1988
+ return { ...field, fields: updatedSubfields };
1989
+ });
1990
+
1991
+ return {
1992
+ ...openTypeField,
1993
+ fields,
1994
+ };
1995
+ }
@@ -1,39 +1,22 @@
1
- // Async font parser — ensures decompressor globals are set, with graceful WOFF2 fallback
1
+ // Async font parser — decompressor globals must be set before lib-font is imported
2
2
 
3
- import pako from 'pako';
4
- import '../vendor/unbrotli.js';
3
+ import './setupDecompressors.js';
5
4
  import { Font } from 'lib-font';
6
5
 
7
- // Immediately set globals — this runs at module evaluation time.
8
- // lib-font's woff.js reads globalThis.pako and woff2.js reads globalThis.unbrotli
9
- // at their top level. If this module evaluates before lib-font (which tsup guarantees
10
- // since we import pako and unbrotli first), the globals will be set in time.
11
- //
12
- // When Vite re-bundles our package, it may reorder evaluation so lib-font's woff2.js
13
- // captures globalThis.unbrotli as undefined. In that case, WOFF2 parsing will fail
14
- // and buildUploadPlan falls back to TTF companion metadata.
15
- globalThis.pako = pako;
16
- // unbrotli.js UMD sets globalThis.unbrotli as a side effect — verify it worked
17
- if (!globalThis.unbrotli) {
18
- console.warn('[parseFont] globalThis.unbrotli not set after vendor import — WOFF2 parsing may fail. TTF/OTF files will still work.');
19
- }
20
-
21
6
  /** Maximum font file size accepted for parsing (50 MB) */
22
7
  const MAX_FONT_FILE_SIZE = 50 * 1024 * 1024;
23
8
 
9
+ /** Parse timeout — prevents hanging if lib-font silently fails (30 seconds) */
10
+ const PARSE_TIMEOUT_MS = 30000;
11
+
24
12
  /**
25
13
  * Parse a font file from an ArrayBuffer.
26
14
  * Returns a lib-font Font object with all tables accessible via font.opentype.tables.*.
27
15
  *
28
- * WOFF2 note: If the brotli decoder couldn't be initialized (common with Vite pre-bundling),
29
- * WOFF2 files will fail to parse. The upload plan handles this gracefully — WOFF2 files
30
- * that share a name with a TTF/OTF get metadata from the companion file. Standalone WOFF2
31
- * uploads will show a clear error directing the user to also include TTF/OTF files.
32
- *
33
16
  * @param {ArrayBuffer} buffer - Raw font file bytes
34
17
  * @param {string} filename - Original filename (used for format detection by lib-font)
35
18
  * @returns {Promise<import('lib-font').Font>} Parsed lib-font Font object
36
- * @throws {Error} If the file exceeds MAX_FONT_FILE_SIZE or parsing fails
19
+ * @throws {Error} If the file exceeds MAX_FONT_FILE_SIZE, parsing fails, or times out
37
20
  */
38
21
  export async function parseFont(buffer, filename) {
39
22
  if (buffer.byteLength > MAX_FONT_FILE_SIZE) {
@@ -41,17 +24,33 @@ export async function parseFont(buffer, filename) {
41
24
  }
42
25
 
43
26
  return new Promise((resolve, reject) => {
27
+ let settled = false;
28
+
29
+ const settle = (fn) => (...args) => {
30
+ if (!settled) {
31
+ settled = true;
32
+ clearTimeout(timer);
33
+ fn(...args);
34
+ }
35
+ };
36
+
37
+ // Timeout guard — prevents infinite hang if lib-font fails silently
38
+ const timer = setTimeout(() => {
39
+ settle(reject)(new Error(`Parsing timed out for ${filename} (${PARSE_TIMEOUT_MS / 1000}s). The file may be corrupted or in an unsupported format.`));
40
+ }, PARSE_TIMEOUT_MS);
41
+
44
42
  const font = new Font('font', { skipStyleSheet: true });
45
- font.onload = (evt) => resolve(evt.detail.font);
46
- font.onerror = (evt) => {
43
+ font.onload = settle((evt) => resolve(evt.detail.font));
44
+ font.onerror = settle((evt) => {
47
45
  const msg = evt.detail?.message || `Failed to parse ${filename}`;
48
46
  reject(new Error(msg));
49
- };
47
+ });
48
+
50
49
  try {
51
50
  font.fromDataBuffer(buffer, filename);
52
51
  } catch (err) {
53
- // lib-font may throw synchronously from WOFF2 constructor if brotli decoder is missing
54
- reject(new Error(`${filename}: ${err.message}`));
52
+ // Catches synchronous throws from WOFF2 constructor when brotli is missing
53
+ settle(reject)(new Error(`${filename}: ${err.message}`));
55
54
  }
56
55
  });
57
56
  }
@@ -1,10 +1,27 @@
1
1
  // Sets up globalThis.pako and globalThis.unbrotli for lib-font WOFF/WOFF2 decompression.
2
- // This module MUST be imported before lib-font is ever evaluated.
3
- // It runs synchronously at module evaluation time to set the globals.
2
+ // Must be imported before lib-font.
4
3
 
5
4
  import pako from 'pako';
6
- import '../vendor/unbrotli.js';
7
5
 
6
+ // Set pako for WOFF (zlib) decompression
8
7
  globalThis.pako = pako;
9
- // unbrotli.js is a UMD that sets globalThis.unbrotli as a side effect on evaluation.
10
- // The import above triggers that side effect.
8
+
9
+ // Set unbrotli for WOFF2 (brotli) decompression
10
+ // The vendor unbrotli.js UMD sets globalThis.unbrotli in browser contexts.
11
+ // In Node/bundler contexts it exports via module.exports instead.
12
+ // We use a side-effect import and then check if the global was set.
13
+ import '../vendor/unbrotli.js';
14
+
15
+ // If the UMD didn't set the global (CJS path in Node/bundler), try to require it
16
+ if (!globalThis.unbrotli) {
17
+ try {
18
+ // In bundler context, the UMD file's module.exports is available
19
+ // tsup will resolve this at build time
20
+ const brotli = require('../vendor/unbrotli.js');
21
+ if (typeof brotli === 'function') {
22
+ globalThis.unbrotli = brotli;
23
+ }
24
+ } catch {
25
+ // Silently fail — WOFF2 parsing will error gracefully
26
+ }
27
+ }