@wix/zero-config-implementation 1.62.0 → 1.63.0
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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"registry": "https://registry.npmjs.org/",
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.
|
|
7
|
+
"version": "1.63.0",
|
|
8
8
|
"description": "Core library for extracting component manifests from JS and CSS files",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "dist/index.js",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
]
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
|
-
"falconPackageHash": "
|
|
83
|
+
"falconPackageHash": "fc05eca6895716f63fc1c075f50bf661142e5d08ab108de4d195403e"
|
|
84
84
|
}
|
|
@@ -200,4 +200,162 @@ describe('resolveLonghand', () => {
|
|
|
200
200
|
expect(resolveLonghand('background', values)).toBe('#001')
|
|
201
201
|
})
|
|
202
202
|
})
|
|
203
|
+
|
|
204
|
+
describe('font composition from longhands', () => {
|
|
205
|
+
it('composes size and family alone', () => {
|
|
206
|
+
const values = new Map([
|
|
207
|
+
['fontSize', '14px'],
|
|
208
|
+
['fontFamily', 'Arial'],
|
|
209
|
+
])
|
|
210
|
+
expect(resolveLonghand('font', values)).toBe('14px Arial')
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('includes weight before size', () => {
|
|
214
|
+
const values = new Map([
|
|
215
|
+
['fontSize', '16px'],
|
|
216
|
+
['fontFamily', "'Helvetica Neue'"],
|
|
217
|
+
['fontWeight', '600'],
|
|
218
|
+
])
|
|
219
|
+
expect(resolveLonghand('font', values)).toBe("600 16px 'Helvetica Neue'")
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('joins size and line-height with /', () => {
|
|
223
|
+
const values = new Map([
|
|
224
|
+
['fontSize', '14px'],
|
|
225
|
+
['fontFamily', 'sans-serif'],
|
|
226
|
+
['lineHeight', '1.5'],
|
|
227
|
+
])
|
|
228
|
+
expect(resolveLonghand('font', values)).toBe('14px/1.5 sans-serif')
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('combines weight, size/line-height, family in spec order', () => {
|
|
232
|
+
const values = new Map([
|
|
233
|
+
['fontStyle', 'italic'],
|
|
234
|
+
['fontWeight', '700'],
|
|
235
|
+
['fontSize', '20px'],
|
|
236
|
+
['lineHeight', '1.3'],
|
|
237
|
+
['fontFamily', 'serif'],
|
|
238
|
+
])
|
|
239
|
+
expect(resolveLonghand('font', values)).toBe('italic 700 20px/1.3 serif')
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it("omits modifiers with literal value 'normal'", () => {
|
|
243
|
+
const values = new Map([
|
|
244
|
+
['fontStyle', 'normal'],
|
|
245
|
+
['fontWeight', 'normal'],
|
|
246
|
+
['fontSize', '16px'],
|
|
247
|
+
['lineHeight', 'normal'],
|
|
248
|
+
['fontFamily', 'Arial'],
|
|
249
|
+
])
|
|
250
|
+
expect(resolveLonghand('font', values)).toBe('16px Arial')
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('returns undefined when fontSize is missing (strict)', () => {
|
|
254
|
+
const values = new Map([['fontFamily', 'Arial']])
|
|
255
|
+
expect(resolveLonghand('font', values)).toBeUndefined()
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('returns undefined when fontFamily is missing (strict)', () => {
|
|
259
|
+
const values = new Map([['fontSize', '14px']])
|
|
260
|
+
expect(resolveLonghand('font', values)).toBeUndefined()
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('returns undefined when only optional modifiers are present', () => {
|
|
264
|
+
const values = new Map([
|
|
265
|
+
['fontWeight', '700'],
|
|
266
|
+
['fontStyle', 'italic'],
|
|
267
|
+
])
|
|
268
|
+
expect(resolveLonghand('font', values)).toBeUndefined()
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('prefers literal font shorthand over longhand composition', () => {
|
|
272
|
+
const values = new Map([
|
|
273
|
+
['font', '400 14px Arial'],
|
|
274
|
+
['fontSize', '99px'],
|
|
275
|
+
['fontFamily', 'WrongFont'],
|
|
276
|
+
])
|
|
277
|
+
expect(resolveLonghand('font', values)).toBe('400 14px Arial')
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
describe('gap composition from row-gap + column-gap', () => {
|
|
282
|
+
it('composes two distinct axis values in row/column order', () => {
|
|
283
|
+
const values = new Map([
|
|
284
|
+
['rowGap', '8px'],
|
|
285
|
+
['columnGap', '10px'],
|
|
286
|
+
])
|
|
287
|
+
expect(resolveLonghand('gap', values)).toBe('8px 10px')
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('collapses to a single value when both axes are equal', () => {
|
|
291
|
+
const values = new Map([
|
|
292
|
+
['rowGap', '4px'],
|
|
293
|
+
['columnGap', '4px'],
|
|
294
|
+
])
|
|
295
|
+
expect(resolveLonghand('gap', values)).toBe('4px')
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('returns undefined when rowGap is missing (strict)', () => {
|
|
299
|
+
const values = new Map([['columnGap', '10px']])
|
|
300
|
+
expect(resolveLonghand('gap', values)).toBeUndefined()
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('returns undefined when columnGap is missing (strict)', () => {
|
|
304
|
+
const values = new Map([['rowGap', '8px']])
|
|
305
|
+
expect(resolveLonghand('gap', values)).toBeUndefined()
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('prefers literal gap shorthand over longhand composition', () => {
|
|
309
|
+
const values = new Map([
|
|
310
|
+
['gap', '12px'],
|
|
311
|
+
['rowGap', '99px'],
|
|
312
|
+
['columnGap', '99px'],
|
|
313
|
+
])
|
|
314
|
+
expect(resolveLonghand('gap', values)).toBe('12px')
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
describe('textDecorationLine extraction from text-decoration shorthand', () => {
|
|
319
|
+
it('extracts the lone line keyword', () => {
|
|
320
|
+
expect(resolveLonghand('textDecorationLine', new Map([['textDecoration', 'underline']]))).toBe('underline')
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('extracts the line keyword and drops style + color tokens', () => {
|
|
324
|
+
expect(resolveLonghand('textDecorationLine', new Map([['textDecoration', 'underline wavy red']]))).toBe(
|
|
325
|
+
'underline',
|
|
326
|
+
)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it('ignores token order — line keyword can appear anywhere', () => {
|
|
330
|
+
expect(resolveLonghand('textDecorationLine', new Map([['textDecoration', 'red wavy underline']]))).toBe(
|
|
331
|
+
'underline',
|
|
332
|
+
)
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('keeps multiple line keywords joined (e.g. underline + overline)', () => {
|
|
336
|
+
expect(resolveLonghand('textDecorationLine', new Map([['textDecoration', 'underline overline']]))).toBe(
|
|
337
|
+
'underline overline',
|
|
338
|
+
)
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
it('extracts `none`', () => {
|
|
342
|
+
expect(resolveLonghand('textDecorationLine', new Map([['textDecoration', 'none']]))).toBe('none')
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('returns undefined when shorthand has no line keyword (just color/style)', () => {
|
|
346
|
+
expect(resolveLonghand('textDecorationLine', new Map([['textDecoration', 'red']]))).toBeUndefined()
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('returns undefined when textDecoration is absent', () => {
|
|
350
|
+
expect(resolveLonghand('textDecorationLine', new Map())).toBeUndefined()
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('prefers literal textDecorationLine over extracting from shorthand', () => {
|
|
354
|
+
const values = new Map([
|
|
355
|
+
['textDecorationLine', 'overline'],
|
|
356
|
+
['textDecoration', 'underline red'],
|
|
357
|
+
])
|
|
358
|
+
expect(resolveLonghand('textDecorationLine', values)).toBe('overline')
|
|
359
|
+
})
|
|
360
|
+
})
|
|
203
361
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CSS_PROPERTIES } from '@wix/react-component-schema'
|
|
2
2
|
import { type Value, generate, parse } from 'css-tree'
|
|
3
|
+
import { SHORTHAND_FALLBACKS } from './css-shorthand-composer'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Resolves a CSS longhand to its default value by reading the developer's
|
|
@@ -195,6 +196,25 @@ function fromCorner(from: string, corner: keyof CornerValues): Fallback {
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
|
|
199
|
+
/**
|
|
200
|
+
* `text-decoration: <line> || <style> || <color>` — order is free and any
|
|
201
|
+
* subset is valid. The schema asks for `textDecorationLine` specifically, so
|
|
202
|
+
* we filter the shorthand's tokens down to just the line-keyword ones.
|
|
203
|
+
*
|
|
204
|
+
* Closed set of legal line keywords per the CSS spec (case-insensitive).
|
|
205
|
+
*/
|
|
206
|
+
const TEXT_DECORATION_LINE_KEYWORDS = new Set(['none', 'underline', 'overline', 'line-through', 'blink'])
|
|
207
|
+
|
|
208
|
+
const fromTextDecorationLine: Fallback = (values) => {
|
|
209
|
+
const shorthand = values.get('textDecoration')
|
|
210
|
+
if (shorthand === undefined) return undefined
|
|
211
|
+
const lineTokens = splitTopLevelTokens(shorthand).filter((token) =>
|
|
212
|
+
TEXT_DECORATION_LINE_KEYWORDS.has(token.toLowerCase()),
|
|
213
|
+
)
|
|
214
|
+
if (lineTokens.length === 0) return undefined
|
|
215
|
+
return lineTokens.join(' ')
|
|
216
|
+
}
|
|
217
|
+
|
|
198
218
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
199
219
|
// Resolution table
|
|
200
220
|
//
|
|
@@ -230,6 +250,14 @@ const FALLBACKS: Partial<Record<CssPropertyName, Fallback[]>> = {
|
|
|
230
250
|
// Background — populate the schema's `background` from `background-color` when
|
|
231
251
|
// the developer only wrote the color.
|
|
232
252
|
background: [fromKey('backgroundColor')],
|
|
253
|
+
|
|
254
|
+
// Text-decoration — `text-decoration: underline wavy red` carries line, style,
|
|
255
|
+
// and color in one declaration. Pick out just the line tokens for the schema.
|
|
256
|
+
textDecorationLine: [fromTextDecorationLine],
|
|
257
|
+
|
|
258
|
+
// Shorthand targets (e.g. `font`) live in css-shorthand-composer.ts and are
|
|
259
|
+
// merged in here — composer-driven, conceptually the inverse of the rows above.
|
|
260
|
+
...SHORTHAND_FALLBACKS,
|
|
233
261
|
}
|
|
234
262
|
|
|
235
263
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composes a CSS shorthand value (`font`, …) from longhand declarations the
|
|
3
|
+
* developer wrote. Inverse of the splitters in `css-longhand-resolver.ts`:
|
|
4
|
+
*
|
|
5
|
+
* resolver splits: `padding: 1rem` → `paddingTop: "1rem"`
|
|
6
|
+
* composer assembles: `font-family + font-size` → `font: "16px Arial"`
|
|
7
|
+
*
|
|
8
|
+
* Strict policy: when the CSS spec marks a piece as required and it's missing
|
|
9
|
+
* from the map, the composer returns `undefined` rather than emit a partial
|
|
10
|
+
* string the editor's value parser would reject as invalid shorthand.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
type Composer = (values: Map<string, string>) => string | undefined
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Builds a `font` shorthand value from longhand declarations, per CSS spec:
|
|
17
|
+
* [ <font-style> || <font-variant> || <font-weight> || <font-stretch> ]?
|
|
18
|
+
* <font-size> [ / <line-height> ]? <font-family>
|
|
19
|
+
*
|
|
20
|
+
* Returns undefined unless both required pieces (font-size and font-family)
|
|
21
|
+
* are present. Modifiers with the literal value `normal` are omitted (they're
|
|
22
|
+
* the shorthand's implicit default and would add noise to the emitted string).
|
|
23
|
+
*/
|
|
24
|
+
export function composeFontShorthand(values: Map<string, string>): string | undefined {
|
|
25
|
+
const size = values.get('fontSize')
|
|
26
|
+
const family = values.get('fontFamily')
|
|
27
|
+
if (size === undefined || family === undefined) return undefined
|
|
28
|
+
|
|
29
|
+
const parts: string[] = []
|
|
30
|
+
|
|
31
|
+
// Optional modifiers (style, variant, weight, stretch). Order matters per CSS
|
|
32
|
+
// spec but their order relative to each other is free; size always follows.
|
|
33
|
+
for (const key of ['fontStyle', 'fontVariant', 'fontWeight', 'fontStretch']) {
|
|
34
|
+
const value = values.get(key)
|
|
35
|
+
if (value !== undefined && value.toLowerCase() !== 'normal') parts.push(value)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const lineHeight = values.get('lineHeight')
|
|
39
|
+
if (lineHeight !== undefined && lineHeight.toLowerCase() !== 'normal') {
|
|
40
|
+
parts.push(`${size}/${lineHeight}`)
|
|
41
|
+
} else {
|
|
42
|
+
parts.push(size)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Family names may carry CSS double-quotes (`"Helvetica Neue"`). Normalize to
|
|
46
|
+
// single quotes so the emitted shorthand can be embedded in JSON / snapshot
|
|
47
|
+
// strings without the inner `"` colliding with the outer delimiter. CSS
|
|
48
|
+
// accepts both quote styles equally for family names.
|
|
49
|
+
parts.push(family.replace(/"/g, "'"))
|
|
50
|
+
return parts.join(' ')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Builds a `gap` shorthand value from `row-gap` + `column-gap` longhands.
|
|
55
|
+
*
|
|
56
|
+
* gap: <row-gap> [<column-gap>]?
|
|
57
|
+
*
|
|
58
|
+
* Strict: both axes required. Composing from just one would lie about intent —
|
|
59
|
+
* `gap: 8px` sets BOTH axes to 8px, whereas the developer's `row-gap: 8px`
|
|
60
|
+
* alone leaves column-gap at its initial value. Collapses to a single value
|
|
61
|
+
* when the two axes are equal, matching how developers write it by hand.
|
|
62
|
+
*/
|
|
63
|
+
export function composeGapShorthand(values: Map<string, string>): string | undefined {
|
|
64
|
+
const rowGap = values.get('rowGap')
|
|
65
|
+
const columnGap = values.get('columnGap')
|
|
66
|
+
if (rowGap === undefined || columnGap === undefined) return undefined
|
|
67
|
+
|
|
68
|
+
if (rowGap === columnGap) return rowGap
|
|
69
|
+
return `${rowGap} ${columnGap}`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Routing table for shorthand targets the schema asks for. Maps each target
|
|
74
|
+
* to the composer(s) that build its value from longhand declarations. The
|
|
75
|
+
* resolver in `css-longhand-resolver.ts` merges this in when dispatching
|
|
76
|
+
* `resolveLonghand`.
|
|
77
|
+
*
|
|
78
|
+
* Add new shorthand targets here as they're needed (background, border, …).
|
|
79
|
+
*/
|
|
80
|
+
export const SHORTHAND_FALLBACKS: Record<string, Composer[]> = {
|
|
81
|
+
font: [composeFontShorthand],
|
|
82
|
+
gap: [composeGapShorthand],
|
|
83
|
+
}
|