@toghen/figma 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +18 -2
- package/cli/bin.mjs +19 -0
- package/cli/bridge.ts +243 -0
- package/cli/check.ts +62 -0
- package/cli/figcraft.ts +764 -0
- package/cli/figcraftApply.ts +1067 -0
- package/cli/figcraftFile.ts +476 -0
- package/cli/figcraftLibrary.ts +147 -0
- package/cli/figcraftRecord.ts +121 -0
- package/cli/figcraftScene.ts +661 -0
- package/cli/main.ts +218 -0
- package/cli/members.ts +65 -0
- package/cli/run.ts +756 -0
- package/component-files.md +665 -0
- package/dist/code.js +24161 -0
- package/dist/manifest.json +23 -0
- package/dist/ui.html +6918 -0
- package/guide.md +985 -0
- package/main/code.ts +50 -0
- package/main/components/apply.ts +704 -0
- package/main/components/endOfRun.ts +168 -0
- package/main/components/errors.ts +17 -0
- package/main/components/fieldWriter.ts +497 -0
- package/main/components/file.ts +263 -0
- package/main/components/receipt.ts +228 -0
- package/main/components/resolve.ts +1193 -0
- package/main/components/run.ts +273 -0
- package/main/components/snapshot.ts +341 -0
- package/main/components/stamp.ts +312 -0
- package/main/components/update.ts +465 -0
- package/main/dispatcher.ts +33 -0
- package/main/engines/batchRunner.ts +200 -0
- package/main/engines/ioAdapter.ts +132 -0
- package/main/engines/operationExecutor.ts +512 -0
- package/main/engines/propertyMapper.ts +221 -0
- package/main/engines/queryEngine.ts +45 -0
- package/main/engines/styleSyncEngine.ts +444 -0
- package/main/engines/variableSyncEngine.ts +561 -0
- package/main/handlers/auditSelection.ts +60 -0
- package/main/handlers/boundLayers.ts +51 -0
- package/main/handlers/cancelOperation.ts +18 -0
- package/main/handlers/executeApplyBindings.ts +69 -0
- package/main/handlers/executeScaffold.ts +59 -0
- package/main/handlers/executeStyleSync.ts +121 -0
- package/main/handlers/executeVariableSync.ts +82 -0
- package/main/handlers/exportDocument.ts +114 -0
- package/main/handlers/getSelection.ts +43 -0
- package/main/handlers/index.ts +30 -0
- package/main/handlers/inspectCorrespondence.ts +172 -0
- package/main/handlers/loadConventions.ts +86 -0
- package/main/handlers/loadDocument.ts +253 -0
- package/main/handlers/lock.ts +270 -0
- package/main/handlers/planApply.ts +157 -0
- package/main/handlers/planApplyBindings.ts +79 -0
- package/main/handlers/planScaffold.ts +84 -0
- package/main/handlers/planStyleSync.ts +49 -0
- package/main/handlers/planVariableSync.ts +49 -0
- package/main/handlers/readSelectionBindings.ts +160 -0
- package/main/handlers/resizeUi.ts +105 -0
- package/main/handlers/runComponent.ts +65 -0
- package/main/handlers/templates.ts +462 -0
- package/main/handlers/tokenGraph.ts +122 -0
- package/main/runtimeState.ts +66 -0
- package/main/utils/abortController.ts +11 -0
- package/main/utils/boundVariables.ts +84 -0
- package/main/utils/buildPayloads.ts +85 -0
- package/main/utils/correspondence.ts +658 -0
- package/main/utils/designSource.ts +122 -0
- package/main/utils/figmaCollectionIndex.ts +120 -0
- package/main/utils/figmaStyleAdapter.ts +268 -0
- package/main/utils/figmaTokenAdapter.ts +255 -0
- package/main/utils/flattenDesignSystem.ts +38 -0
- package/main/utils/folds.ts +203 -0
- package/main/utils/fontStack.ts +57 -0
- package/main/utils/lists.ts +4 -0
- package/main/utils/lockStore.ts +72 -0
- package/main/utils/moves.ts +67 -0
- package/main/utils/pageRoots.ts +58 -0
- package/main/utils/planSymbols.ts +96 -0
- package/main/utils/purpose.ts +351 -0
- package/main/utils/recentStore.ts +166 -0
- package/main/utils/resolverModes.ts +271 -0
- package/main/utils/rootRecord.ts +137 -0
- package/main/utils/shardedData.ts +142 -0
- package/main/utils/standing.ts +106 -0
- package/main/utils/styleTarget.ts +51 -0
- package/main/utils/sum.ts +612 -0
- package/main/utils/symbolicIdRegistry.ts +26 -0
- package/main/utils/syncState.ts +133 -0
- package/main/utils/templateStore.ts +73 -0
- package/main/utils/tokenPath.ts +28 -0
- package/main/utils/tokenStamps.ts +91 -0
- package/manifest.json +15 -0
- package/package.json +64 -4
- package/reference.md +1609 -0
- package/shared/components/artboard.ts +126 -0
- package/shared/components/axes.ts +420 -0
- package/shared/components/bindings.ts +64 -0
- package/shared/components/blueprint.ts +248 -0
- package/shared/components/check.ts +705 -0
- package/shared/components/diff.ts +668 -0
- package/shared/components/fields.ts +404 -0
- package/shared/components/groups.ts +306 -0
- package/shared/components/index.ts +140 -0
- package/shared/components/members.ts +280 -0
- package/shared/components/model.ts +273 -0
- package/shared/components/order.ts +53 -0
- package/shared/components/read.ts +480 -0
- package/shared/components/stack.ts +124 -0
- package/shared/components/steps.ts +186 -0
- package/shared/components/validate.ts +565 -0
- package/shared/conventions.ts +28 -0
- package/shared/inputs.ts +40 -0
- package/shared/operations.ts +233 -0
- package/shared/pendingNames.ts +39 -0
- package/shared/protocol.ts +588 -0
- package/shared/tokenTypeMap.ts +21 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The field vocabulary: which fields each kind of layer takes, and what kind of
|
|
3
|
+
* value each field holds.
|
|
4
|
+
*
|
|
5
|
+
* Field names are the Plugin API's own, uppercase enums included, so nothing here
|
|
6
|
+
* is a rename. One table serves the reader (what may this node say), the
|
|
7
|
+
* validator (does a rule's patch fit the layer it addresses) and, later, the
|
|
8
|
+
* Figma-side engine (which fields a layer has set).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { LayerKind } from './model.js';
|
|
12
|
+
|
|
13
|
+
export const LAYER_KINDS: readonly LayerKind[] = ['FRAME', 'SLOT', 'TEXT', 'INSTANCE', 'LINE'];
|
|
14
|
+
|
|
15
|
+
export const PROPERTY_TYPES = ['VARIANT', 'BOOLEAN', 'TEXT', 'INSTANCE_SWAP', 'SLOT'] as const;
|
|
16
|
+
export type PropertyType = (typeof PROPERTY_TYPES)[number];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* What a field's value looks like in the file.
|
|
20
|
+
*
|
|
21
|
+
* - `boolean`, `string`, `component`: plain, or a `(property)` wire (see `wires`); `visible` and `characters` also take a
|
|
22
|
+
* token reference, whose `boolean` or `string` variable is bound.
|
|
23
|
+
* - `dimension`: a token or a `(literal)` number. A bare number is refused.
|
|
24
|
+
* - `size`: a dimension, or `#null` to clear it.
|
|
25
|
+
* - `token`: a token reference only: a style (`textStyle`, `effectStyle`), or, where the field's token binds, a variable
|
|
26
|
+
* (`fontFamily`, `fontStyle`, `fontWeight`).
|
|
27
|
+
* - `group`: a token reference naming a group; `tokens=`, which the reader turns into the
|
|
28
|
+
* bindings the group's keys imply (`bindings.ts`) and then drops.
|
|
29
|
+
* - `angle`: a plain number, degrees as Figma takes them; no token type holds one.
|
|
30
|
+
* - `enum`: one of `options`.
|
|
31
|
+
* - `paints`, `tracks`, `placement`, `modes`, `set`: structured, written as child nodes.
|
|
32
|
+
* - `effects`, `grids`: structured, written as child nodes: a list of shadow and blur tokens, a list of layout grids.
|
|
33
|
+
* - `dashes`: a child node whose arguments are one token reference (a `strokeStyle`), or `(literal)` lengths, or none.
|
|
34
|
+
* - `constraints`: a child node, `constraints horizontal=… vertical=…`, both given.
|
|
35
|
+
*/
|
|
36
|
+
export type ValueKind =
|
|
37
|
+
| 'boolean'
|
|
38
|
+
| 'string'
|
|
39
|
+
| 'component'
|
|
40
|
+
| 'dimension'
|
|
41
|
+
| 'size'
|
|
42
|
+
| 'angle'
|
|
43
|
+
| 'token'
|
|
44
|
+
| 'enum'
|
|
45
|
+
| 'group'
|
|
46
|
+
| 'paints'
|
|
47
|
+
| 'tracks'
|
|
48
|
+
| 'placement'
|
|
49
|
+
| 'modes'
|
|
50
|
+
| 'set'
|
|
51
|
+
| 'effects'
|
|
52
|
+
| 'grids'
|
|
53
|
+
| 'dashes'
|
|
54
|
+
| 'constraints';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* How a variable is bound to a field: the roster's Bind column (`.reference/figma/property_roster.md`).
|
|
58
|
+
*
|
|
59
|
+
* - `call`: the Plugin API call that binds it. `field` is `setBoundVariable`; `paint` is `setBoundVariableForPaint`;
|
|
60
|
+
* `effect`, `grid`, `text` and `property` are `setBoundVariableForEffect`, `setBoundVariableForLayoutGrid`, a
|
|
61
|
+
* whole-text `setRangeBoundVariable` and `setProperties` with an alias. `text` binds the text fields; the others no
|
|
62
|
+
* field uses yet.
|
|
63
|
+
* - `field`: the name that call takes.
|
|
64
|
+
* - `scope`: the scopes whose pickers offer a variable for it, or, where a text differs, one list for a text and
|
|
65
|
+
* one for every other layer (a fill).
|
|
66
|
+
* - `parts`: the fields Figma reports the binding on instead of this one, on a layer that has them.
|
|
67
|
+
*/
|
|
68
|
+
export interface TokenBinding {
|
|
69
|
+
call: 'field' | 'paint' | 'effect' | 'grid' | 'text' | 'property';
|
|
70
|
+
field: string;
|
|
71
|
+
variable: VariableResolvedDataType;
|
|
72
|
+
scope?: readonly VariableScope[] | { readonly text: readonly VariableScope[]; readonly other: readonly VariableScope[] };
|
|
73
|
+
parts?: readonly string[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* How a token reaches a field: the roster's Bind and Token columns. `types` are the token types it carries. With
|
|
78
|
+
* `bind`, the token's variable is bound. Without it, a field whose value is `token` names a style (`textStyle`,
|
|
79
|
+
* `effectStyle`), and any other is written as the token's resolved value (`x`, `y`, `cornerSmoothing`, `dashPattern`).
|
|
80
|
+
*/
|
|
81
|
+
export interface FieldToken {
|
|
82
|
+
types: readonly string[];
|
|
83
|
+
bind?: TokenBinding;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface FieldSpec {
|
|
87
|
+
nodes: readonly LayerKind[];
|
|
88
|
+
value: ValueKind;
|
|
89
|
+
options?: readonly string[];
|
|
90
|
+
/** The property type a `(property)` value must name, when the field can be wired. */
|
|
91
|
+
wires?: PropertyType;
|
|
92
|
+
/** How a token reaches the field; absent, the field takes no token. */
|
|
93
|
+
token?: FieldToken;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const ALL = LAYER_KINDS;
|
|
97
|
+
const CONTAINER: readonly LayerKind[] = ['FRAME', 'SLOT'];
|
|
98
|
+
/** The kinds that carry a fill, and name a group: everything but an instance. */
|
|
99
|
+
const STYLED: readonly LayerKind[] = ['FRAME', 'SLOT', 'TEXT'];
|
|
100
|
+
/** A line has a length (`width`) and no height. */
|
|
101
|
+
const HEIGHT: readonly LayerKind[] = ['FRAME', 'SLOT', 'TEXT', 'INSTANCE'];
|
|
102
|
+
/** The kinds that take a stroke. */
|
|
103
|
+
const STROKED: readonly LayerKind[] = ['FRAME', 'SLOT', 'LINE'];
|
|
104
|
+
const SIZING = ['FIXED', 'HUG', 'FILL'] as const;
|
|
105
|
+
/** The frame kinds, which take a layout grid. */
|
|
106
|
+
const FRAMES: readonly LayerKind[] = ['FRAME', 'SLOT', 'INSTANCE'];
|
|
107
|
+
/** How a grid child sits in its cell. */
|
|
108
|
+
const IN_CELL = ['MIN', 'CENTER', 'MAX', 'AUTO'] as const;
|
|
109
|
+
/** Figma's `BlendMode`. */
|
|
110
|
+
const BLEND_MODES = [
|
|
111
|
+
'PASS_THROUGH',
|
|
112
|
+
'NORMAL',
|
|
113
|
+
'DARKEN',
|
|
114
|
+
'MULTIPLY',
|
|
115
|
+
'LINEAR_BURN',
|
|
116
|
+
'COLOR_BURN',
|
|
117
|
+
'LIGHTEN',
|
|
118
|
+
'SCREEN',
|
|
119
|
+
'LINEAR_DODGE',
|
|
120
|
+
'COLOR_DODGE',
|
|
121
|
+
'OVERLAY',
|
|
122
|
+
'SOFT_LIGHT',
|
|
123
|
+
'HARD_LIGHT',
|
|
124
|
+
'DIFFERENCE',
|
|
125
|
+
'EXCLUSION',
|
|
126
|
+
'HUE',
|
|
127
|
+
'SATURATION',
|
|
128
|
+
'COLOR',
|
|
129
|
+
'LUMINOSITY',
|
|
130
|
+
] as const;
|
|
131
|
+
/** Figma's `StrokeCap`. */
|
|
132
|
+
const STROKE_CAPS = [
|
|
133
|
+
'NONE',
|
|
134
|
+
'ROUND',
|
|
135
|
+
'SQUARE',
|
|
136
|
+
'ARROW_LINES',
|
|
137
|
+
'ARROW_EQUILATERAL',
|
|
138
|
+
'DIAMOND_FILLED',
|
|
139
|
+
'TRIANGLE_FILLED',
|
|
140
|
+
'CIRCLE_FILLED',
|
|
141
|
+
] as const;
|
|
142
|
+
|
|
143
|
+
/** The corners `cornerRadius` sets, and the sides `strokeWeight` sets: Figma reports either's binding on these. */
|
|
144
|
+
const CORNERS = ['topLeftRadius', 'topRightRadius', 'bottomRightRadius', 'bottomLeftRadius'];
|
|
145
|
+
const SIDES = ['strokeTopWeight', 'strokeRightWeight', 'strokeBottomWeight', 'strokeLeftWeight'];
|
|
146
|
+
|
|
147
|
+
/** A field bound with `setBoundVariable` to a FLOAT variable under its own name. */
|
|
148
|
+
const float = (field: string, scope: VariableScope, types: readonly string[] = ['dimension'], parts?: readonly string[]): FieldToken => ({
|
|
149
|
+
types,
|
|
150
|
+
bind: { call: 'field', field, variable: 'FLOAT', scope: [scope], ...(parts ? { parts } : {}) },
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const dimensions = (names: string[], scope: VariableScope): Record<string, FieldSpec> =>
|
|
154
|
+
names.reduce<Record<string, FieldSpec>>((table, name) => {
|
|
155
|
+
table[name] = { nodes: CONTAINER, value: 'dimension', token: float(name, scope) };
|
|
156
|
+
return table;
|
|
157
|
+
}, {});
|
|
158
|
+
|
|
159
|
+
const sizes = (names: string[], nodes: readonly LayerKind[]): Record<string, FieldSpec> =>
|
|
160
|
+
names.reduce<Record<string, FieldSpec>>((table, name) => {
|
|
161
|
+
table[name] = { nodes, value: 'size', token: float(name, 'WIDTH_HEIGHT') };
|
|
162
|
+
return table;
|
|
163
|
+
}, {});
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Text fields: the roster's text rows (`property_roster.md`, *Text*)
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
/** A text field bound on the whole text, `setRangeBoundVariable(0, length, …)`: Figma reports it as an array of aliases. */
|
|
170
|
+
const wholeText = (field: string, variable: VariableResolvedDataType, scope: VariableScope, types: readonly string[]): FieldToken => ({
|
|
171
|
+
types,
|
|
172
|
+
bind: { call: 'text', field, variable, scope: [scope] },
|
|
173
|
+
});
|
|
174
|
+
const TEXT_ONLY: readonly LayerKind[] = ['TEXT'];
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The fields of a text beyond its characters, style and sizing. `fontFamily`, `fontStyle` and `fontWeight` take a token
|
|
178
|
+
* only: a font's halves are bound, and `fontWeight` cannot be assigned (measured §14). A line height or letter spacing
|
|
179
|
+
* binds in pixels only (measured §14), so one in another unit is written as its value. `listSpacing` and `maxLines`
|
|
180
|
+
* cannot be bound.
|
|
181
|
+
*/
|
|
182
|
+
export const TEXT_FIELDS: Readonly<Record<string, FieldSpec>> = {
|
|
183
|
+
fontFamily: { nodes: TEXT_ONLY, value: 'token', token: wholeText('fontFamily', 'STRING', 'FONT_FAMILY', ['fontFamily', 'string']) },
|
|
184
|
+
fontStyle: { nodes: TEXT_ONLY, value: 'token', token: wholeText('fontStyle', 'STRING', 'FONT_STYLE', ['string']) },
|
|
185
|
+
fontWeight: { nodes: TEXT_ONLY, value: 'token', token: wholeText('fontWeight', 'FLOAT', 'FONT_WEIGHT', ['fontWeight', 'number']) },
|
|
186
|
+
fontSize: { nodes: TEXT_ONLY, value: 'dimension', token: wholeText('fontSize', 'FLOAT', 'FONT_SIZE', ['dimension']) },
|
|
187
|
+
lineHeight: { nodes: TEXT_ONLY, value: 'dimension', token: wholeText('lineHeight', 'FLOAT', 'LINE_HEIGHT', ['dimension', 'number']) },
|
|
188
|
+
letterSpacing: {
|
|
189
|
+
nodes: TEXT_ONLY,
|
|
190
|
+
value: 'dimension',
|
|
191
|
+
token: wholeText('letterSpacing', 'FLOAT', 'LETTER_SPACING', ['dimension', 'letterSpacing']),
|
|
192
|
+
},
|
|
193
|
+
paragraphSpacing: {
|
|
194
|
+
nodes: TEXT_ONLY,
|
|
195
|
+
value: 'dimension',
|
|
196
|
+
token: wholeText('paragraphSpacing', 'FLOAT', 'PARAGRAPH_SPACING', ['dimension']),
|
|
197
|
+
},
|
|
198
|
+
paragraphIndent: {
|
|
199
|
+
nodes: TEXT_ONLY,
|
|
200
|
+
value: 'dimension',
|
|
201
|
+
token: wholeText('paragraphIndent', 'FLOAT', 'PARAGRAPH_INDENT', ['dimension']),
|
|
202
|
+
},
|
|
203
|
+
listSpacing: { nodes: TEXT_ONLY, value: 'dimension', token: { types: ['dimension'] } },
|
|
204
|
+
maxLines: { nodes: TEXT_ONLY, value: 'dimension', token: { types: ['number'] } },
|
|
205
|
+
textAlignHorizontal: { nodes: TEXT_ONLY, value: 'enum', options: ['LEFT', 'CENTER', 'RIGHT', 'JUSTIFIED'] },
|
|
206
|
+
textAlignVertical: { nodes: TEXT_ONLY, value: 'enum', options: ['TOP', 'CENTER', 'BOTTOM'] },
|
|
207
|
+
textCase: { nodes: TEXT_ONLY, value: 'enum', options: ['ORIGINAL', 'UPPER', 'LOWER', 'TITLE', 'SMALL_CAPS', 'SMALL_CAPS_FORCED'] },
|
|
208
|
+
textDecoration: { nodes: TEXT_ONLY, value: 'enum', options: ['NONE', 'UNDERLINE', 'STRIKETHROUGH'] },
|
|
209
|
+
textTruncation: { nodes: TEXT_ONLY, value: 'enum', options: ['DISABLED', 'ENDING'] },
|
|
210
|
+
leadingTrim: { nodes: TEXT_ONLY, value: 'enum', options: ['CAP_HEIGHT', 'NONE'] },
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/** The text fields a text style also sets: one beside `textStyle` would be a second source for one value, and is refused. */
|
|
214
|
+
export const STYLE_TEXT_FIELDS: readonly string[] = [
|
|
215
|
+
'fontFamily',
|
|
216
|
+
'fontStyle',
|
|
217
|
+
'fontWeight',
|
|
218
|
+
'fontSize',
|
|
219
|
+
'lineHeight',
|
|
220
|
+
'letterSpacing',
|
|
221
|
+
'paragraphSpacing',
|
|
222
|
+
'paragraphIndent',
|
|
223
|
+
'listSpacing',
|
|
224
|
+
'textCase',
|
|
225
|
+
'textDecoration',
|
|
226
|
+
'leadingTrim',
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
/** A field that names a style: a `token` field whose token is not bound. */
|
|
230
|
+
export const namesStyle = (spec: FieldSpec | undefined): boolean => spec?.value === 'token' && !spec.token?.bind;
|
|
231
|
+
|
|
232
|
+
/** A line height and a letter spacing bind in pixels only: whether one is bound or written as its value depends on its token. */
|
|
233
|
+
export const PIXELS_ONLY_FIELDS: readonly string[] = ['lineHeight', 'letterSpacing'];
|
|
234
|
+
|
|
235
|
+
export const FIELDS: Readonly<Record<string, FieldSpec>> = {
|
|
236
|
+
visible: {
|
|
237
|
+
nodes: ALL,
|
|
238
|
+
value: 'boolean',
|
|
239
|
+
wires: 'BOOLEAN',
|
|
240
|
+
token: { types: ['boolean'], bind: { call: 'field', field: 'visible', variable: 'BOOLEAN' } },
|
|
241
|
+
},
|
|
242
|
+
opacity: { nodes: ALL, value: 'dimension', token: float('opacity', 'OPACITY', ['number']) },
|
|
243
|
+
modes: { nodes: ALL, value: 'modes' },
|
|
244
|
+
grid: { nodes: ALL, value: 'placement' },
|
|
245
|
+
layoutSizingHorizontal: { nodes: ALL, value: 'enum', options: SIZING },
|
|
246
|
+
layoutSizingVertical: { nodes: ALL, value: 'enum', options: SIZING },
|
|
247
|
+
...sizes(['width', 'minWidth', 'maxWidth'], ALL),
|
|
248
|
+
...sizes(['height', 'minHeight', 'maxHeight'], HEIGHT),
|
|
249
|
+
rotation: { nodes: ALL, value: 'angle' },
|
|
250
|
+
|
|
251
|
+
// `SLOT` may not be a grid; that is checked where the layer's kind is known,
|
|
252
|
+
// so a patch that makes a slot a grid is caught in the member it produces.
|
|
253
|
+
layoutMode: { nodes: CONTAINER, value: 'enum', options: ['NONE', 'HORIZONTAL', 'VERTICAL', 'GRID'] },
|
|
254
|
+
primaryAxisAlignItems: {
|
|
255
|
+
nodes: CONTAINER,
|
|
256
|
+
value: 'enum',
|
|
257
|
+
options: ['MIN', 'MAX', 'CENTER', 'SPACE_BETWEEN', 'SPACE_EVENLY', 'SPACE_AROUND'],
|
|
258
|
+
},
|
|
259
|
+
counterAxisAlignItems: { nodes: CONTAINER, value: 'enum', options: ['MIN', 'MAX', 'CENTER', 'BASELINE'] },
|
|
260
|
+
...dimensions(['itemSpacing', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'gridRowGap', 'gridColumnGap'], 'GAP'),
|
|
261
|
+
...dimensions(SIDES, 'STROKE_FLOAT'),
|
|
262
|
+
cornerRadius: { nodes: CONTAINER, value: 'dimension', token: float('cornerRadius', 'CORNER_RADIUS', ['dimension'], CORNERS) },
|
|
263
|
+
...dimensions(CORNERS, 'CORNER_RADIUS'),
|
|
264
|
+
strokeWeight: { nodes: STROKED, value: 'dimension', token: float('strokeWeight', 'STROKE_FLOAT', ['dimension'], SIDES) },
|
|
265
|
+
gridRowSizes: { nodes: CONTAINER, value: 'tracks' },
|
|
266
|
+
gridColumnSizes: { nodes: CONTAINER, value: 'tracks' },
|
|
267
|
+
clipsContent: { nodes: CONTAINER, value: 'boolean' },
|
|
268
|
+
fills: {
|
|
269
|
+
nodes: STYLED,
|
|
270
|
+
value: 'paints',
|
|
271
|
+
token: {
|
|
272
|
+
types: ['color', 'gradient'],
|
|
273
|
+
bind: { call: 'paint', field: 'color', variable: 'COLOR', scope: { text: ['TEXT_FILL'], other: ['FRAME_FILL', 'SHAPE_FILL'] } },
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
strokes: {
|
|
277
|
+
nodes: STROKED,
|
|
278
|
+
value: 'paints',
|
|
279
|
+
token: { types: ['color', 'gradient'], bind: { call: 'paint', field: 'color', variable: 'COLOR', scope: ['STROKE_COLOR'] } },
|
|
280
|
+
},
|
|
281
|
+
effectStyle: { nodes: CONTAINER, value: 'token', token: { types: ['shadow'] } },
|
|
282
|
+
|
|
283
|
+
characters: {
|
|
284
|
+
nodes: ['TEXT'],
|
|
285
|
+
value: 'string',
|
|
286
|
+
wires: 'TEXT',
|
|
287
|
+
token: { types: ['string'], bind: { call: 'field', field: 'characters', variable: 'STRING', scope: ['TEXT_CONTENT'] } },
|
|
288
|
+
},
|
|
289
|
+
textStyle: { nodes: ['TEXT'], value: 'token', token: { types: ['typography'] } },
|
|
290
|
+
textAutoResize: { nodes: ['TEXT'], value: 'enum', options: ['NONE', 'HEIGHT', 'WIDTH_AND_HEIGHT'] },
|
|
291
|
+
...TEXT_FIELDS,
|
|
292
|
+
|
|
293
|
+
component: { nodes: ['INSTANCE'], value: 'component', wires: 'INSTANCE_SWAP' },
|
|
294
|
+
// A component file only: the instance's own properties are listed on the component's instances.
|
|
295
|
+
isExposedInstance: { nodes: ['INSTANCE'], value: 'boolean' },
|
|
296
|
+
|
|
297
|
+
tokens: { nodes: STYLED, value: 'group' },
|
|
298
|
+
|
|
299
|
+
// The values of an instance's properties, by display name.
|
|
300
|
+
set: { nodes: ['INSTANCE'], value: 'set' },
|
|
301
|
+
|
|
302
|
+
// Wrap, a grid child's alignment, position, appearance and strokes: the roster's sections 3 to 7. Where the format
|
|
303
|
+
// narrows the roster: the wrap fields and `cornerSmoothing` are on FRAME and SLOT, as the other auto-layout and corner
|
|
304
|
+
// fields are; the stroke fields are on the kinds that take `strokes`; `strokeCap` is on a LINE only, since a frame's
|
|
305
|
+
// outline has no open end to cap.
|
|
306
|
+
layoutWrap: { nodes: CONTAINER, value: 'enum', options: ['NO_WRAP', 'WRAP'] },
|
|
307
|
+
counterAxisAlignContent: { nodes: CONTAINER, value: 'enum', options: ['AUTO', 'SPACE_BETWEEN'] },
|
|
308
|
+
counterAxisSpacing: { nodes: CONTAINER, value: 'dimension', token: float('counterAxisSpacing', 'GAP') },
|
|
309
|
+
gridChildHorizontalAlign: { nodes: ALL, value: 'enum', options: IN_CELL },
|
|
310
|
+
gridChildVerticalAlign: { nodes: ALL, value: 'enum', options: IN_CELL },
|
|
311
|
+
layoutPositioning: { nodes: ALL, value: 'enum', options: ['AUTO', 'ABSOLUTE'] },
|
|
312
|
+
// `options` are what each axis takes: Figma's `ConstraintType`.
|
|
313
|
+
constraints: { nodes: ALL, value: 'constraints', options: ['MIN', 'CENTER', 'MAX', 'STRETCH', 'SCALE'] },
|
|
314
|
+
// Measured: not bindable (`Unknown field`), so a token is written as its value.
|
|
315
|
+
x: { nodes: ALL, value: 'dimension', token: { types: ['dimension'] } },
|
|
316
|
+
y: { nodes: ALL, value: 'dimension', token: { types: ['dimension'] } },
|
|
317
|
+
blendMode: { nodes: ALL, value: 'enum', options: BLEND_MODES },
|
|
318
|
+
cornerSmoothing: { nodes: CONTAINER, value: 'dimension', token: { types: ['number'] } },
|
|
319
|
+
strokeAlign: { nodes: STROKED, value: 'enum', options: ['INSIDE', 'OUTSIDE', 'CENTER'] },
|
|
320
|
+
strokeCap: { nodes: ['LINE'], value: 'enum', options: STROKE_CAPS },
|
|
321
|
+
strokeJoin: { nodes: STROKED, value: 'enum', options: ['MITER', 'BEVEL', 'ROUND'] },
|
|
322
|
+
dashPattern: { nodes: STROKED, value: 'dashes', token: { types: ['strokeStyle'] } },
|
|
323
|
+
|
|
324
|
+
// Effects and layout grids (unit 4). Each binds by parts: a shadow's five fields and a blur's radius
|
|
325
|
+
// (`SHADOW_FIELDS`), a grid's fields as its alignment allows (`GRID_FIELDS`). `field` and `variable` name the case
|
|
326
|
+
// the parts share; a shadow's colour is a COLOR, scope `EFFECT_COLOR`. No scope names a layout grid.
|
|
327
|
+
effects: {
|
|
328
|
+
nodes: ALL,
|
|
329
|
+
value: 'effects',
|
|
330
|
+
token: { types: ['shadow', 'dimension'], bind: { call: 'effect', field: 'radius', variable: 'FLOAT', scope: ['EFFECT_FLOAT'] } },
|
|
331
|
+
},
|
|
332
|
+
layoutGrids: {
|
|
333
|
+
nodes: FRAMES,
|
|
334
|
+
value: 'grids',
|
|
335
|
+
token: { types: ['number', 'dimension', 'color'], bind: { call: 'grid', field: 'count', variable: 'FLOAT' } },
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* The fields Apply can put one token of `type` on, in the table's order: those whose token column takes the type, on a
|
|
341
|
+
* frame (Apply reads an instance and a component definition as one). A style field, effects and layout grids are left
|
|
342
|
+
* out: a component file writes those, part by part. The Apply pane offers these when its query names one token.
|
|
343
|
+
*/
|
|
344
|
+
export const applyFieldsFor = (type: string): string[] =>
|
|
345
|
+
Object.keys(FIELDS).filter((name) => {
|
|
346
|
+
const spec = FIELDS[name];
|
|
347
|
+
return (
|
|
348
|
+
!!spec.token?.types.includes(type) &&
|
|
349
|
+
spec.nodes.includes('FRAME') &&
|
|
350
|
+
!namesStyle(spec) &&
|
|
351
|
+
spec.value !== 'effects' &&
|
|
352
|
+
spec.value !== 'grids'
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
/** Fields written as a child node of the layer rather than as a KDL property. */
|
|
357
|
+
export const isStructured = (spec: FieldSpec): boolean =>
|
|
358
|
+
spec.value === 'paints' ||
|
|
359
|
+
spec.value === 'tracks' ||
|
|
360
|
+
spec.value === 'placement' ||
|
|
361
|
+
spec.value === 'modes' ||
|
|
362
|
+
spec.value === 'set' ||
|
|
363
|
+
spec.value === 'dashes' ||
|
|
364
|
+
spec.value === 'constraints' ||
|
|
365
|
+
spec.value === 'effects' ||
|
|
366
|
+
spec.value === 'grids';
|
|
367
|
+
|
|
368
|
+
/** The fields that only make sense in a grid, and those that only make sense outside one. */
|
|
369
|
+
export const GRID_ONLY_FIELDS = ['gridRowSizes', 'gridColumnSizes', 'gridRowGap', 'gridColumnGap'] as const;
|
|
370
|
+
export const NOT_IN_GRID_FIELDS = ['primaryAxisAlignItems', 'counterAxisAlignItems', 'itemSpacing'] as const;
|
|
371
|
+
/** The fields of a wrapping flow: only on a frame whose layoutMode is HORIZONTAL or VERTICAL (measured: WRAP throws on any other). */
|
|
372
|
+
export const WRAP_FIELDS = ['layoutWrap', 'counterAxisAlignContent', 'counterAxisSpacing'] as const;
|
|
373
|
+
/** The fields that align a child in its cell: only on a child of a grid. */
|
|
374
|
+
export const GRID_CHILD_FIELDS = ['gridChildHorizontalAlign', 'gridChildVerticalAlign'] as const;
|
|
375
|
+
|
|
376
|
+
/** The fields written as a child node: the vocabulary tells kdlet, which then hands them over as a list or a structure. */
|
|
377
|
+
export const STRUCTURED_FIELDS: readonly string[] = Object.keys(FIELDS).filter((name) => isStructured(FIELDS[name]));
|
|
378
|
+
|
|
379
|
+
// ---------------------------------------------------------------------------
|
|
380
|
+
// Effects and layout grids (unit 4)
|
|
381
|
+
// ---------------------------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* A shadow token's members, by the effect field each is bound to with `setBoundVariableForEffect`: all five bind on a
|
|
385
|
+
* drop and an inner shadow (measured §14). A layer blur binds `radius` only.
|
|
386
|
+
*/
|
|
387
|
+
export const SHADOW_FIELDS = { color: 'color', offsetX: 'offsetX', offsetY: 'offsetY', blur: 'radius', spread: 'spread' } as const;
|
|
388
|
+
|
|
389
|
+
/** The patterns of a layout grid, as the Plugin API names them: columns, rows, and a square grid. */
|
|
390
|
+
export const GRID_PATTERNS = ['COLUMNS', 'ROWS', 'GRID'] as const;
|
|
391
|
+
export const GRID_ALIGNMENTS = ['MIN', 'MAX', 'STRETCH', 'CENTER'] as const;
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* The fields a layout grid takes, by its alignment, or `GRID` for a square grid: those `setBoundVariableForLayoutGrid`
|
|
395
|
+
* binds (measured §14: `STRETCH` refuses `sectionSize`, `CENTER` refuses `offset`, `MIN` binds `sectionSize`, a square
|
|
396
|
+
* grid binds `sectionSize` only). `MAX` is taken to be `MIN`'s mirror. // unmeasured: MAX
|
|
397
|
+
*/
|
|
398
|
+
export const GRID_FIELDS: Readonly<Record<string, readonly string[]>> = {
|
|
399
|
+
STRETCH: ['count', 'gutterSize', 'offset'],
|
|
400
|
+
CENTER: ['count', 'gutterSize', 'sectionSize'],
|
|
401
|
+
MIN: ['count', 'gutterSize', 'offset', 'sectionSize'],
|
|
402
|
+
MAX: ['count', 'gutterSize', 'offset', 'sectionSize'],
|
|
403
|
+
GRID: ['sectionSize'],
|
|
404
|
+
};
|