zyzz 0.0.2 → 0.0.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.
Files changed (44) hide show
  1. package/dist/Config.d.ts +1 -1
  2. package/dist/Config.d.ts.map +1 -1
  3. package/dist/Config.js.map +1 -1
  4. package/dist/Style.d.ts +5 -2
  5. package/dist/Style.d.ts.map +1 -1
  6. package/dist/Style.js +43 -2
  7. package/dist/Style.js.map +1 -1
  8. package/dist/Theme.d.ts +12 -9
  9. package/dist/Theme.d.ts.map +1 -1
  10. package/dist/Theme.js +20 -4
  11. package/dist/Theme.js.map +1 -1
  12. package/dist/compiler/Source.d.ts +2 -0
  13. package/dist/compiler/Source.d.ts.map +1 -1
  14. package/dist/compiler/Source.js +22 -0
  15. package/dist/compiler/Source.js.map +1 -1
  16. package/dist/compiler/Transform.d.ts.map +1 -1
  17. package/dist/compiler/Transform.js +2 -0
  18. package/dist/compiler/Transform.js.map +1 -1
  19. package/dist/compiler/internal/Contract.d.ts.map +1 -1
  20. package/dist/compiler/internal/Contract.js +10 -3
  21. package/dist/compiler/internal/Contract.js.map +1 -1
  22. package/dist/default.d.ts +1218 -1
  23. package/dist/default.d.ts.map +1 -1
  24. package/dist/default.js +304 -0
  25. package/dist/default.js.map +2 -2
  26. package/dist/default.js.zyzz.json +1 -1
  27. package/dist/internal/Shorthands.d.ts +1 -1
  28. package/dist/internal/Shorthands.d.ts.map +1 -1
  29. package/dist/internal/Shorthands.js +1 -0
  30. package/dist/internal/Shorthands.js.map +1 -1
  31. package/dist/internal/Typography.d.ts +23 -0
  32. package/dist/internal/Typography.d.ts.map +1 -0
  33. package/dist/internal/Typography.js +18 -0
  34. package/dist/internal/Typography.js.map +1 -0
  35. package/package.json +1 -1
  36. package/src/Config.ts +1 -0
  37. package/src/Style.ts +91 -27
  38. package/src/Theme.ts +106 -35
  39. package/src/compiler/Source.ts +29 -0
  40. package/src/compiler/Transform.ts +6 -0
  41. package/src/compiler/internal/Contract.ts +19 -5
  42. package/src/default.ts +694 -1
  43. package/src/internal/Shorthands.ts +2 -0
  44. package/src/internal/Typography.ts +51 -0
@@ -0,0 +1,18 @@
1
+ import * as Token from './Token.js';
2
+ /** Resolves the scalar fields owned by one dotted set path. */
3
+ export function fields(theme, name) {
4
+ const data = theme &&
5
+ Object.getOwnPropertyDescriptor(theme, Token.definition)?.value;
6
+ if (!data || typeof name !== 'string')
7
+ return [];
8
+ return properties.filter((property) => Object.hasOwn(data.values, `typography.${name}.${property}`));
9
+ }
10
+ /** Scalar properties applied by a typography set. */
11
+ export const properties = [
12
+ 'fontFamily',
13
+ 'fontSize',
14
+ 'fontWeight',
15
+ 'letterSpacing',
16
+ 'lineHeight',
17
+ ];
18
+ //# sourceMappingURL=Typography.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Typography.js","sourceRoot":"","sources":["../../src/internal/Typography.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAEnC,+DAA+D;AAC/D,MAAM,UAAU,MAAM,CACpB,KAAyB,EACzB,IAAa;IAEb,MAAM,IAAI,GACR,KAAK;QACJ,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,KAE7C,CAAA;IAChB,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAA;IAEhD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CACpC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,IAAI,IAAI,QAAQ,EAAE,CAAC,CAC7D,CAAA;AACH,CAAC;AAeD,qDAAqD;AACrD,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,YAAY;IACZ,UAAU;IACV,YAAY;IACZ,eAAe;IACf,YAAY;CACJ,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zyzz",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "license": "MIT",
5
5
  "description": "Modern, universal, simple styling library for the Web and React Native",
