@symbo.ls/default-config 3.14.0 → 3.14.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/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Default Config
2
2
 
3
- Default design system configuration for Symbols v4. Provides baseline color, typography, spacing, media query, and theme definitions used when no custom config is supplied. The atomic CSS engine (`@symbo.ls/css`) and `@symbo.ls/scratch` consume this config to generate design tokens.
3
+ Default design system configuration for Symbols v3.14. Provides baseline color, typography, spacing, media query, and theme definitions used when no custom config is supplied. The atomic CSS engine (`@symbo.ls/css`) and `@symbo.ls/scratch` consume this config to generate design tokens.
4
4
 
5
5
  Check out the [docs page](http://symbols.app/developersintro#configuration) to learn more.
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Asset-aware media components: Img, Video, Audio, AssetPicture. The
3
+ * components themselves are plain DOMQL config objects — DOMQL evaluates
4
+ * functions against an element at runtime. These tests bypass DOMQL and
5
+ * call the functions directly with a mock `el` shape, asserting the
6
+ * src/srcset/children outputs computed from `el.context.assets`.
7
+ */
8
+
9
+ import { describe, it, expect } from '@jest/globals'
10
+ import { Img } from '../components/Atoms/Img.js'
11
+ import { Video } from '../components/Atoms/Video.js'
12
+ import { Audio } from '../components/Atoms/Audio.js'
13
+ import { AssetPicture } from '../components/Atoms/AssetPicture.js'
14
+
15
+ const HERO_ASSET = {
16
+ src: '/assets/hero.jpg',
17
+ type: 'image/jpeg',
18
+ category: 'image',
19
+ variants: [
20
+ { src: '/assets/hero.jpg', format: 'image/jpeg', scale: 1 },
21
+ { src: '/assets/hero@2x.jpg', format: 'image/jpeg', scale: 2 },
22
+ { src: '/assets/hero.webp', format: 'image/webp', scale: 1 },
23
+ { src: '/assets/hero@2x.webp', format: 'image/webp', scale: 2 },
24
+ { src: '/assets/hero.avif', format: 'image/avif', scale: 1 }
25
+ ]
26
+ }
27
+
28
+ const HERO_WIDTHS_ASSET = {
29
+ src: '/assets/cover.jpg',
30
+ type: 'image/jpeg',
31
+ category: 'image',
32
+ variants: [
33
+ { src: '/assets/cover.jpg', format: 'image/jpeg', scale: 1 },
34
+ { src: '/assets/cover-640w.jpg', format: 'image/jpeg', scale: 1, width: 640 },
35
+ { src: '/assets/cover-1280w.jpg', format: 'image/jpeg', scale: 1, width: 1280 }
36
+ ]
37
+ }
38
+
39
+ const INTRO_VIDEO_ASSET = {
40
+ src: '/assets/intro.mp4',
41
+ type: 'video/mp4',
42
+ category: 'video',
43
+ variants: [
44
+ { src: '/assets/intro.mp4', format: 'video/mp4', scale: 1 },
45
+ { src: '/assets/intro.webm', format: 'video/webm', scale: 1 }
46
+ ]
47
+ }
48
+
49
+ const VOICE_AUDIO_ASSET = {
50
+ src: '/assets/voice.mp3',
51
+ type: 'audio/mpeg',
52
+ category: 'audio',
53
+ variants: [
54
+ { src: '/assets/voice.mp3', format: 'audio/mpeg', scale: 1 },
55
+ { src: '/assets/voice.ogg', format: 'audio/ogg', scale: 1 }
56
+ ]
57
+ }
58
+
59
+ const ctxWith = (assets) => ({ context: { assets } })
60
+
61
+ const elFor = (overrides = {}) => ({
62
+ ...overrides,
63
+ context: { assets: { 'hero.jpg': HERO_ASSET } }
64
+ })
65
+
66
+ describe('Img — asset-manifest awareness via src prop', () => {
67
+ it('falls through to literal src when no manifest exists', () => {
68
+ const el = { src: '/manual.jpg', alt: 'a' }
69
+ expect(Img.attr.src(el)).toBe('/manual.jpg')
70
+ expect(Img.attr.srcset(el)).toBeUndefined()
71
+ })
72
+
73
+ it('uses asset.src when src matches a manifest key', () => {
74
+ const el = { src: 'hero.jpg', ...ctxWith({ 'hero.jpg': HERO_ASSET }) }
75
+ expect(Img.attr.src(el)).toBe('/assets/hero.jpg')
76
+ })
77
+
78
+ it('emits scale-based srcset for same-format variants only', () => {
79
+ const el = { src: 'hero.jpg', ...ctxWith({ 'hero.jpg': HERO_ASSET }) }
80
+ const srcset = Img.attr.srcset(el)
81
+ // Same-format only — webp/avif must not appear in srcset (those need <picture>).
82
+ expect(srcset).toBe('/assets/hero@2x.jpg 2x')
83
+ expect(srcset).not.toMatch(/webp|avif/)
84
+ })
85
+
86
+ it('emits width-based srcset when variants carry .width', () => {
87
+ const el = { src: 'cover.jpg', ...ctxWith({ 'cover.jpg': HERO_WIDTHS_ASSET }) }
88
+ expect(Img.attr.srcset(el)).toBe('/assets/cover-640w.jpg 640w, /assets/cover-1280w.jpg 1280w')
89
+ })
90
+
91
+ it('treats src as a literal URL when not in the manifest', () => {
92
+ const el = { src: '/manual.jpg', alt: 'a', ...ctxWith({}) }
93
+ expect(Img.attr.src(el)).toBe('/manual.jpg')
94
+ expect(Img.attr.srcset(el)).toBeUndefined()
95
+ })
96
+
97
+ it('preserves the title-from-alt fallback', () => {
98
+ expect(Img.attr.title({ title: 't' })).toBe('t')
99
+ expect(Img.attr.title({ alt: 'a' })).toBe('a')
100
+ })
101
+ })
102
+
103
+ describe('Video — asset-manifest awareness', () => {
104
+ it('emits a <source> child per format variant', () => {
105
+ const el = { src: 'intro.mp4', ...ctxWith({ 'intro.mp4': INTRO_VIDEO_ASSET }) }
106
+ const children = Video.children(el)
107
+ expect(children).toEqual([
108
+ { tag: 'source', attr: { src: '/assets/intro.mp4', type: 'video/mp4' } },
109
+ { tag: 'source', attr: { src: '/assets/intro.webm', type: 'video/webm' } }
110
+ ])
111
+ })
112
+
113
+ it('sets src to the primary variant URL', () => {
114
+ const el = { src: 'intro.mp4', ...ctxWith({ 'intro.mp4': INTRO_VIDEO_ASSET }) }
115
+ expect(Video.attr.src(el)).toBe('/assets/intro.mp4')
116
+ })
117
+
118
+ it('returns undefined when no asset resolves (lets DOMQL fall through)', () => {
119
+ expect(Video.children({ src: '/manual.mp4' })).toBeUndefined()
120
+ })
121
+ })
122
+
123
+ describe('Audio — asset-manifest awareness', () => {
124
+ it('emits a <source> child per format variant', () => {
125
+ const el = { src: 'voice.mp3', ...ctxWith({ 'voice.mp3': VOICE_AUDIO_ASSET }) }
126
+ expect(Audio.children(el)).toEqual([
127
+ { tag: 'source', attr: { src: '/assets/voice.mp3', type: 'audio/mpeg' } },
128
+ { tag: 'source', attr: { src: '/assets/voice.ogg', type: 'audio/ogg' } }
129
+ ])
130
+ })
131
+
132
+ it('sets src to the primary variant URL', () => {
133
+ const el = { src: 'voice.mp3', ...ctxWith({ 'voice.mp3': VOICE_AUDIO_ASSET }) }
134
+ expect(Audio.attr.src(el)).toBe('/assets/voice.mp3')
135
+ })
136
+ })
137
+
138
+ describe('Smart resolution: cloud strings + context.files fallback', () => {
139
+ // Cloud / legacy entries arrive as bare URL strings, not manifest
140
+ // objects. Components must use the URL verbatim and skip variant
141
+ // emission entirely (no <source> chain, no srcset).
142
+
143
+ it('Img: cloud-string URL in context.assets resolves to plain src, no srcset', () => {
144
+ const el = { src: 'logo', ...ctxWith({ logo: 'https://cdn.example.com/logo.svg' }) }
145
+ expect(Img.attr.src(el)).toBe('https://cdn.example.com/logo.svg')
146
+ expect(Img.attr.srcset(el)).toBeUndefined()
147
+ })
148
+
149
+ it('Img: falls back to context.files when key missing in context.assets', () => {
150
+ const el = {
151
+ src: 'doc.pdf-thumb.jpg',
152
+ context: { files: { 'doc.pdf-thumb.jpg': '/cdn/doc-thumb.jpg' } }
153
+ }
154
+ expect(Img.attr.src(el)).toBe('/cdn/doc-thumb.jpg')
155
+ })
156
+
157
+ it('Video: cloud-string URL skips <source> children', () => {
158
+ const el = { src: 'reel', ...ctxWith({ reel: 'https://cdn.example.com/reel.mp4' }) }
159
+ expect(Video.attr.src(el)).toBe('https://cdn.example.com/reel.mp4')
160
+ // Video.children falls through to el.children; with none defined that's undefined.
161
+ expect(Video.children(el)).toBeUndefined()
162
+ })
163
+
164
+ it('Audio: cloud-string URL skips <source> children', () => {
165
+ const el = { src: 'theme', ...ctxWith({ theme: 'https://cdn.example.com/theme.mp3' }) }
166
+ expect(Audio.attr.src(el)).toBe('https://cdn.example.com/theme.mp3')
167
+ expect(Audio.children(el)).toBeUndefined()
168
+ })
169
+
170
+ it('AssetPicture: cloud-string URL emits a single <img> (no <source> chain)', () => {
171
+ const el = { src: 'banner', alt: 'Banner', ...ctxWith({ banner: 'https://cdn.example.com/banner.jpg' }) }
172
+ expect(AssetPicture.children(el)).toEqual([
173
+ { tag: 'img', attr: { src: 'https://cdn.example.com/banner.jpg', alt: 'Banner' } }
174
+ ])
175
+ })
176
+
177
+ it('Img: variant manifest still wins when both shapes present in different stores', () => {
178
+ // Same key in both context.assets (cloud string) and context.files (manifest).
179
+ // assets is checked first per resolveAsset's lookup order.
180
+ const el = {
181
+ src: 'hero.jpg',
182
+ context: {
183
+ assets: { 'hero.jpg': '/cloud/hero.jpg' },
184
+ files: { 'hero.jpg': HERO_ASSET }
185
+ }
186
+ }
187
+ expect(Img.attr.src(el)).toBe('/cloud/hero.jpg')
188
+ expect(Img.attr.srcset(el)).toBeUndefined() // cloud string → no variants
189
+ })
190
+
191
+ it('AssetPicture: object-shape entry without variants array emits just <img>', () => {
192
+ // Real-world cloud shape — provider returns { src, type } but no variant chain.
193
+ const minimalManifest = { src: 'https://cdn.example.com/hero.jpg', type: 'image/jpeg' }
194
+ const el = { src: 'hero.jpg', alt: 'Hero', ...ctxWith({ 'hero.jpg': minimalManifest }) }
195
+ expect(AssetPicture.children(el)).toEqual([
196
+ { tag: 'img', attr: { src: 'https://cdn.example.com/hero.jpg', alt: 'Hero' } }
197
+ ])
198
+ })
199
+ })
200
+
201
+ describe('AssetPicture — format-negotiated <picture>', () => {
202
+ it('emits <source type=…> for each non-primary format + trailing <img>', () => {
203
+ const el = { src: 'hero.jpg', alt: 'Hero', ...ctxWith({ 'hero.jpg': HERO_ASSET }) }
204
+ const children = AssetPicture.children(el)
205
+
206
+ // Non-primary formats become <source>: webp and avif (jpg is the primary).
207
+ const sources = children.filter((c) => c.tag === 'source')
208
+ const types = sources.map((c) => c.attr.type).sort()
209
+ expect(types).toEqual(['image/avif', 'image/webp'])
210
+
211
+ // Trailing <img> uses the primary fallback URL + same-format srcset.
212
+ const img = children.find((c) => c.tag === 'img')
213
+ expect(img.attr.src).toBe('/assets/hero.jpg')
214
+ expect(img.attr.alt).toBe('Hero')
215
+ expect(img.attr.srcset).toBe('/assets/hero@2x.jpg 2x')
216
+ })
217
+
218
+ it('webp <source> includes scale variants in its srcset', () => {
219
+ const el = { src: 'hero.jpg', ...ctxWith({ 'hero.jpg': HERO_ASSET }) }
220
+ const children = AssetPicture.children(el)
221
+ const webp = children.find((c) => c.tag === 'source' && c.attr.type === 'image/webp')
222
+ expect(webp.attr.srcset).toBe('/assets/hero.webp, /assets/hero@2x.webp 2x')
223
+ })
224
+
225
+ it('avif <source> with single variant emits no scale descriptor', () => {
226
+ const el = { src: 'hero.jpg', ...ctxWith({ 'hero.jpg': HERO_ASSET }) }
227
+ const avif = AssetPicture.children(el).find((c) => c.tag === 'source' && c.attr.type === 'image/avif')
228
+ expect(avif.attr.srcset).toBe('/assets/hero.avif')
229
+ })
230
+
231
+ it('returns undefined when no asset resolves (lets DOMQL fall through)', () => {
232
+ expect(AssetPicture.children({ src: '/manual.jpg' })).toBeUndefined()
233
+ })
234
+
235
+ it('emits just <img> when asset has no variants array', () => {
236
+ const minimalAsset = { src: '/assets/x.jpg', type: 'image/jpeg' }
237
+ const el = { src: 'x.jpg', alt: 'X', ...ctxWith({ 'x.jpg': minimalAsset }) }
238
+ expect(AssetPicture.children(el)).toEqual([
239
+ { tag: 'img', attr: { src: '/assets/x.jpg', alt: 'X' } }
240
+ ])
241
+ })
242
+ })
@@ -0,0 +1,41 @@
1
+ /**
2
+ * FT-FRAMEWORK-1: the explicit COMPONENTS manifest must surface every
3
+ * uppercase-keyed component published by the default-config atoms /
4
+ * input / component modules. Parcel under smbls's `sideEffects: false`
5
+ * was pruning entries off the namespace import that prepare.js used to
6
+ * iterate, and consumer projects ended up missing atoms from
7
+ * `context.components`. This test pins the manifest contract so a future
8
+ * build-time prune can't silently strip entries again.
9
+ */
10
+
11
+ import { describe, it, expect } from '@jest/globals'
12
+ import { COMPONENTS } from '../components/index.js'
13
+
14
+ const REQUIRED = [
15
+ // Atoms
16
+ 'Block', 'Box', 'Flex', 'Grid', 'Img', 'Form', 'Iframe',
17
+ 'Picture', 'Svg', 'Audio', 'Video', 'Hgroup',
18
+ // Text atoms (from Atoms/Text.js)
19
+ 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
20
+ 'P', 'Span', 'Strong',
21
+ // Input
22
+ 'Input', 'NumberInput', 'Checkbox', 'Radio', 'Toggle', 'Textarea',
23
+ // Components
24
+ 'Icon', 'Link', 'Select', 'Button', 'Dialog', 'Tooltip',
25
+ 'Avatar', 'Range', 'Dropdown', 'Notification'
26
+ ]
27
+
28
+ describe('default-config COMPONENTS manifest', () => {
29
+ it('exposes every required atom + component as a uppercase-keyed entry', () => {
30
+ const missing = REQUIRED.filter(name => !COMPONENTS[name])
31
+ expect(missing).toEqual([])
32
+ })
33
+
34
+ it('every entry is an object (not a function or primitive)', () => {
35
+ for (const name of REQUIRED) {
36
+ const entry = COMPONENTS[name]
37
+ expect(entry).toBeTruthy()
38
+ expect(typeof entry).toBe('object')
39
+ }
40
+ })
41
+ })
@@ -0,0 +1,34 @@
1
+ 'use strict'
2
+
3
+ import { resolveAsset, buildSourcesAndImg } from '@symbo.ls/utils'
4
+
5
+ /**
6
+ * Format-negotiated `<picture>`: emits one `<source type="…" srcset="…">`
7
+ * per non-primary format from the asset variant manifest, ending in a
8
+ * fallback `<img src="…">` for the primary (legacy-compatible) format.
9
+ *
10
+ * { extends: 'AssetPicture', src: 'hero.jpg', alt: 'Hero photo' }
11
+ *
12
+ * Asset resolution and source-emission are shared with `Img`/`Video`/
13
+ * `Audio` (see `@symbo.ls/utils#resolveAsset` and `buildSourcesAndImg`):
14
+ * cloud-string URLs, runner-discovered manifests, and literal URLs are
15
+ * all handled transparently. Asset entries without a variants array
16
+ * collapse to a single `<img>` instead of an empty `<source>` chain.
17
+ *
18
+ * Distinct from the existing `Picture` component, which uses
19
+ * `<source media="…">` for theme-based switching. Pick `AssetPicture`
20
+ * when you want format negotiation; pick `Picture` when you want
21
+ * theme/media-query switching.
22
+ */
23
+ export const AssetPicture = {
24
+ tag: 'picture',
25
+
26
+ // Returning `el.children` inside a `children: (el) => …` function would
27
+ // recursively re-enter it. Return undefined so DOMQL falls through to
28
+ // user-provided children (or none) when no asset resolves.
29
+ children: (el) => {
30
+ const asset = resolveAsset(el)
31
+ if (!asset) return undefined
32
+ return buildSourcesAndImg(asset, el.alt)
33
+ }
34
+ }
@@ -0,0 +1,38 @@
1
+ 'use strict'
2
+
3
+ import { resolveAsset } from '@symbo.ls/utils'
4
+
5
+ /**
6
+ * `<audio>` mirroring `Video`'s shape — same `src`-prop convention,
7
+ * same smart resolution. See `resolveAsset` in `@symbo.ls/utils`.
8
+ */
9
+ export const Audio = {
10
+ tag: 'audio',
11
+
12
+ controls: true,
13
+
14
+ attr: {
15
+ src: (el) => {
16
+ const asset = resolveAsset(el)
17
+ return (asset && asset.src) || el.src
18
+ }
19
+ },
20
+
21
+ // Reading `el.children` inside a `children: (el) => …` function would
22
+ // recursively re-enter it. Return undefined to let DOMQL's extends
23
+ // chain pick up user-provided children or fall to childExtends.
24
+ children: (el) => {
25
+ const asset = resolveAsset(el)
26
+ if (!asset || !Array.isArray(asset.variants) || !asset.variants.length) {
27
+ return undefined
28
+ }
29
+ return asset.variants.map((v) => ({
30
+ tag: 'source',
31
+ attr: { src: v.src, type: v.format }
32
+ }))
33
+ },
34
+
35
+ childExtends: {
36
+ tag: 'source'
37
+ }
38
+ }
@@ -1,5 +1,21 @@
1
1
  'use strict'
2
2
 
3
+ // FRAMEWORK-4: Hgroup's `H` and `P` slots used to render unconditionally,
4
+ // so consumers who wrote `Hgroup: { Name, Submeta }` got phantom empty
5
+ // `<h3></h3><p></p>` rows above their actual content. The `if:` predicate
6
+ // suppresses each slot when the consumer hasn't supplied text, html, or
7
+ // any uppercase-keyed child for it. Self-contained (no closures) so the
8
+ // predicate survives JSON round-trip via deepStringifyFunctions.
9
+ const hasSlotContent = (el) => {
10
+ if (el.text != null) return true
11
+ if (el.html != null) return true
12
+ for (const k in el) {
13
+ const c = k.charCodeAt(0)
14
+ if (c >= 65 && c <= 90) return true
15
+ }
16
+ return false
17
+ }
18
+
3
19
  export const Hgroup = {
4
20
  display: 'flex',
5
21
  tag: 'hgroup',
@@ -8,12 +24,14 @@ export const Hgroup = {
8
24
  gap: 'Y2',
9
25
 
10
26
  H: {
27
+ if: hasSlotContent,
11
28
  color: 'title',
12
29
  tag: 'h3',
13
30
  lineHeight: '1em',
14
31
  margin: '0'
15
32
  },
16
33
  P: {
34
+ if: hasSlotContent,
17
35
  margin: '0',
18
36
  color: 'paragraph'
19
37
  }
@@ -1,9 +1,33 @@
1
1
  'use strict'
2
2
 
3
+ import { resolveAsset, buildImgSrcset } from '@symbo.ls/utils'
4
+
5
+ /**
6
+ * `<img>` with smart asset resolution.
7
+ *
8
+ * `src` is the only prop you set. Resolution order (transparent — see
9
+ * `resolveAsset` in `@symbo.ls/utils`):
10
+ * 1. Cloud-uploaded entry in `ctx.assets`/`ctx.files` → use that URL.
11
+ * 2. Runner-discovered manifest in `ctx.assets`/`ctx.files` → use the
12
+ * primary URL + auto-emit `srcset` from same-format variants.
13
+ * 3. Not found → treat `src` as a literal URL (drop-in `<img>`).
14
+ *
15
+ * Format-negotiated variants (webp/avif fallback chain) need `<picture>`
16
+ * markup — use `AssetPicture` for that.
17
+ */
3
18
  export const Img = {
4
19
  tag: 'img',
5
20
 
6
21
  attr: {
7
- title: (el) => el.title || el.alt
22
+ title: (el) => el.title || el.alt,
23
+ src: (el) => {
24
+ const asset = resolveAsset(el)
25
+ return (asset && asset.src) || el.src
26
+ },
27
+ srcset: (el) => {
28
+ const asset = resolveAsset(el)
29
+ const fromAsset = asset ? buildImgSrcset(asset) : ''
30
+ return fromAsset || el.srcset
31
+ }
8
32
  }
9
33
  }
@@ -30,15 +30,48 @@ export const Svg = {
30
30
  let SVGKey = SVG[symbolId]
31
31
  if (SVGKey && SVG[SVGKey]) return useSVGSymbol(SVGKey)
32
32
 
33
- SVGKey = SVG[symbolId] = Math.random()
34
- if (src) {
35
- utils.init({
36
- svg: { [SVGKey]: src }
37
- }, {
38
- document: context.document,
39
- emotion: context.emotion
40
- })
33
+ // FA-L3: when `el.src` is a sprite symbol NAME (string) but the symbol
34
+ // isn't registered in the merged sprite, the previous code allocated
35
+ // `Math.random()` as the href, producing `<use href="#0.7878391…">`
36
+ // which never resolves and silently renders nothing. That made every
37
+ // missing icon a needle-in-haystack visual bug.
38
+ //
39
+ // New behavior:
40
+ // - If `src` is missing entirely: emit an empty `<use>` (no href).
41
+ // - If `src` is a string (sprite-symbol lookup) but unknown: warn
42
+ // in dev and render no `<use>`. The console-warn surfaces the
43
+ // bad icon name to the consumer.
44
+ // - If `src` is an actual SVG body string (registered raw via
45
+ // `utils.init({svg:{…}})` — the original Math.random path): keep
46
+ // a stable hash-derived key instead of Math.random so the same
47
+ // SVG body always resolves to the same symbol id (and so SSR /
48
+ // hydration produce identical output).
49
+ if (!src) return useSVGSymbol('')
50
+
51
+ if (typeof src === 'string' && !src.includes('<')) {
52
+ // Bare symbol name — not in sprite. Warn loudly in dev so the
53
+ // consumer fixes the icon name; render nothing rather than a
54
+ // broken random href.
55
+ if (typeof console !== 'undefined' && console.warn) {
56
+ console.warn(`[Svg] Unknown sprite symbol "${src}" — not registered in design-system sprite. Set the icon name to one that exists in designSystem.icons / context.svg, or register the SVG body via utils.init({ svg: { '${src}': '<svg>...</svg>' } }).`)
57
+ }
58
+ return useSVGSymbol('')
59
+ }
60
+
61
+ // Inline SVG body — derive a stable sha-like key from the content so
62
+ // identical bodies share a sprite symbol across renders. Avoids
63
+ // Math.random churn in SSR diffs.
64
+ let hash = 0
65
+ for (let i = 0; i < src.length; i++) {
66
+ hash = ((hash << 5) - hash + src.charCodeAt(i)) | 0
41
67
  }
68
+ SVGKey = SVG[symbolId] = `svg-${(hash >>> 0).toString(36)}`
69
+ utils.init({
70
+ svg: { [SVGKey]: src }
71
+ }, {
72
+ document: context.document,
73
+ emotion: context.emotion
74
+ })
42
75
 
43
76
  return useSVGSymbol(SVGKey)
44
77
  }
@@ -2,6 +2,8 @@
2
2
 
3
3
  export const Text = {}
4
4
 
5
+ export const Span = { tag: 'span' }
6
+
5
7
  export const H1 = { tag: 'h1' }
6
8
  export const H2 = { tag: 'h2' }
7
9
  export const H3 = { tag: 'h3' }
@@ -1,10 +1,44 @@
1
1
  'use strict'
2
2
 
3
+ import { resolveAsset } from '@symbo.ls/utils'
4
+
5
+ /**
6
+ * `<video>` with smart asset resolution. Set `src` to either a manifest
7
+ * key (local-discovered or cloud-uploaded) or a literal URL — the
8
+ * component figures out which one and emits `<source>` children only
9
+ * when the manifest carries multiple variants.
10
+ *
11
+ * Same `src`-prop convention as `<img>` and `<audio>`. See
12
+ * `resolveAsset` in `@symbo.ls/utils` for the normalization rules.
13
+ */
3
14
  export const Video = {
4
15
  tag: 'video',
5
16
 
6
17
  controls: true,
7
18
 
19
+ attr: {
20
+ src: (el) => {
21
+ const asset = resolveAsset(el)
22
+ return (asset && asset.src) || el.src
23
+ }
24
+ },
25
+
26
+ // Auto-populate <source> children only when the resolved asset carries
27
+ // a variants array. Returns undefined otherwise so DOMQL's extends
28
+ // chain falls through to user-provided `children: [...]` when present
29
+ // (override semantics) or to the empty list. Reading `el.children`
30
+ // inside this function would recursively re-enter it — never do that.
31
+ children: (el) => {
32
+ const asset = resolveAsset(el)
33
+ if (!asset || !Array.isArray(asset.variants) || !asset.variants.length) {
34
+ return undefined
35
+ }
36
+ return asset.variants.map((v) => ({
37
+ tag: 'source',
38
+ attr: { src: v.src, type: v.format }
39
+ }))
40
+ },
41
+
8
42
  childExtends: {
9
43
  tag: 'source'
10
44
  }
@@ -20,11 +20,16 @@ export const Tooltip = {
20
20
  attr: { tooltip: true },
21
21
 
22
22
  Title: {
23
- if: ({ parent }) => isDefined(parent.title) || parent.title,
23
+ // FA106: canonical signature is `(el, s)` destructure-from-object is
24
+ // banned because frank's audit flags it and it's inconsistent with
25
+ // every other reactive prop in the codebase. The `({ parent }) =>`
26
+ // shape happens to work at runtime (the destructure pulls `parent`
27
+ // out of `el`) but breaks on any project that runs frank-audit.
28
+ if: (el) => isDefined(el.parent.title) || el.parent.title,
24
29
  width: 'fit-content',
25
30
  fontWeight: 500,
26
31
  color: 'gray12',
27
- text: ({ parent }) => parent.title
32
+ text: (el) => el.parent.title
28
33
  },
29
34
 
30
35
  P: {
@@ -1,21 +1,60 @@
1
1
  'use strict'
2
2
 
3
3
  // Atoms
4
+ import * as _Block from './Atoms/Block.js'
5
+ import * as _Img from './Atoms/Img.js'
6
+ import * as _Form from './Atoms/Form.js'
7
+ import * as _Iframe from './Atoms/Iframe.js'
8
+ import * as _InteractiveComponent from './Atoms/InteractiveComponent.js'
9
+ import * as _Picture from './Atoms/Picture.js'
10
+ import * as _AssetPicture from './Atoms/AssetPicture.js'
11
+ import * as _Svg from './Atoms/Svg.js'
12
+ import * as _Shape from './Atoms/Shape.js'
13
+ import * as _Video from './Atoms/Video.js'
14
+ import * as _Audio from './Atoms/Audio.js'
15
+ import * as _Theme from './Atoms/Theme.js'
16
+ import * as _Text from './Atoms/Text.js'
17
+ import * as _Box from './Atoms/Box.js'
18
+ import * as _Hgroup from './Atoms/Hgroup.js'
19
+
20
+ // Input
21
+ import * as _Input from './Input/Input.js'
22
+ import * as _NumberInput from './Input/NumberInput.js'
23
+ import * as _Checkbox from './Input/Checkbox.js'
24
+ import * as _Radio from './Input/Radio.js'
25
+ import * as _Toggle from './Input/Toggle.js'
26
+ import * as _Textarea from './Input/Textarea.js'
27
+
28
+ // Components
29
+ import * as _Icon from './Icon/index.js'
30
+ import * as _Link from './Link.js'
31
+ import * as _Select from './Select.js'
32
+ import * as _Button from './Button.js'
33
+ import * as _Dialog from './Dialog.js'
34
+ import * as _Tooltip from './Tooltip/index.js'
35
+ import * as _Avatar from './Avatar.js'
36
+ import * as _Range from './Range.js'
37
+ import * as _Dropdown from './Dropdown.js'
38
+ import * as _Notification from './Notification.js'
39
+
40
+ // Re-export named exports for direct consumers (e.g.
41
+ // `import { Box } from '@symbo.ls/default-config/components'`).
4
42
  export * from './Atoms/Block.js'
5
43
  export * from './Atoms/Img.js'
6
44
  export * from './Atoms/Form.js'
7
45
  export * from './Atoms/Iframe.js'
8
46
  export * from './Atoms/InteractiveComponent.js'
9
47
  export * from './Atoms/Picture.js'
48
+ export * from './Atoms/AssetPicture.js'
10
49
  export * from './Atoms/Svg.js'
11
50
  export * from './Atoms/Shape.js'
12
51
  export * from './Atoms/Video.js'
52
+ export * from './Atoms/Audio.js'
13
53
  export * from './Atoms/Theme.js'
14
54
  export * from './Atoms/Text.js'
15
55
  export * from './Atoms/Box.js'
16
56
  export * from './Atoms/Hgroup.js'
17
57
 
18
- // Input
19
58
  export * from './Input/Input.js'
20
59
  export * from './Input/NumberInput.js'
21
60
  export * from './Input/Checkbox.js'
@@ -23,7 +62,6 @@ export * from './Input/Radio.js'
23
62
  export * from './Input/Toggle.js'
24
63
  export * from './Input/Textarea.js'
25
64
 
26
- // Components
27
65
  export * from './Icon/index.js'
28
66
  export * from './Link.js'
29
67
  export * from './Select.js'
@@ -34,3 +72,20 @@ export * from './Avatar.js'
34
72
  export * from './Range.js'
35
73
  export * from './Dropdown.js'
36
74
  export * from './Notification.js'
75
+
76
+ // FT-FRAMEWORK-1: explicit components manifest. Built by spreading every
77
+ // atom/component module's named exports into one plain object. Parcel
78
+ // (under smbls's `sideEffects: false`) sometimes pruned exports off the
79
+ // `import * as uikit from '...'` namespace that prepare.js iterated, so
80
+ // consumer projects saw atoms missing from `context.components`. This
81
+ // dict is bundler-agnostic — it's a regular object literal whose
82
+ // references are statically locked into the module by `Object.assign`.
83
+ export const COMPONENTS = Object.assign(
84
+ {},
85
+ _Block, _Img, _Form, _Iframe, _InteractiveComponent,
86
+ _Picture, _AssetPicture, _Svg, _Shape, _Video, _Audio,
87
+ _Theme, _Text, _Box, _Hgroup,
88
+ _Input, _NumberInput, _Checkbox, _Radio, _Toggle, _Textarea,
89
+ _Icon, _Link, _Select, _Button, _Dialog, _Tooltip,
90
+ _Avatar, _Range, _Dropdown, _Notification
91
+ )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbo.ls/default-config",
3
- "version": "3.14.0",
3
+ "version": "3.14.1",
4
4
  "source": "./blank/index.js",
5
5
  "main": "./blank/index.js",
6
6
  "type": "module",