@liiift-studio/sanity-font-manager 2.5.1 → 2.5.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liiift-studio/sanity-font-manager",
3
- "version": "2.5.1",
3
+ "version": "2.5.3",
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",
@@ -459,7 +459,7 @@ export const BatchUploadFonts = () => {
459
459
  >
460
460
  {ext}
461
461
  </Text>
462
- <Box style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
462
+ <Box style={{ flex: 1, minWidth: 0 }}>
463
463
  <Text size={1}>{file.name}</Text>
464
464
  </Box>
465
465
  </Flex>
@@ -148,72 +148,80 @@ export function KeyValueReferenceInput(props) {
148
148
  <Box>
149
149
  <Stack space={2}>
150
150
  {pairs.map((pair, index) => (
151
- <Box key={index} style={{ position: 'relative' }}>
151
+ <Flex key={index} gap={1} align="center">
152
152
  {/* Reorder buttons */}
153
- <div style={{ position: 'absolute', height: '100%', top: '0', left: '-5px', width: 'min-content', transform: 'translate(-100%, 0%)' }}>
154
- <button className="manualButton manualButtonUp" style={{ fontSize: '15px', height: '50%' }} onClick={() => handleMoveUp(index)}>
155
- <ArrowUpIcon />
156
- </button>
157
- <button className="manualButton manualButtonDown" style={{ fontSize: '15px', height: '50%' }} onClick={() => handleMoveDown(index)}>
158
- <ArrowDownIcon />
159
- </button>
160
- </div>
161
-
162
- <Flex gap={2} align="flex-start">
163
- {/* Key input */}
164
- <Box flex={1}>
165
- <TextInput
166
- value={pair.key}
167
- onChange={(e) => handlePairChange(index, 'key', e.target.value)}
168
- placeholder={keyPlaceholder}
169
- />
170
- </Box>
171
-
172
- {/* Reference display or empty-state picker trigger */}
173
- <Box flex={1} style={{ minHeight: '100%' }}>
174
- {pair.value?._ref ? (
175
- <Card className="referenceCard" radius={2} tone="primary" style={{ paddingLeft: '1rem', height: 'fit-content' }}>
176
- <Flex align="center" justify="space-between">
177
- <Text
178
- size={2}
179
- style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: '90%' }}
180
- >
181
- {referenceData[pair.value._ref] || 'Loading...'}
182
- </Text>
183
- <MenuButton
184
- button={<Button icon={EllipsisHorizontalIcon} mode="bleed" title="Options" />}
185
- id={`ref-options-${index}`}
186
- menu={
187
- <Menu>
188
- <MenuItem tone="critical" icon={TrashIcon} text="Remove" onClick={() => handlePairChange(index, 'value', null)} />
189
- <MenuItem icon={SyncIcon} text="Replace" onClick={() => openReferenceSelector(index)} />
190
- </Menu>
191
- }
192
- popover={{ portal: true, tone: 'default', placement: 'left' }}
193
- />
194
- </Flex>
195
- </Card>
196
- ) : (
197
- <Box
198
- padding={2}
199
- style={{ minHeight: '100%', border: '1px dashed #ccc', borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}
200
- onClick={() => openReferenceSelector(index)}
201
- >
202
- <Text muted size={2}>Click to select a {pickerLabel}</Text>
203
- </Box>
204
- )}
205
- </Box>
153
+ <Flex direction="column" style={{ flexShrink: 0 }}>
154
+ <Button
155
+ mode="bleed"
156
+ icon={ArrowUpIcon}
157
+ padding={1}
158
+ fontSize={0}
159
+ onClick={() => handleMoveUp(index)}
160
+ disabled={index === 0}
161
+ style={{ cursor: index === 0 ? 'default' : 'pointer' }}
162
+ />
163
+ <Button
164
+ mode="bleed"
165
+ icon={ArrowDownIcon}
166
+ padding={1}
167
+ fontSize={0}
168
+ onClick={() => handleMoveDown(index)}
169
+ disabled={index === pairs.length - 1}
170
+ style={{ cursor: index === pairs.length - 1 ? 'default' : 'pointer' }}
171
+ />
206
172
  </Flex>
207
173
 
174
+ {/* Key input */}
175
+ <Box flex={1}>
176
+ <TextInput
177
+ value={pair.key}
178
+ onChange={(e) => handlePairChange(index, 'key', e.target.value)}
179
+ placeholder={keyPlaceholder}
180
+ />
181
+ </Box>
182
+
183
+ {/* Reference display or empty-state picker trigger */}
184
+ <Box flex={1}>
185
+ {pair.value?._ref ? (
186
+ <Card radius={2} tone="primary" style={{ paddingLeft: '0.75rem', height: 'fit-content' }}>
187
+ <Flex align="center" justify="space-between">
188
+ <Text size={2} style={{ whiteSpace: 'nowrap' }}>
189
+ {referenceData[pair.value._ref] || 'Loading...'}
190
+ </Text>
191
+ <MenuButton
192
+ button={<Button icon={EllipsisHorizontalIcon} mode="bleed" title="Options" />}
193
+ id={`ref-options-${index}`}
194
+ menu={
195
+ <Menu>
196
+ <MenuItem tone="critical" icon={TrashIcon} text="Remove" onClick={() => handlePairChange(index, 'value', null)} />
197
+ <MenuItem icon={SyncIcon} text="Replace" onClick={() => openReferenceSelector(index)} />
198
+ </Menu>
199
+ }
200
+ popover={{ portal: true, tone: 'default', placement: 'left' }}
201
+ />
202
+ </Flex>
203
+ </Card>
204
+ ) : (
205
+ <Box
206
+ padding={2}
207
+ style={{ border: '1px dashed #ccc', borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}
208
+ onClick={() => openReferenceSelector(index)}
209
+ >
210
+ <Text muted size={2}>Click to select a {pickerLabel}</Text>
211
+ </Box>
212
+ )}
213
+ </Box>
214
+
208
215
  {/* Remove button */}
209
- <button
210
- className="manualButton"
216
+ <Button
217
+ mode="bleed"
218
+ tone="critical"
219
+ icon={TrashIcon}
220
+ padding={2}
211
221
  onClick={() => handleRemovePair(index)}
212
- style={{ position: 'absolute', top: '0', right: '-7px', transform: 'translate(100%, 0%)' }}
213
- >
214
- <TrashIcon />
215
- </button>
216
- </Box>
222
+ style={{ flexShrink: 0, cursor: 'pointer' }}
223
+ />
224
+ </Flex>
217
225
  ))}
218
226
  </Stack>
219
227
  </Box>
@@ -479,7 +479,7 @@ export const SingleUploaderTool = (props) => {
479
479
  {formatUpper}
480
480
  </Text>
481
481
  {hasFile ? (
482
- <Box style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
482
+ <Box style={{ flex: 1, minWidth: 0 }}>
483
483
  <a href={fileUrl} target="_blank" rel="noreferrer">{filenames?.[format] || 'File'}</a>
484
484
  </Box>
485
485
  ) : (
@@ -520,7 +520,7 @@ export const SingleUploaderTool = (props) => {
520
520
  {label}
521
521
  </Text>
522
522
  {hasFile ? (
523
- <Box style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
523
+ <Box style={{ flex: 1, minWidth: 0 }}>
524
524
  <a href={fileUrl} target="_blank" rel="noreferrer">{filename || 'File'}</a>
525
525
  </Box>
526
526
  ) : (
@@ -561,7 +561,7 @@ export const SingleUploaderTool = (props) => {
561
561
  CSS
562
562
  </Text>
563
563
  {hasFile ? (
564
- <Box style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
564
+ <Box style={{ flex: 1, minWidth: 0 }}>
565
565
  <a href={fileUrl} target="_blank" rel="noreferrer">{filenames?.css || 'File'}</a>
566
566
  </Box>
567
567
  ) : (
@@ -235,7 +235,7 @@ export default function UploadStep1Settings({ settings, onStartProcessing }) {
235
235
  >
236
236
  {ext.toUpperCase()}
237
237
  </Badge>
238
- <Text size={1} style={{ flex: 1, textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
238
+ <Text size={1} style={{ flex: 1 }}>
239
239
  {file.name}
240
240
  </Text>
241
241
  <Button
@@ -181,7 +181,7 @@ export default function UploadStep3Execute({
181
181
  return (
182
182
  <Card key={entry.tempId} border radius={1} padding={2}>
183
183
  <Flex align="center" gap={2}>
184
- <Text size={1} style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>
184
+ <Text size={1} style={{ flex: 1 }}>
185
185
  {entry.title}
186
186
  </Text>
187
187
  <Box style={{ width: 120, flexShrink: 0, textAlign: 'right' }}>
@@ -2,6 +2,8 @@
2
2
  import React from 'react';
3
3
  import { AdvancedRefArray } from '@liiift-studio/sanity-advanced-reference-array';
4
4
  import { RegenerateSubfamiliesComponent } from '../components/RegenerateSubfamiliesComponent.jsx';
5
+ import { GenerateCollectionsPairsComponent } from '../components/GenerateCollectionsPairsComponent.jsx';
6
+ import { PrimaryCollectionGeneratorTypeface } from '../components/PrimaryCollectionGeneratorTypeface.jsx';
5
7
 
6
8
  // Returns extra GROQ params scoped to the current typeface document
7
9
  const typefaceParams = (doc) => ({ typefaceName: doc?.title || '' });
@@ -93,6 +95,8 @@ export function createStylesField({
93
95
  subfamilyFontFilter = false,
94
96
  subfamilyPreview = false,
95
97
  pairs = true,
98
+ generateCollections = false,
99
+ generateFullFamilyCollection = false,
96
100
  } = {}) {
97
101
 
98
102
  const subfamilyFields = [
@@ -236,6 +240,22 @@ export function createStylesField({
236
240
  type: 'array',
237
241
  of: [subfamilyItem],
238
242
  },
243
+ ...field(generateCollections, {
244
+ title: 'Generate Collections and Pairs',
245
+ name: 'generateCollections',
246
+ type: 'string',
247
+ description: "Generate Collections and Pairs from the typeface's fonts.",
248
+ components: { input: GenerateCollectionsPairsComponent },
249
+ hidden: ({ parent }) => !parent?.fonts?.length,
250
+ }),
251
+ ...field(generateFullFamilyCollection, {
252
+ title: 'Generate Full Family Collection',
253
+ name: 'generateCollectionGroup',
254
+ type: 'string',
255
+ description: 'Generate a Collection that includes all styles from this typeface.',
256
+ components: { input: PrimaryCollectionGeneratorTypeface },
257
+ hidden: ({ parent }) => !parent?.fonts?.length,
258
+ }),
239
259
  {
240
260
  title: 'Collections',
241
261
  name: 'collections',
@@ -1,26 +1,22 @@
1
1
  // Async font parser — wraps lib-font event model in a Promise with decompressor bootstrap
2
2
 
3
- import pako from 'pako';
4
-
5
- // Set decompressor globals BEFORE lib-font is imported.
6
- // lib-font reads globalThis.pako and globalThis.unbrotli at module evaluation time
7
- // (top of woff.js / woff2.js), not at parse time. These must be set before lib-font
8
- // is first evaluated, so we use dynamic import() below to guarantee ordering.
9
- globalThis.pako = pako;
10
-
11
- // unbrotli is a UMD that sets globalThis.unbrotli as a side effect on evaluation.
12
- // We vendor it from lib-font/lib/unbrotli.js because the subpath is not in
13
- // lib-font's exports map (ERR_PACKAGE_PATH_NOT_EXPORTED).
14
- import '../vendor/unbrotli.js';
15
-
16
3
  // Lazy-loaded lib-font Font constructor — resolved on first parseFont() call.
17
- // Using dynamic import() guarantees globalThis.pako and globalThis.unbrotli are
18
- // set before lib-font evaluates, which static imports cannot guarantee in ESM.
4
+ // All decompressor globals (pako for WOFF, unbrotli for WOFF2) are set dynamically
5
+ // inside getFont() BEFORE lib-font is imported, guaranteeing correct evaluation order
6
+ // regardless of how the bundler (tsup/esbuild/vite) reorders static imports.
19
7
  let _Font = null;
20
8
 
21
- /** Returns the lib-font Font constructor, loading it on first call */
9
+ /** Returns the lib-font Font constructor, bootstrapping decompressors on first call */
22
10
  async function getFont() {
23
11
  if (!_Font) {
12
+ // Set pako (zlib) for WOFF decompression
13
+ const pako = await import('pako');
14
+ globalThis.pako = pako.default || pako;
15
+
16
+ // Set unbrotli for WOFF2 decompression — UMD side-effect sets globalThis.unbrotli
17
+ await import('../vendor/unbrotli.js');
18
+
19
+ // NOW safe to import lib-font — both globals are set
24
20
  const mod = await import('lib-font');
25
21
  _Font = mod.Font;
26
22
  }
@@ -1,6 +0,0 @@
1
- import {
2
- UploadModal
3
- } from "./chunk-TMDE4A54.mjs";
4
- export {
5
- UploadModal as default
6
- };
@@ -1,6 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
-
3
- var _chunkJCDZ7SWZjs = require('./chunk-JCDZ7SWZ.js');
4
-
5
-
6
- exports.default = _chunkJCDZ7SWZjs.UploadModal;