6
6
  "repository": {
package/src/Config.ts CHANGED
@@ -487,6 +487,7 @@ export type Body<
487
487
  | keyof mappings
488
488
  | 'selectors'
489
489
  | 'targets'
490
+ | 'typography'
490
491
  | 'variables'
491
492
  | keyof Style.DeclarationProperties
492
493
  | Exclude<Condition.Keys<tokens, Keys<styles>>, `@layer${string}`>
package/src/Style.ts CHANGED
@@ -7,6 +7,7 @@ import * as Query from './internal/Query.js'
7
7
  import * as Binding from './internal/Binding.js'
8
8
  import type * as Literal from './internal/Literal.js'
9
9
  import * as Token from './internal/Token.js'
10
+ import * as Typography from './internal/Typography.js'
10
11
  import * as Value from './internal/Value.js'
11
12
  import type * as Theme from './Theme.js'
12
13
  import * as Targets from './internal/Targets.js'
@@ -24,6 +25,7 @@ export type Accepted<
24
25
  | Condition.Keys<tokens, Keys<style>>
25
26
  | 'selectors'
26
27
  | 'targets'
28
+ | 'typography'
27
29
  | 'variables'
28
30
  >,
29
31
  never
@@ -55,28 +57,32 @@ export type Accepted<
55
57
  : never
56
58
  }
57
59
  : never
58
- : key extends keyof Literal.Properties
59
- ? Value.Accepted<
60
- Pick<style, key>,
61
- literal extends true
62
- ? LiteralDeclarations
63
- : DeclarationProperties<tokens>
64
- >[key] &
65
- Value.Checked<Pick<style, key>, tokens>[key]
66
- : key extends Condition.Keys<tokens, key>
67
- ? [style[key]] extends [undefined]
68
- ? never
69
- : NonNullable<style[key]> extends Record<string, unknown>
70
- ?
71
- | Accepted<
72
- NonNullable<style[key]>,
73
- tokens,
74
- literal,
75
- targets
76
- >
77
- | Extract<style[key], undefined>
78
- : never
79
- : never
60
+ : key extends 'typography'
61
+ ? literal extends true
62
+ ? never
63
+ : Value.Atom<Typography.Names<tokens>>
64
+ : key extends keyof Literal.Properties
65
+ ? Value.Accepted<
66
+ Pick<style, key>,
67
+ literal extends true
68
+ ? LiteralDeclarations
69
+ : DeclarationProperties<tokens>
70
+ >[key] &
71
+ Value.Checked<Pick<style, key>, tokens>[key]
72
+ : key extends Condition.Keys<tokens, key>
73
+ ? [style[key]] extends [undefined]
74
+ ? never
75
+ : NonNullable<style[key]> extends Record<string, unknown>
76
+ ?
77
+ | Accepted<
78
+ NonNullable<style[key]>,
79
+ tokens,
80
+ literal,
81
+ targets
82
+ >
83
+ | Extract<style[key], undefined>
84
+ : never
85
+ : never
80
86
  }
81
87
  : never)
82
88
 
@@ -364,16 +370,72 @@ export function define(
364
370
  continue
365
371
  }
366
372
 
