@adobe/design-data-spec 0.9.0 → 0.10.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/components/accordion.json +18 -1
- package/components/action-bar.json +12 -1
- package/components/action-button.json +19 -1
- package/components/action-group.json +12 -1
- package/components/alert-banner.json +11 -1
- package/components/alert-dialog.json +19 -1
- package/components/avatar-group.json +4 -1
- package/components/avatar.json +4 -1
- package/components/badge.json +4 -1
- package/components/body.json +3 -0
- package/components/bottom-navigation-android.json +17 -0
- package/components/breadcrumbs.json +19 -2
- package/components/button-group.json +12 -1
- package/components/button.json +21 -3
- package/components/calendar.json +22 -0
- package/components/cards.json +18 -1
- package/components/checkbox-group.json +17 -1
- package/components/checkbox.json +24 -1
- package/components/close-button.json +19 -1
- package/components/coach-indicator.json +4 -1
- package/components/coach-mark.json +13 -1
- package/components/code.json +3 -0
- package/components/color-area.json +24 -1
- package/components/color-handle.json +13 -1
- package/components/color-loupe.json +4 -1
- package/components/color-slider.json +24 -1
- package/components/color-wheel.json +24 -1
- package/components/combo-box.json +24 -1
- package/components/contextual-help.json +18 -1
- package/components/date-picker.json +23 -1
- package/components/detail.json +3 -0
- package/components/divider.json +4 -1
- package/components/drop-zone.json +18 -1
- package/components/field-label.json +4 -1
- package/components/heading.json +3 -0
- package/components/help-text.json +5 -2
- package/components/illustrated-message.json +4 -1
- package/components/in-field-progress-button.json +18 -0
- package/components/in-field-progress-circle.json +4 -1
- package/components/in-line-alert.json +11 -1
- package/components/link.json +19 -1
- package/components/list-view.json +18 -1
- package/components/menu.json +19 -1
- package/components/meter.json +4 -1
- package/components/number-field.json +24 -1
- package/components/opacity-checkerboard.json +4 -1
- package/components/picker.json +24 -1
- package/components/popover.json +12 -1
- package/components/progress-bar.json +4 -1
- package/components/progress-circle.json +4 -1
- package/components/radio-button.json +24 -1
- package/components/radio-group.json +17 -1
- package/components/rating.json +24 -1
- package/components/scroll-zoom-bar.json +12 -0
- package/components/search-field.json +28 -1
- package/components/segmented-control.json +18 -1
- package/components/select-box.json +18 -1
- package/components/side-navigation.json +18 -1
- package/components/slider.json +24 -1
- package/components/standard-dialog.json +19 -1
- package/components/standard-panel.json +3 -0
- package/components/status-light.json +4 -1
- package/components/steplist.json +18 -1
- package/components/swatch-group.json +12 -1
- package/components/swatch.json +18 -1
- package/components/switch.json +19 -1
- package/components/tab-bar-ios.json +17 -0
- package/components/table.json +23 -1
- package/components/tabs.json +18 -1
- package/components/tag-field.json +18 -1
- package/components/tag-group.json +18 -1
- package/components/tag.json +18 -1
- package/components/takeover-dialog.json +19 -1
- package/components/text-area.json +28 -1
- package/components/text-field.json +28 -1
- package/components/thumbnail.json +4 -1
- package/components/title.json +40 -10
- package/components/toast.json +13 -1
- package/components/tooltip.json +17 -1
- package/components/tray.json +11 -0
- package/components/tree-view.json +19 -1
- package/conformance/README.md +7 -7
- package/conformance/invalid/SPEC-001/dataset.json +10 -0
- package/conformance/invalid/SPEC-001/expected-errors.json +1 -1
- package/conformance/invalid/SPEC-002/dataset.json +16 -0
- package/conformance/invalid/SPEC-002/expected-errors.json +1 -1
- package/conformance/invalid/SPEC-003/dataset.json +15 -0
- package/conformance/invalid/SPEC-003/expected-errors.json +1 -1
- package/conformance/invalid/SPEC-004/dataset.json +15 -0
- package/conformance/invalid/SPEC-004/expected-errors.json +1 -1
- package/conformance/invalid/SPEC-005/dataset.json +11 -0
- package/conformance/invalid/SPEC-005/expected-errors.json +1 -1
- package/conformance/invalid/SPEC-006/dataset.json +15 -0
- package/conformance/invalid/SPEC-006/expected-errors.json +1 -1
- package/conformance/invalid/SPEC-007/dataset.json +9 -0
- package/conformance/invalid/SPEC-007/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-008/dataset.json +25 -0
- package/conformance/invalid/SPEC-008/expected-errors.json +2 -1
- package/conformance/invalid/SPEC-009/dataset.json +12 -0
- package/conformance/invalid/SPEC-009/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-010/dataset.json +12 -0
- package/conformance/invalid/SPEC-011/dataset.json +25 -0
- package/conformance/invalid/SPEC-012/dataset.json +16 -0
- package/conformance/invalid/SPEC-013/dataset.json +11 -0
- package/conformance/invalid/SPEC-014/dataset.json +12 -0
- package/conformance/invalid/SPEC-014/expected-errors.json +1 -1
- package/conformance/invalid/SPEC-015/dataset.json +22 -0
- package/conformance/invalid/SPEC-015/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-015/tokens.tokens.json +19 -0
- package/conformance/invalid/SPEC-016/dataset.json +11 -0
- package/conformance/invalid/SPEC-016/expected-errors.json +2 -2
- package/conformance/invalid/SPEC-017/dataset.json +10 -0
- package/conformance/invalid/SPEC-024/dataset.json +4 -3
- package/conformance/invalid/SPEC-024/expected-errors.json +2 -2
- package/conformance/invalid/SPEC-025/dataset.json +12 -0
- package/conformance/invalid/SPEC-025/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-026/dataset.json +18 -0
- package/conformance/invalid/SPEC-026/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-028/dataset.json +21 -0
- package/conformance/invalid/SPEC-028/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-029/dataset.json +20 -0
- package/conformance/invalid/SPEC-029/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-030/dataset.json +15 -0
- package/conformance/invalid/SPEC-030/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-031/dataset.json +17 -0
- package/conformance/invalid/SPEC-031/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-032/dataset.json +27 -0
- package/conformance/invalid/SPEC-032/expected-errors.json +15 -0
- package/conformance/resolution/base-fallback/expected.json +1 -1
- package/conformance/resolution/product-layer-wins/expected.json +5 -0
- package/conformance/resolution/product-layer-wins/input/tokens.tokens.json +7 -0
- package/conformance/resolution/product-layer-wins/product-context.json +11 -0
- package/conformance/resolution/product-layer-wins/query.json +4 -0
- package/conformance/resolution/specificity-wins/expected.json +1 -1
- package/conformance/valid/SPEC-014/dataset.json +12 -0
- package/conformance/valid/SPEC-016/dataset.json +16 -0
- package/conformance/valid/SPEC-025/dataset.json +24 -0
- package/conformance/valid/SPEC-026/dataset.json +15 -0
- package/conformance/valid/SPEC-028/dataset.json +21 -0
- package/conformance/valid/SPEC-029/dataset.json +24 -0
- package/conformance/valid/SPEC-030/dataset.json +17 -0
- package/conformance/valid/SPEC-031/dataset.json +24 -0
- package/conformance/valid/SPEC-032/dataset.json +16 -0
- package/conformance/valid/component-refs/README.md +5 -0
- package/conformance/valid/component-with-accessibility.json +32 -0
- package/fields/color-scheme.json +2 -2
- package/fields/contrast.json +2 -2
- package/fields/density.json +1 -1
- package/fields/scale.json +2 -2
- package/fields/size.json +1 -1
- package/package.json +4 -13
- package/rules/rules.yaml +40 -4
- package/schemas/accessibility.schema.json +60 -0
- package/schemas/component.schema.json +19 -0
- package/schemas/field.schema.json +2 -2
- package/schemas/manifest.schema.json +1 -1
- package/schemas/{dimension.schema.json → mode-set.schema.json} +3 -3
- package/schemas/token.schema.json +5 -5
- package/schemas/value-types/README.md +20 -0
- package/schemas/value-types/drop-shadow.schema.json +5 -5
- package/schemas/value-types/typography-scale.schema.json +2 -2
- package/schemas/value-types/typography.schema.json +5 -5
- package/spec/accessibility-adapters.md +219 -0
- package/spec/agent-surface.md +4 -4
- package/spec/cascade.md +7 -7
- package/spec/component-format.md +32 -0
- package/spec/document-blocks.md +1 -1
- package/spec/index.md +22 -20
- package/spec/manifest.md +2 -2
- package/spec/mode-sets.md +64 -0
- package/spec/query.md +18 -18
- package/spec/taxonomy.md +5 -5
- package/spec/token-format.md +12 -12
- package/spec/dimensions.md +0 -64
- package/src/canonical.js +0 -61
- package/src/validate.js +0 -190
- /package/conformance/resolution/alias-resolved-after-cascade/{dimensions → mode-sets}/color-scheme.json +0 -0
- /package/conformance/resolution/base-fallback/{dimensions → mode-sets}/color-scheme.json +0 -0
- /package/conformance/resolution/specificity-wins/{dimensions → mode-sets}/color-scheme.json +0 -0
package/spec/token-format.md
CHANGED
|
@@ -35,7 +35,7 @@ A token's `name` field **MAY** be a non-empty plain string instead of a name obj
|
|
|
35
35
|
|
|
36
36
|
**NORMATIVE:** String-named tokens **MUST** trigger rule SPEC-017 (severity: `warning`, category: `tech-debt`). The warning surfaces the token as tracked debt requiring future remediation.
|
|
37
37
|
|
|
38
|
-
**NORMATIVE:** String-named tokens **MUST NOT** participate in name-object cascade
|
|
38
|
+
**NORMATIVE:** String-named tokens **MUST NOT** participate in name-object cascade mode-set matching, specificity calculation, or registry vocabulary checks (SPEC-009 does not apply).
|
|
39
39
|
|
|
40
40
|
**RECOMMENDED:** Authors **SHOULD** treat string names as a temporary escape hatch and track a remediation plan. Each string-named token **SHOULD** eventually be given a structured name object, at which point SPEC-017 no longer fires.
|
|
41
41
|
|
|
@@ -45,9 +45,9 @@ The **name object** identifies the token in a structured way. Implementations us
|
|
|
45
45
|
|
|
46
46
|
**NORMATIVE fields** (all string unless noted):
|
|
47
47
|
|
|
48
|
-
The set of available name-object fields is declared in the design system's **field catalog** (`fields/` directory). Each field declaration conforms to [`field.schema.json`](../schemas/field.schema.json) and specifies its kind (`semantic` or `
|
|
48
|
+
The set of available name-object fields is declared in the design system's **field catalog** (`fields/` directory). Each field declaration conforms to [`field.schema.json`](../schemas/field.schema.json) and specifies its kind (`semantic` or `mode-set`), vocabulary registry, validation severity, and default serialization position. See [Taxonomy](taxonomy.md) for the full concept category hierarchy, component anatomy vs. token objects, and serialization rules.
|
|
49
49
|
|
|
50
|
-
Fields are divided into **semantic fields** (identity, structure, intent) and **
|
|
50
|
+
Fields are divided into **semantic fields** (identity, structure, intent) and **mode-set fields** (axes of variation for cascade resolution). The tables below list Spectrum's foundation-standard fields as declared in the catalog.
|
|
51
51
|
|
|
52
52
|
#### Semantic fields
|
|
53
53
|
|
|
@@ -67,16 +67,16 @@ Fields are divided into **semantic fields** (identity, structure, intent) and **
|
|
|
67
67
|
| `density` | OPTIONAL | Density | Space within or around component parts (e.g. `spacious`, `compact`). |
|
|
68
68
|
| `shape` | OPTIONAL | Shape | Relative to overall component shape (e.g. `uniform`). |
|
|
69
69
|
|
|
70
|
-
####
|
|
70
|
+
#### Mode-set fields
|
|
71
71
|
|
|
72
|
-
| Field | Status | Description
|
|
73
|
-
| --------------- | -------- |
|
|
74
|
-
| `colorScheme` | OPTIONAL |
|
|
75
|
-
| `scale` | OPTIONAL |
|
|
76
|
-
| `contrast` | OPTIONAL |
|
|
77
|
-
| Additional keys | OPTIONAL | Other
|
|
72
|
+
| Field | Status | Description |
|
|
73
|
+
| --------------- | -------- | ------------------------------------------------------------------------------------------- |
|
|
74
|
+
| `colorScheme` | OPTIONAL | Mode set: light / dark / wireframe / etc. |
|
|
75
|
+
| `scale` | OPTIONAL | Mode set: platform density scale (e.g. `desktop`, `mobile`). Distinct from semantic `size`. |
|
|
76
|
+
| `contrast` | OPTIONAL | Mode set: contrast level (e.g. `regular`, `high`). |
|
|
77
|
+
| Additional keys | OPTIONAL | Other mode sets declared in the dataset’s mode set catalog (see [Mode Sets](mode-sets.md)). |
|
|
78
78
|
|
|
79
|
-
**NORMATIVE:** Each field is validated according to the `validation` severity declared in its field declaration. Semantic fields typically use **advisory** severity (warning);
|
|
79
|
+
**NORMATIVE:** Each field is validated according to the `validation` severity declared in its field declaration. Semantic fields typically use **advisory** severity (warning); mode-set fields use **strict** severity (error). See [Taxonomy — Name object field categories](taxonomy.md#name-object-field-categories).
|
|
80
80
|
|
|
81
81
|
**RECOMMENDED:** Name objects use a consistent key ordering in authored files for diffs; this is not a conformance requirement. Concept ordering for serialized names is defined in [Taxonomy — Default serialization](taxonomy.md#default-serialization-legacy-format).
|
|
82
82
|
|
|
@@ -249,7 +249,7 @@ The current `@adobe/spectrum-tokens` JSON uses **sets** (`color-set`, `scale-set
|
|
|
249
249
|
| ------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
250
250
|
| Identity field | `id` | `uuid` |
|
|
251
251
|
| Name model | `name.original` (string) + `name.structure` (nested object) | Flat fields directly on `name` (`property`, `component`, `colorScheme`, …) |
|
|
252
|
-
| Complexity tracking | `name.semanticComplexity` (stored on token) | Computed at validation time from
|
|
252
|
+
| Complexity tracking | `name.semanticComplexity` (stored on token) | Computed at validation time from mode set declarations |
|
|
253
253
|
|
|
254
254
|
**NORMATIVE:** The flat `name` object defined in this spec is the authoritative serialization format. RFC [#646](https://github.com/adobe/spectrum-design-data/issues/646)'s `name.structure` / `name.original` shape is not a conformance target; it remains a useful reference for the analytical model that informed this design.
|
|
255
255
|
|
package/spec/dimensions.md
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# Dimensions
|
|
2
|
-
|
|
3
|
-
**Spec version:** `1.0.0-draft` (see [Overview](index.md))
|
|
4
|
-
|
|
5
|
-
This document defines how **dimensions** (modes such as color scheme, scale, contrast) are **declared**, assigned **defaults**, and validated for **coverage**.
|
|
6
|
-
|
|
7
|
-
## Dimension declaration
|
|
8
|
-
|
|
9
|
-
A **dimension declaration** is a JSON object describing one axis of variation. It **MUST** conform to [`dimension.schema.json`](../schemas/dimension.schema.json) (canonical `$id`: `https://opensource.adobe.com/spectrum-design-data/schemas/v0/dimension.schema.json`).
|
|
10
|
-
|
|
11
|
-
### Required fields
|
|
12
|
-
|
|
13
|
-
| Field | Description |
|
|
14
|
-
| --------- | --------------------------------------------------------- |
|
|
15
|
-
| `name` | Stable identifier for the dimension (e.g. `colorScheme`). |
|
|
16
|
-
| `modes` | Array of allowed mode values (strings). |
|
|
17
|
-
| `default` | Default mode; **MUST** be a member of `modes`. |
|
|
18
|
-
|
|
19
|
-
### Optional fields
|
|
20
|
-
|
|
21
|
-
| Field | Description |
|
|
22
|
-
| ------------- | ------------------------------------ |
|
|
23
|
-
| `description` | Human-readable documentation. |
|
|
24
|
-
| `coverage` | Rules for mode coverage (see below). |
|
|
25
|
-
|
|
26
|
-
## Built-in dimensions
|
|
27
|
-
|
|
28
|
-
These dimensions are declared in the `dimensions/` catalog (see [Dimension catalog](#dimension-catalog)) and **SHOULD** be used consistently across Spectrum-compatible datasets:
|
|
29
|
-
|
|
30
|
-
| `name` | `modes` | `default` | Notes |
|
|
31
|
-
| ------------- | ---------------------------- | --------- | --------------------------------------------------------------------------------- |
|
|
32
|
-
| `colorScheme` | `light`, `dark`, `wireframe` | `light` | Theme / appearance. |
|
|
33
|
-
| `scale` | `desktop`, `mobile` | `desktop` | Density scale. Legacy names; desktop = medium, mobile = large in W3C terminology. |
|
|
34
|
-
| `contrast` | `regular`, `high` | `regular` | Accessibility contrast level. |
|
|
35
|
-
|
|
36
|
-
## Dimension catalog
|
|
37
|
-
|
|
38
|
-
The Spectrum foundation publishes dimension declarations as JSON files under `packages/design-data-spec/dimensions/`. Each file conforms to [`dimension.schema.json`](../schemas/dimension.schema.json).
|
|
39
|
-
|
|
40
|
-
**NORMATIVE:** Tooling (validators, resolution engine) **MUST** load dimension declarations from the dataset’s dimension catalog before performing specificity calculations or coverage validation.
|
|
41
|
-
|
|
42
|
-
**RECOMMENDED:** The catalog directory is named `dimensions/` and is co-located with the dataset’s spec package or manifest.
|
|
43
|
-
|
|
44
|
-
## Optional dimensions
|
|
45
|
-
|
|
46
|
-
Additional dimensions (e.g. `language`, `motion`) **MAY** be declared in a dataset’s dimension catalog. Token name objects **MAY** include keys matching declared dimension names.
|
|
47
|
-
|
|
48
|
-
## Defaults and specificity
|
|
49
|
-
|
|
50
|
-
**NORMATIVE:** A token name object **omitting** a dimension field implies the token applies under the dimension’s **`default`** mode for specificity and matching purposes unless the spec for that dimension states otherwise.
|
|
51
|
-
|
|
52
|
-
**NORMATIVE:** Only **non-default** dimension fields on the name object increase **semantic specificity** (see [Cascade](cascade.md)).
|
|
53
|
-
|
|
54
|
-
## Coverage validation
|
|
55
|
-
|
|
56
|
-
**RECOMMENDED:** If a dimension’s `coverage` requires **peer modes** (e.g. defining `dark` requires `light`), validators implement rule **`SPEC-005`** (see `rules/rules.yaml`).
|
|
57
|
-
|
|
58
|
-
**RECOMMENDED:** Explicit **combination** tokens are used for rare cross-dimensional cases instead of inferring Cartesian products.
|
|
59
|
-
|
|
60
|
-
## References
|
|
61
|
-
|
|
62
|
-
* [#646 — Token Schema Structure and Validation System](https://github.com/adobe/spectrum-design-data/discussions/646)
|
|
63
|
-
* [#714 — Design Data Specification](https://github.com/adobe/spectrum-design-data/discussions/714)
|
|
64
|
-
* [#746 — Phase 2: Dimension declarations (machine-readable)](https://github.com/adobe/spectrum-design-data/issues/746)
|
package/src/canonical.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
-
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
-
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
|
|
7
|
-
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
-
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
-
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
-
governing permissions and limitations under the License.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// Canonical vocabulary sets derived from spec chapters.
|
|
14
|
-
// Sources: spec/component-format.md, spec/anatomy-format.md, spec/state-model.md
|
|
15
|
-
|
|
16
|
-
export const CANONICAL_SLOTS = new Set([
|
|
17
|
-
"default",
|
|
18
|
-
"icon",
|
|
19
|
-
"label",
|
|
20
|
-
"help-text",
|
|
21
|
-
"negative-help-text",
|
|
22
|
-
"action",
|
|
23
|
-
"heading",
|
|
24
|
-
"description",
|
|
25
|
-
"hero",
|
|
26
|
-
"footer",
|
|
27
|
-
"tooltip",
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
export const CANONICAL_ANATOMY_PARTS = new Set([
|
|
31
|
-
"body",
|
|
32
|
-
"checkmark",
|
|
33
|
-
"disclosure-triangle",
|
|
34
|
-
"field",
|
|
35
|
-
"handle",
|
|
36
|
-
"header",
|
|
37
|
-
"icon",
|
|
38
|
-
"label",
|
|
39
|
-
"picker",
|
|
40
|
-
"progress-bar",
|
|
41
|
-
"swatch",
|
|
42
|
-
"thumbnail",
|
|
43
|
-
"track",
|
|
44
|
-
"value",
|
|
45
|
-
]);
|
|
46
|
-
|
|
47
|
-
export const CANONICAL_STATES = new Set([
|
|
48
|
-
"default",
|
|
49
|
-
"hover",
|
|
50
|
-
"focus",
|
|
51
|
-
"focus-visible",
|
|
52
|
-
"active",
|
|
53
|
-
"pressed",
|
|
54
|
-
"selected",
|
|
55
|
-
"indeterminate",
|
|
56
|
-
"disabled",
|
|
57
|
-
"read-only",
|
|
58
|
-
"invalid",
|
|
59
|
-
"valid",
|
|
60
|
-
"dragging",
|
|
61
|
-
]);
|
package/src/validate.js
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
-
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
-
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
|
|
7
|
-
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
-
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
-
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
-
governing permissions and limitations under the License.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Layer 2 cross-reference validator for design-data-spec.
|
|
15
|
-
*
|
|
16
|
-
* Implements SPEC-018 through SPEC-027: semantic rules that validate token
|
|
17
|
-
* name-object fields against component declarations, validate component
|
|
18
|
-
* declarations internally, and validate tokenBindings references.
|
|
19
|
-
*
|
|
20
|
-
* @see spec/component-format.md#spec-rules
|
|
21
|
-
* @see spec/anatomy-format.md#spec-rules
|
|
22
|
-
* @see spec/state-model.md#spec-rules
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import {
|
|
26
|
-
CANONICAL_SLOTS,
|
|
27
|
-
CANONICAL_ANATOMY_PARTS,
|
|
28
|
-
CANONICAL_STATES,
|
|
29
|
-
} from "./canonical.js";
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @typedef {{ ruleId: string, severity: 'error'|'warning', message: string, tokenName?: string, componentName?: string }} Diagnostic
|
|
33
|
-
* @typedef {{ name: string|object, [key: string]: unknown }} Token
|
|
34
|
-
* @typedef {{ name: string, options?: object, anatomy?: Array<{name:string,description?:string}>, slots?: Array<{name:string,description?:string}>, states?: Array<{name:string,trigger?:string,precedence?:number,layered?:boolean,description?:string}> }} ComponentDeclaration
|
|
35
|
-
* @typedef {{ tokens?: Token[], components?: ComponentDeclaration[] }} Dataset
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Validate a dataset for SPEC-018 through SPEC-024 compliance.
|
|
40
|
-
*
|
|
41
|
-
* @param {Dataset} dataset
|
|
42
|
-
* @returns {Diagnostic[]}
|
|
43
|
-
*/
|
|
44
|
-
export function validateDataset(dataset) {
|
|
45
|
-
const tokens = dataset.tokens ?? [];
|
|
46
|
-
const components = dataset.components ?? [];
|
|
47
|
-
|
|
48
|
-
// Build component lookup map keyed by name.
|
|
49
|
-
const componentMap = new Map(components.map((c) => [c.name, c]));
|
|
50
|
-
|
|
51
|
-
const diagnostics = [];
|
|
52
|
-
|
|
53
|
-
// --- Token cross-reference rules ---
|
|
54
|
-
for (const token of tokens) {
|
|
55
|
-
const name = token.name;
|
|
56
|
-
// String names (SPEC-017 escape hatch) skip cross-reference checks.
|
|
57
|
-
if (typeof name !== "object" || name === null) continue;
|
|
58
|
-
|
|
59
|
-
const tokenLabel = JSON.stringify(name);
|
|
60
|
-
|
|
61
|
-
if (name.component != null) {
|
|
62
|
-
// SPEC-018: component name must be declared
|
|
63
|
-
if (!componentMap.has(name.component)) {
|
|
64
|
-
diagnostics.push({
|
|
65
|
-
ruleId: "SPEC-018",
|
|
66
|
-
severity: "error",
|
|
67
|
-
message: `Token '${tokenLabel}' references undeclared component '${name.component}'`,
|
|
68
|
-
tokenName: tokenLabel,
|
|
69
|
-
});
|
|
70
|
-
// Can't validate further fields without a component declaration.
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const component = componentMap.get(name.component);
|
|
75
|
-
|
|
76
|
-
// SPEC-019: variant must be in component's variant option enum
|
|
77
|
-
if (name.variant != null) {
|
|
78
|
-
const variantEnum = component.options?.variant?.enum;
|
|
79
|
-
if (Array.isArray(variantEnum) && !variantEnum.includes(name.variant)) {
|
|
80
|
-
diagnostics.push({
|
|
81
|
-
ruleId: "SPEC-019",
|
|
82
|
-
severity: "error",
|
|
83
|
-
message: `Token '${tokenLabel}' has variant '${name.variant}' which is not declared on component '${name.component}'`,
|
|
84
|
-
tokenName: tokenLabel,
|
|
85
|
-
componentName: name.component,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// SPEC-020: anatomy must match a declared anatomy part name
|
|
91
|
-
if (name.anatomy != null) {
|
|
92
|
-
const declaredParts = new Set(
|
|
93
|
-
(component.anatomy ?? []).map((p) => p.name),
|
|
94
|
-
);
|
|
95
|
-
if (declaredParts.size > 0 && !declaredParts.has(name.anatomy)) {
|
|
96
|
-
diagnostics.push({
|
|
97
|
-
ruleId: "SPEC-020",
|
|
98
|
-
severity: "error",
|
|
99
|
-
message: `Token '${tokenLabel}' references undeclared anatomy part '${name.anatomy}' on component '${name.component}'`,
|
|
100
|
-
tokenName: tokenLabel,
|
|
101
|
-
componentName: name.component,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// SPEC-022: state must match a declared state name (only when states are declared)
|
|
107
|
-
if (name.state != null) {
|
|
108
|
-
const declaredStates = new Set(
|
|
109
|
-
(component.states ?? []).map((s) => s.name),
|
|
110
|
-
);
|
|
111
|
-
if (declaredStates.size > 0 && !declaredStates.has(name.state)) {
|
|
112
|
-
diagnostics.push({
|
|
113
|
-
ruleId: "SPEC-022",
|
|
114
|
-
severity: "error",
|
|
115
|
-
message: `Token '${tokenLabel}' references undeclared state '${name.state}' on component '${name.component}'`,
|
|
116
|
-
tokenName: tokenLabel,
|
|
117
|
-
componentName: name.component,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// --- Component declaration internal rules ---
|
|
125
|
-
for (const component of components) {
|
|
126
|
-
const cName = component.name;
|
|
127
|
-
|
|
128
|
-
// SPEC-021: custom slot names should have descriptions
|
|
129
|
-
for (const slot of component.slots ?? []) {
|
|
130
|
-
if (!CANONICAL_SLOTS.has(slot.name) && !slot.description) {
|
|
131
|
-
diagnostics.push({
|
|
132
|
-
ruleId: "SPEC-021",
|
|
133
|
-
severity: "warning",
|
|
134
|
-
message: `Component '${cName}' has custom slot '${slot.name}' with no description — add a description or use a canonical slot name`,
|
|
135
|
-
componentName: cName,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// SPEC-023: custom anatomy part names should have descriptions
|
|
141
|
-
for (const part of component.anatomy ?? []) {
|
|
142
|
-
if (!CANONICAL_ANATOMY_PARTS.has(part.name) && !part.description) {
|
|
143
|
-
diagnostics.push({
|
|
144
|
-
ruleId: "SPEC-023",
|
|
145
|
-
severity: "warning",
|
|
146
|
-
message: `Component '${cName}' has custom anatomy part '${part.name}' with no description`,
|
|
147
|
-
componentName: cName,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// SPEC-024: custom state names should have descriptions
|
|
153
|
-
for (const state of component.states ?? []) {
|
|
154
|
-
if (!CANONICAL_STATES.has(state.name) && !state.description) {
|
|
155
|
-
diagnostics.push({
|
|
156
|
-
ruleId: "SPEC-024",
|
|
157
|
-
severity: "warning",
|
|
158
|
-
message: `Component '${cName}' has custom state '${state.name}' with no description`,
|
|
159
|
-
componentName: cName,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// --- Token binding rules ---
|
|
166
|
-
|
|
167
|
-
// SPEC-027: each tokenBindings[].token must resolve to a known token name.
|
|
168
|
-
// String-named tokens are matched directly. Name-object tokens are skipped
|
|
169
|
-
// here because tokenBindings always reference tokens by their string name.
|
|
170
|
-
const tokenNameSet = new Set(
|
|
171
|
-
tokens
|
|
172
|
-
.map((t) => (typeof t.name === "string" ? t.name : null))
|
|
173
|
-
.filter(Boolean),
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
for (const component of components) {
|
|
177
|
-
for (const binding of component.tokenBindings ?? []) {
|
|
178
|
-
if (!tokenNameSet.has(binding.token)) {
|
|
179
|
-
diagnostics.push({
|
|
180
|
-
ruleId: "SPEC-027",
|
|
181
|
-
severity: "error",
|
|
182
|
-
message: `Component '${component.name}' tokenBindings references unknown token '${binding.token}'`,
|
|
183
|
-
componentName: component.name,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return diagnostics;
|
|
190
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|