367
- const properties = authored.flatMap(([property, input]) =>
368
- (mappings?.[property] ?? [property]).map(
369
- (target) => [target, input, property] as const,
370
- ),
373
+ const properties = authored.flatMap(
374
+ ([property, input]): readonly (readonly [string, unknown, string])[] => {
375
+ if (property !== 'typography')
376
+ return (mappings?.[property] ?? [property]).map(
377
+ (target) => [target, input, property] as const,
378
+ )
379
+
380
+ const parsed = Value.parse(input, 'fontFamily')
381
+ if (parsed && 'invalid' in parsed) {
382
+ report(
383
+ 'invalid_value',
384
+ [name, property],
385
+ 'Importance requires the suffix " !important".',
386
+ )
387
+ return []
388
+ }
389
+
390
+ const path = parsed?.value ?? input
391
+ const data = options.theme?.[Token.definition]
392
+ const fields = Typography.fields(options.theme, path)
393
+ if (!fields.length) {
394
+ report(
395
+ 'invalid_value',
396
+ [name, property],
397
+ 'Expected a named typography set from the bound theme.',
398
+ )
399
+ return []
400
+ }
401
+
402
+ // Explicit declarations in this block override the corresponding preset fields.
403
+ const explicit = new Set(
404
+ authored.flatMap(([property]) => mappings?.[property] ?? [property]),
405
+ )
406
+ return fields
407
+ .filter((field) => !explicit.has(field))
408
+ .map((field) => {
409
+ const key = `typography.${path}.${field}`
410
+ const reference = Token.create({
411
+ contract: data!.contract,
412
+ group: field,
413
+ path: key,
414
+ value: data!.values[key]!,
415
+ })
416
+ return [
417
+ field,
418
+ parsed?.important
419
+ ? Token.compose([reference, ' !important'])
420
+ : reference,
421
+ property,
422
+ ] as const
423
+ })
424
+ },
371
425
  )
372
426
 
373
427
  if (properties.some(([key]) => Condition.is(key))) {
374
428
  const rules: Rule[] = []
375
429
 
376
- for (const [key, input] of authored) {
430
+ const blocks = authored.flatMap(([key, input]) =>
431
+ key === 'typography'
432
+ ? properties
433
+ .filter(([, , original]) => original === key)
434
+ .map(([property, value]) => [property, value] as const)
435
+ : [[key, input] as const],
436
+ )
437
+
438
+ for (const [key, input] of blocks) {
377
439
  try {
378
440
  const condition = Condition.is(key)
379
441
  ? Query.resolve(
@@ -707,6 +769,8 @@ export type Properties<
707
769
  readonly targets?:
708
770
  | (targets extends true ? TargetBranches<tokens> : never)
709
771
  | undefined
772
+ /** Applies a named typography set. Explicit fields in the same block take precedence. */
773
+ readonly typography?: Value.Atom<Typography.Names<tokens>>
710
774
  }
711
775
 
712
776
  /** Nested literal declarations retain exact keys at every depth. */
package/src/Theme.ts CHANGED
@@ -10,6 +10,7 @@ import type * as Binding from './internal/Binding.js'
10
10
  import type * as Literal from './internal/Literal.js'
11
11
  import * as Query from './internal/Query.js'
12
12
  import * as Token from './internal/Token.js'
13
+ import * as Typography from './internal/Typography.js'
13
14
  import type * as Value from './internal/Value.js'
14
15
  import type * as Style from './Style.js'
15
16
 
@@ -51,7 +52,7 @@ export type StyleFactory<tokens extends Tokens> = {
51
52
  }
52
53
 
53
54
  /**
54
- * Defines scalar tokens without metadata, defaults, or environment access.
55
+ * Defines scalar tokens and typography sets without defaults or environment access.
55
56
  * Colors currently use the supported literal color grammar; spacing and radius
56
57
  * values use nonnegative literal lengths or zero. Nested palettes are supported.
57
58
  * @throws {InvalidError} If groups, paths, values, or data records are invalid.
@@ -166,7 +167,16 @@ type OverrideTree<tree, group> = tree extends string | number
166
167
  ? Scalar<group>
167
168
  : tree extends { readonly dark: string; readonly light: string }
168
169
  ? Color
169
- : { readonly [key in keyof tree]?: OverrideTree<tree[key], group> }
170
+ : {
171
+ readonly [key in keyof tree]?: OverrideTree<
172
+ tree[key],
173
+ group extends 'typography'
174
+ ? key extends Typography.Property
175
+ ? key
176
+ : group
177
+ : group
178
+ >
179
+ }
170
180
 
171
181
  /** Nested palette or scale data. */
172
182
  export type Palette<leaf> = { readonly [key: string]: leaf | Palette<leaf> }
@@ -177,17 +187,32 @@ export type Reference<group extends Token.Group = Token.Group> =
177
187
 
178
188
  /** The inferred tree replaces scalar values and scheme pairs with references. */
179
189
  export type References<tokens> = {
180
- readonly [group in keyof tokens as group extends Token.Group
190
+ readonly [group in keyof tokens as group extends Token.Group | 'typography'
181
191
  ? group
182
- : never]: ReferenceTree<tokens[group], Extract<group, Token.Group>>
192
+ : never]: ReferenceTree<
193
+ tokens[group],
194
+ Extract<group, Token.Group | 'typography'>
195
+ >
183
196
  }
184
197
 
185
- type ReferenceTree<tree, group extends Token.Group> = tree extends
198
+ type ReferenceTree<
199
+ tree,
200
+ group extends Token.Group | 'typography',
201
+ > = tree extends
186
202
  | string
187
203
  | number
188
204
  | { readonly dark: string; readonly light: string }
189
- ? Reference<group>
190
- : { readonly [key in keyof tree]: ReferenceTree<tree[key], group> }
205
+ ? Reference<Extract<group, Token.Group>>
206
+ : {
207
+ readonly [key in keyof tree]: ReferenceTree<
208
+ tree[key],
209
+ group extends 'typography'
210
+ ? key extends Typography.Property
211
+ ? key
212
+ : group
213
+ : group
214
+ >
215
+ }
191
216
 
192
217
  /** Supported scalar groups; composite presets and query metadata follow separately. */
193
218
  type Scalar<group> = group extends
@@ -253,6 +278,8 @@ export type Tokens = {
253
278
  readonly spacing?: Palette<Literal.Length> | undefined
254
279
  /** Colors available to text declarations. */
255
280
  readonly textColor?: Palette<Color> | undefined
281
+ /** Named sets of font family, size, weight, letter spacing, and line height. */
282
+ readonly typography?: Typography.Sets | undefined
256
283
  }
257
284
 
258
285
  function build(
@@ -282,9 +309,18 @@ function build(
282
309
  let hasQueries = !!baseQueries
283
310
  const active = new Set<object>()
284
311
 
285
- function visit(value: unknown, group: Token.Group, path: readonly string[]) {
312
+ function visit(
313
+ value: unknown,
314
+ group: Token.Group | 'typography',
315
+ path: readonly string[],
316
+ ) {
286
317
  const key = path.join('.')
287
318
  const scalar = typeof value === 'string' || typeof value === 'number'
319
+ if (group === 'typography' && scalar)
320
+ throw new InvalidError(
321
+ path,
322
+ 'Expected a typography set or a nested set group.',
323
+ )
288
324
  const entries = scalar ? undefined : record(value, path)
289
325
  const pair =
290
326
  ['color', 'backgroundColor', 'borderColor', 'textColor'].includes(
@@ -347,7 +383,18 @@ function build(
347
383
  )
348
384
  throw new InvalidError(next, 'Extensions cannot add token paths.')
349
385
 
350
- visit(child, group, next)
386
+ const property =
387
+ group === 'typography' &&
388
+ Typography.properties.includes(name as Typography.Property)
389
+ ? (name as Typography.Property)
390
+ : undefined
391
+ if (property && typeof child !== 'string' && typeof child !== 'number')
392
+ throw new InvalidError(
393
+ next,
394
+ 'Typography properties require scalar values.',
395
+ )
396
+
397
+ visit(child, property ?? group, next)
351
398
  }
352
399
 
353
400
  active.delete(value as object)
@@ -445,6 +492,7 @@ function build(
445
492
  'letterSpacing',
446
493
  'lineHeight',
447
494
  'textColor',
495
+ 'typography',
448
496
  ].includes(group)
449
497
  )
450
498
  throw new InvalidError([group], 'Unsupported token group.')
@@ -459,8 +507,17 @@ function build(
459
507
  if (!entries.length && !base)
460
508
  throw new InvalidError([group], 'Token palettes cannot be empty.')
461
509
 
462
- for (const [name, value] of entries)
463
- visit(value, group as Token.Group, [group, name])
510
+ for (const [name, value] of entries) {
511
+ if (
512
+ group === 'typography' &&
513
+ Typography.properties.includes(name as Typography.Property)
514
+ )
515
+ throw new InvalidError(
516
+ [group, name],
517
+ 'Typography properties require a named set.',
518
+ )
519
+ visit(value, group as Token.Group | 'typography', [group, name])
520
+ }
464
521
  }
465
522
 
466
523
  type Tree = { [key: string]: Tree | Token.Reference }
@@ -476,7 +533,9 @@ function build(
476
533
 
477
534
  tree[parts.at(-1)!] = Token.create({
478
535
  contract,
479
- group: parts[0] as Token.Group,
536
+ group: (parts[0] === 'typography'
537
+ ? parts.at(-1)
538
+ : parts[0]) as Token.Group,
480
539
  path,
481
540
  value,
482
541
  })
@@ -634,7 +693,11 @@ type ValidPalette<palette, group> = palette extends undefined
634
693
  : {
635
694
  [key in keyof palette]: key extends `${string}!${string}`
636
695
  ? never
637
- : ValidTree<palette[key], group>
696
+ : group extends 'typography'
697
+ ? key extends Typography.Property
698
+ ? never
699
+ : ValidTree<palette[key], group>
700
+ : ValidTree<palette[key], group>
638
701
  }
639
702
 
640
703
  type WeightDigits<
@@ -671,27 +734,29 @@ type Weight<value> = value extends number
671
734
  : value
672
735
 
673
736
  type ValidTree<tree, group> = tree extends string | number
674
- ? tree extends Scalar<group>
675
- ? group extends 'breakpoints' | 'containers'
676
- ? tree extends `-${string}`
677
- ? never
678
- : Literal.Checked<tree>
679
- : group extends keyof Literal.Properties
680
- ? Literal.Checked<tree> &
681
- Value.Checked<Record<group, tree>>[group] &
682
- (group extends 'fontWeight' ? Weight<tree> : unknown) &
683
- (tree extends string
684
- ? Lowercase<tree> extends
685
- | 'inherit'
686
- | 'initial'
687
- | 'unset'
688
- | 'revert'
689
- | 'revert-layer'
690
- ? never
691
- : unknown
692
- : unknown)
693
- : Literal.Checked<tree>
694
- : never
737
+ ? group extends 'typography'
738
+ ? never
739
+ : tree extends Scalar<group>
740
+ ? group extends 'breakpoints' | 'containers'
741
+ ? tree extends `-${string}`
742
+ ? never
743
+ : Literal.Checked<tree>
744
+ : group extends keyof Literal.Properties
745
+ ? Literal.Checked<tree> &
746
+ Value.Checked<Record<group, tree>>[group] &
747
+ (group extends 'fontWeight' ? Weight<tree> : unknown) &
748
+ (tree extends string
749
+ ? Lowercase<tree> extends
750
+ | 'inherit'
751
+ | 'initial'
752
+ | 'unset'
753
+ | 'revert'
754
+ | 'revert-layer'
755
+ ? never
756
+ : unknown
757
+ : unknown)
758
+ : Literal.Checked<tree>
759
+ : never
695
760
  : Extract<
696
761
  keyof tree,
697
762
  group extends 'color' | 'backgroundColor' | 'borderColor' | 'textColor'
@@ -701,7 +766,13 @@ type ValidTree<tree, group> = tree extends string | number
701
766
  ? {
702
767
  [key in keyof tree]: key extends `${string}!${string}`
703
768
  ? never
704
- : ValidTree<tree[key], group>
769
+ : group extends 'typography'
770
+ ? key extends Typography.Property
771
+ ? tree[key] extends string | number
772
+ ? ValidTree<tree[key], key>
773
+ : never
774
+ : ValidTree<tree[key], group>
775
+ : ValidTree<tree[key], group>
705
776
  }
706
777
  : group extends 'color' | 'backgroundColor' | 'borderColor' | 'textColor'
707
778
  ? {
@@ -31,6 +31,8 @@ import type * as Theme from '../Theme.js'
31
31
  import * as Themes from './internal/Themes.js'
32
32
  import * as ThemeValues from '../web/internal/Themes.js'
33
33
  import * as Token from '../internal/Token.js'
34
+ import * as Typography from '../internal/Typography.js'
35
+ import * as Value from '../internal/Value.js'
34
36
  import * as Variables from './internal/Variables.js'
35
37
  import * as Walker from 'oxc-walker'
36
38
 
@@ -97,6 +99,8 @@ export type Call = {
97
99
  readonly body?: Ast.ObjectExpression | undefined
98
100
  /** Alias targets retained for declaration source locations. */
99
101
  readonly shorthands?: Shorthands.Map | undefined
102
+ /** Emitted typography field counts keyed by authored property offsets. */
103
+ readonly typography?: ReadonlyMap<number, number> | undefined
100
104
  /** Native HTML attribute output selected by the bound configuration. */
101
105
  readonly output?: 'html' | undefined
102
106
  /** Typed runtime slots for callback definitions. */
@@ -640,6 +644,7 @@ export function extract(options: extract.Options): extract.ReturnType {
640
644
  }
641
645
  const name = `style-${namespace}-${call.start}`
642
646
  const locations: Style.SourceLocation[] = []
647
+ const typography = new Map<number, number>()
643
648
  const conditionKeys = new Map<string, Ast.Node>()
644
649
 
645
650
  function object(
@@ -1101,6 +1106,29 @@ export function extract(options: extract.Options): extract.ReturnType {
1101
1106
  values[key] = value(property.value, [name, ...prefix, key])
1102
1107
  }
1103
1108
 
1109
+ if (typeof values.typography === 'string') {
1110
+ const theme = themes?.styles.get(call.start)?.theme
1111
+ const mappings = theme?.[Token.definition].contract.shorthands
1112
+ const explicit = new Set(
1113
+ Object.keys(values).flatMap((key) => mappings?.[key] ?? [key]),
1114
+ )
1115
+ const parsed = Value.parse(values.typography, 'fontFamily')
1116
+ const count = Typography.fields(
1117
+ theme,
1118
+ parsed && !('invalid' in parsed) ? parsed.value : values.typography,
1119
+ ).filter((field) => !explicit.has(field)).length
1120
+ for (const property of properties) {
1121
+ if (property.type !== 'Property') continue
1122
+ const key =
1123
+ property.key.type === 'Identifier'
1124
+ ? property.key.name
1125
+ : property.key.type === 'Literal'
1126
+ ? property.key.value
1127
+ : undefined
1128
+ if (key === 'typography') typography.set(property.start, count)
1129
+ }
1130
+ }
1131
+
1104
1132
  return values
1105
1133
  }
1106
1134
 
@@ -1185,6 +1213,7 @@ export function extract(options: extract.Options): extract.ReturnType {
1185
1213
  ? { body: argument }
1186
1214
  : {}),
1187
1215
  ...(shorthands ? { shorthands } : {}),
1216
+ ...(typography.size ? { typography } : {}),
1188
1217
  ...(themes?.styles.get(call.start)?.output
1189
1218
  ? { output: 'html' as const }
1190
1219
  : {}),
@@ -995,6 +995,12 @@ export function compile(options: compile.Options): compile.ReturnType {
995
995
  ? String(property.key.value)
996
996
  : ''
997
997
 
998
+ if (key === 'typography' && call.typography?.has(property.start))
999
+ return Array.from(
1000
+ { length: call.typography.get(property.start)! },
1001
+ () => property,
1002
+ )
1003
+
998
1004
  const authoredLocations: readonly Ast.Node[] =
999
1005
  value.type === 'ArrayExpression'
1000
1006
  ? value.elements.filter(
@@ -24,7 +24,7 @@ export function read(
24
24
  if (
25
25
  ![
26
26
  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
27
- 22, 23,
27
+ 22, 23, 24,
28
28
  ].includes(data.version as number)
29
29
  )
30
30
  throw new Error('Unsupported Zyzz contract version.')
@@ -108,6 +108,13 @@ export function read(
108
108
  }
109
109
 
110
110
  const definition = Theme.define(record(entry.tokens) as Theme.Tokens)
111
+ if (
112
+ (data.version as number) < 24 &&
113
+ Object.hasOwn(definition.tokens, 'typography')
114
+ )
115
+ throw new Error(
116
+ 'Packed typography sets require contract version 24 or later.',
117
+ )
111
118
 
112
119
  themes[name] = Token.bind(definition, contract)
113
120
  types[name] = type(input(definition))
@@ -198,9 +205,9 @@ export function read(
198
205
  const name = string(entry.name)
199
206
  const reference = string(entry.reference)
200
207
  if (
201
- ![9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23].includes(
202
- data.version as number,
203
- ) ||
208
+ ![
209
+ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
210
+ ].includes(data.version as number) ||
204
211
  ![
205
212
  'cssFunction',
206
213
  'customMedia',
@@ -249,7 +256,7 @@ export function read(
249
256
  if (entry.kind === 'style-reference') {
250
257
  if (
251
258
  entry.style !== undefined &&
252
- ![16, 17, 18, 19, 20, 21, 22, 23].includes(data.version as number)
259
+ ![16, 17, 18, 19, 20, 21, 22, 23, 24].includes(data.version as number)
253
260
  )
254
261
  throw new Error(
255
262
  'Packed callable styles require contract version 16 or later.',
@@ -574,6 +581,13 @@ export function write(
574
581
  ]),
575
582
  ),
576
583
  version: (() => {
584
+ if (
585
+ Object.values(themes).some((theme) =>
586
+ Object.hasOwn(theme.tokens, 'typography'),
587
+ )
588
+ )
589
+ return 24
590
+
577
591
  function callable(link: Themes.Link): boolean {
578
592
  return !!link.style || Object.values(link.members ?? {}).some(callable)
579
593
  }