@ethlete/cli 3.0.0-next.24 → 3.0.0-next.26
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/CHANGELOG.md +126 -0
- package/design/define-call.ts +69 -0
- package/design/define-part.ts +43 -9
- package/design/embed-page.ts +37 -4
- package/design/icon-sheet.ts +151 -0
- package/design/logo-sheet.ts +52 -0
- package/design/mark-part.ts +79 -2
- package/design/motion-drive.ts +215 -0
- package/design/motion-runtime.ts +224 -5
- package/design/motion-tokens.ts +177 -0
- package/design/spring.ts +53 -0
- package/design/token-sheet.ts +87 -0
- package/design/web/frame-fill.ts +4 -0
- package/design/web/frame-scheme.ts +44 -0
- package/design/web/frame.ts +239 -31
- package/package.json +1 -1
- package/src/lib/design/calls-tsconfig.d.ts +2 -0
- package/src/lib/design/calls-tsconfig.js +11 -5
- package/src/lib/design/calls-tsconfig.js.map +1 -1
- package/src/lib/design/check.d.ts +1 -0
- package/src/lib/design/check.js +62 -6
- package/src/lib/design/check.js.map +1 -1
- package/src/lib/design/contact-sheet.d.ts +9 -0
- package/src/lib/design/contact-sheet.js +38 -4
- package/src/lib/design/contact-sheet.js.map +1 -1
- package/src/lib/design/design-command.js +56 -7
- package/src/lib/design/design-command.js.map +1 -1
- package/src/lib/design/draw-ledger.d.ts +36 -0
- package/src/lib/design/draw-ledger.js +52 -0
- package/src/lib/design/draw-ledger.js.map +1 -0
- package/src/lib/design/force-interactions.d.ts +2 -1
- package/src/lib/design/force-interactions.js +7 -4
- package/src/lib/design/force-interactions.js.map +1 -1
- package/src/lib/design/mcp-command.d.ts +81 -0
- package/src/lib/design/mcp-command.js +422 -0
- package/src/lib/design/mcp-command.js.map +1 -0
- package/src/lib/design/motion-command.d.ts +71 -0
- package/src/lib/design/motion-command.js +308 -0
- package/src/lib/design/motion-command.js.map +1 -0
- package/src/lib/design/motion-endpoint.d.ts +61 -0
- package/src/lib/design/motion-endpoint.js +133 -0
- package/src/lib/design/motion-endpoint.js.map +1 -0
- package/src/lib/design/motion-flow.d.ts +5 -0
- package/src/lib/design/motion-flow.js +29 -0
- package/src/lib/design/motion-flow.js.map +1 -0
- package/src/lib/design/motion-shot.d.ts +43 -0
- package/src/lib/design/motion-shot.js +97 -0
- package/src/lib/design/motion-shot.js.map +1 -0
- package/src/lib/design/paths.d.ts +14 -0
- package/src/lib/design/paths.js +50 -1
- package/src/lib/design/paths.js.map +1 -1
- package/src/lib/design/ready-shots.d.ts +7 -0
- package/src/lib/design/ready-shots.js +22 -1
- package/src/lib/design/ready-shots.js.map +1 -1
- package/src/lib/design/render-packages.d.ts +14 -0
- package/src/lib/design/render-packages.js +18 -3
- package/src/lib/design/render-packages.js.map +1 -1
- package/src/lib/design/render-worker.js +1 -0
- package/src/lib/design/render-worker.js.map +1 -1
- package/src/lib/design/render.js +3 -3
- package/src/lib/design/render.js.map +1 -1
- package/src/lib/design/serve.d.ts +2 -2
- package/src/lib/design/serve.js +191 -21
- package/src/lib/design/serve.js.map +1 -1
- package/src/lib/design/shot-command.d.ts +13 -4
- package/src/lib/design/shot-command.js +30 -24
- package/src/lib/design/shot-command.js.map +1 -1
- package/src/lib/design/shot-store.d.ts +24 -0
- package/src/lib/design/shot-store.js +120 -0
- package/src/lib/design/shot-store.js.map +1 -0
- package/src/lib/design/shot.d.ts +37 -1
- package/src/lib/design/shot.js +185 -34
- package/src/lib/design/shot.js.map +1 -1
- package/src/lib/design/sketch-command.js +197 -51
- package/src/lib/design/sketch-command.js.map +1 -1
- package/src/lib/design/ssr-shims.d.ts +2 -0
- package/src/lib/design/ssr-shims.js +12 -1
- package/src/lib/design/ssr-shims.js.map +1 -1
- package/src/lib/design/studio-api.d.ts +31 -0
- package/src/lib/design/studio-api.js +118 -1
- package/src/lib/design/studio-api.js.map +1 -1
- package/src/lib/design/system/check-system.d.ts +6 -2
- package/src/lib/design/system/check-system.js +102 -20
- package/src/lib/design/system/check-system.js.map +1 -1
- package/src/lib/design/system/colour.d.ts +4 -1
- package/src/lib/design/system/colour.js +54 -2
- package/src/lib/design/system/colour.js.map +1 -1
- package/src/lib/design/system/complete-command.d.ts +1 -1
- package/src/lib/design/system/complete-command.js +80 -6
- package/src/lib/design/system/complete-command.js.map +1 -1
- package/src/lib/design/system/complete.d.ts +172 -9
- package/src/lib/design/system/complete.js +792 -167
- package/src/lib/design/system/complete.js.map +1 -1
- package/src/lib/design/system/contrast-check.d.ts +6 -1
- package/src/lib/design/system/contrast-check.js +167 -12
- package/src/lib/design/system/contrast-check.js.map +1 -1
- package/src/lib/design/system/drawn-open-points.d.ts +16 -0
- package/src/lib/design/system/drawn-open-points.js +43 -0
- package/src/lib/design/system/drawn-open-points.js.map +1 -0
- package/src/lib/design/system/exceptions.d.ts +59 -0
- package/src/lib/design/system/exceptions.js +132 -0
- package/src/lib/design/system/exceptions.js.map +1 -0
- package/src/lib/design/system/flows/flow.d.ts +3 -0
- package/src/lib/design/system/flows/flow.js +5 -3
- package/src/lib/design/system/flows/flow.js.map +1 -1
- package/src/lib/design/system/handoff.d.ts +11 -0
- package/src/lib/design/system/handoff.js +30 -0
- package/src/lib/design/system/handoff.js.map +1 -0
- package/src/lib/design/system/inventory-approval.d.ts +41 -0
- package/src/lib/design/system/inventory-approval.js +71 -0
- package/src/lib/design/system/inventory-approval.js.map +1 -0
- package/src/lib/design/system/inventory.d.ts +65 -0
- package/src/lib/design/system/inventory.js +168 -0
- package/src/lib/design/system/inventory.js.map +1 -0
- package/src/lib/design/system/list-system.d.ts +4 -1
- package/src/lib/design/system/list-system.js +11 -3
- package/src/lib/design/system/list-system.js.map +1 -1
- package/src/lib/design/system/open-points.d.ts +101 -0
- package/src/lib/design/system/open-points.js +156 -0
- package/src/lib/design/system/open-points.js.map +1 -0
- package/src/lib/design/system/part-ready.d.ts +52 -0
- package/src/lib/design/system/part-ready.js +101 -0
- package/src/lib/design/system/part-ready.js.map +1 -0
- package/src/lib/design/system/read-open-points.d.ts +27 -0
- package/src/lib/design/system/read-open-points.js +81 -0
- package/src/lib/design/system/read-open-points.js.map +1 -0
- package/src/lib/design/system/ready-command.d.ts +7 -0
- package/src/lib/design/system/ready-command.js +65 -0
- package/src/lib/design/system/ready-command.js.map +1 -0
- package/src/lib/design/system/render-check.d.ts +2 -0
- package/src/lib/design/system/render-check.js +216 -32
- package/src/lib/design/system/render-check.js.map +1 -1
- package/src/lib/design/system/resolve-product.d.ts +4 -4
- package/src/lib/design/system/resolve-product.js +22 -7
- package/src/lib/design/system/resolve-product.js.map +1 -1
- package/src/lib/design/system/rules/implements.d.ts +7 -0
- package/src/lib/design/system/rules/implements.js +43 -0
- package/src/lib/design/system/rules/implements.js.map +1 -0
- package/src/lib/design/system/rules/motion.d.ts +2 -0
- package/src/lib/design/system/rules/motion.js +26 -1
- package/src/lib/design/system/rules/motion.js.map +1 -1
- package/src/lib/design/system/rules/option-values.d.ts +15 -0
- package/src/lib/design/system/rules/option-values.js +145 -0
- package/src/lib/design/system/rules/option-values.js.map +1 -0
- package/src/lib/design/system/rules/stale-replace.js +80 -3
- package/src/lib/design/system/rules/stale-replace.js.map +1 -1
- package/src/lib/design/system/rules/token-selectors.js +1 -1
- package/src/lib/design/system/rules/token-selectors.js.map +1 -1
- package/src/lib/design/system/sketch/baseline.d.ts +11 -2
- package/src/lib/design/system/sketch/baseline.js +4 -2
- package/src/lib/design/system/sketch/baseline.js.map +1 -1
- package/src/lib/design/system/sketch/check-sketches.d.ts +1 -0
- package/src/lib/design/system/sketch/check-sketches.js +4 -2
- package/src/lib/design/system/sketch/check-sketches.js.map +1 -1
- package/src/lib/design/system/sketch/host-edit.d.ts +4 -0
- package/src/lib/design/system/sketch/host-edit.js +68 -0
- package/src/lib/design/system/sketch/host-edit.js.map +1 -0
- package/src/lib/design/system/sketch/merge.d.ts +3 -2
- package/src/lib/design/system/sketch/merge.js +79 -12
- package/src/lib/design/system/sketch/merge.js.map +1 -1
- package/src/lib/design/system/sketch/merged-parts.d.ts +11 -1
- package/src/lib/design/system/sketch/merged-parts.js +24 -3
- package/src/lib/design/system/sketch/merged-parts.js.map +1 -1
- package/src/lib/design/system/sketch/no-visible-change.d.ts +6 -0
- package/src/lib/design/system/sketch/no-visible-change.js +25 -1
- package/src/lib/design/system/sketch/no-visible-change.js.map +1 -1
- package/src/lib/design/system/sketch/overlay.js +11 -3
- package/src/lib/design/system/sketch/overlay.js.map +1 -1
- package/src/lib/design/system/sketch/part-definition.d.ts +36 -0
- package/src/lib/design/system/sketch/part-definition.js +56 -2
- package/src/lib/design/system/sketch/part-definition.js.map +1 -1
- package/src/lib/design/system/sketch/sketch.d.ts +47 -0
- package/src/lib/design/system/sketch/sketch.js +137 -44
- package/src/lib/design/system/sketch/sketch.js.map +1 -1
- package/src/lib/design/system/sketch/unchanged-states.d.ts +4 -1
- package/src/lib/design/system/sketch/unchanged-states.js +9 -5
- package/src/lib/design/system/sketch/unchanged-states.js.map +1 -1
- package/src/lib/design/system/sketch/unused-closure.d.ts +12 -0
- package/src/lib/design/system/sketch/unused-closure.js +195 -0
- package/src/lib/design/system/sketch/unused-closure.js.map +1 -0
- package/src/lib/design/system/sketch/unused-files.d.ts +1 -1
- package/src/lib/design/system/sketch/unused-files.js +6 -3
- package/src/lib/design/system/sketch/unused-files.js.map +1 -1
- package/src/lib/design/system/system-modules.js +6 -9
- package/src/lib/design/system/system-modules.js.map +1 -1
- package/src/lib/design/system/system-tsconfig.d.ts +2 -1
- package/src/lib/design/system/system-tsconfig.js +10 -6
- package/src/lib/design/system/system-tsconfig.js.map +1 -1
- package/src/lib/design/system/ts-source.d.ts +3 -0
- package/src/lib/design/system/ts-source.js +21 -4
- package/src/lib/design/system/ts-source.js.map +1 -1
- package/src/lib/design/thumb.d.ts +31 -4
- package/src/lib/design/thumb.js +105 -40
- package/src/lib/design/thumb.js.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,131 @@
|
|
|
1
1
|
# @ethlete/cli
|
|
2
2
|
|
|
3
|
+
## 3.0.0-next.26
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- `et design check` measures text contrast in each colour scheme a product renders (light and dark when its stylesheets use `prefers-color-scheme` or `light-dark()`) and names the scheme in a CONTRAST finding.
|
|
8
|
+
- `et design` takes a product `base` from `node_modules`: `render.packages` defaults to `node_modules` without `dist/libs`, and an installed base gets no generated `tsconfig.json`.
|
|
9
|
+
- The `et design mcp` `check` tool checks a call and its variant, and `et design check --receipt <path>` writes the result to a JSON file.
|
|
10
|
+
- The new `call` tool of `et design mcp` reads a call: its `call.ts`, its `fixture.ts`, the variant file and the frame URL.
|
|
11
|
+
- A design part can name its component with `implements`, and `et design check` prints the open points of each part.
|
|
12
|
+
- A design product can declare the colour schemes it draws with `schemes` in `.ethlete/design/config.json`; both when left out.
|
|
13
|
+
- The design server answers `GET /__design/queue` with its queued and drawing shots, the age of the oldest, and the last shots that failed.
|
|
14
|
+
- A design product can list `generatedFiles` in its config. The part hash skips them, so regenerating a file no longer makes `design check` warn that the part changed outside a sketch.
|
|
15
|
+
- Add `iconSheet` to `@design-system`: a shared icon foundation sheet with a `catalogue` and a `construction` view, drawing each icon on its own grid (from its viewBox or `grid`) with centre lines and the padding box.
|
|
16
|
+
- `design complete` counts an option or a `row` control with `data-active-source="keyboard"` as its focus-visible look, the same as an option named by `aria-activedescendant`.
|
|
17
|
+
- `@design-system` exports `logoSheet({ marks })`, which draws a `foundations/logo` part: each mark at its sizes, with an optional light bar.
|
|
18
|
+
- `et design shot` and `et design motion` take `--scheme <light|dark>` (MCP: `scheme`) to draw a part in that colour scheme.
|
|
19
|
+
- Add `et design sketch note <layer>/<name> <variant> <text> --by <model id>`: it sets the note of one variant in sketch.json and refuses an unknown sketch or variant.
|
|
20
|
+
- `et design check`, `sketch check`, `sketch ready` and `design complete` fail interaction states on a part that declares no control (`CONTROL-STATE`). `design complete` hints a state that changes under 10 % of its frame.
|
|
21
|
+
- `et design check` lists missing motions as open points: every ordered state pair, except focus, and a looping motion in an ongoing-work state. `definePart({ unreachable })` exempts a pair with a reason, checked by the `MOTION-UNREACHABLE-*` rules.
|
|
22
|
+
- The design render check measures non-text contrast: the border of each control (and the frame around an input) and each svg icon must reach 3:1 against what it sits on. Decorative borders give no finding.
|
|
23
|
+
- `et design check` lists two more open points: a component part with no controls declared, and the contrasts the render check found. With `--complete`, it also lists each control that lacks a drawn state or surface.
|
|
24
|
+
- The design server answers `GET /__design/open-points?product=&part=`, the open-point list of one part or of every part; `draw=true` adds the contrasts and missing control states of one part.
|
|
25
|
+
- Design checks report missing mock option values as hints for merged parts and block newly introduced gaps in sketches; use `omits` to record intentional omissions.
|
|
26
|
+
- `et design ready <part>` and `POST /__design/part-ready` mark a part ready in `ready.json`, refused while the part has open points; the mark drops when the part changes.
|
|
27
|
+
- `et design` shot requests take `view` (a number raised per view change) and `rank=active|visible`: the active frame of the shown view draws first, then its visible cells, then the rest, then older views. A hung-up request's queued shot is dropped.
|
|
28
|
+
- `et design sketch ready` refuses while a target in a live variant has open points the sketch can close: no controls declared or a missing motion.
|
|
29
|
+
- The Studio motion timeline makes the motion's start frame live and drives it: the slider pauses its animations at that time, play and speed run them, closing resumes them at 1x.
|
|
30
|
+
- Motion timeline tracks name the foundations duration and easing tokens they use.
|
|
31
|
+
- `et design sketch ready` now runs the render check on the sketch's variants and refuses on any render issue, as `sketch check` reports it. Skipped elements are honoured; without a browser the check is skipped.
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- `et design check` now lists the findings of a base product's parts per rule and says they do not block the product, instead of pointing to `--product <base>`.
|
|
36
|
+
- A design product without its own `tokens` takes the token parts of its base, so frames of a product that extends a base draw under the base's tokens.
|
|
37
|
+
- `et design complete` no longer crashes on an option that is also a `data-et-control` in an open surface.
|
|
38
|
+
- The design contrast check measures text against a `z-index: -1` fill behind it (a positioned sibling or a `::before`/`::after`) instead of the panel under that fill.
|
|
39
|
+
- `et design complete` no longer fails a page on the gaps of a control part it uses from its base product; those gaps stay open points of the base part.
|
|
40
|
+
- `et design check` no longer fails a product on the findings of a part it takes from its base product. It counts them in one line that points to the base product's own check.
|
|
41
|
+
- `et design check` prints one `RENDER CHECK NOT RUN` line when failing checks stop the render, naming the rules and how many problems are in the base.
|
|
42
|
+
- `et design` commands that load design modules (`system --json`, `sketch ready`) no longer share the dependency cache of the running design server, so they stop deleting its optimized deps and blanking every live frame.
|
|
43
|
+
- `et design complete` reports `CONTROL-UNDECLARED` as a hint; the sketch check fails on a control a sketch adds. For a native control the hint says to mark it with `control(markup, name)` and declare the name in `controls`.
|
|
44
|
+
- `et design` listens on `DE_PORT` when it is set, also when Studio did not start it. Before, it took a free port.
|
|
45
|
+
- `design complete` no longer reports the focus container of a composite widget (a `menu`, `listbox`, `tree`, `grid`, `tablist` or similar with `tabindex="-1"` or `aria-activedescendant`) as an undeclared control; its items are still checked.
|
|
46
|
+
- A package rebuild that writes a file back unchanged no longer redraws the design shots that use it.
|
|
47
|
+
- `et design complete` requires hover, focus and active of a menu item but no selected look; a select still requires the selected look.
|
|
48
|
+
- The design server keeps its part shots on disk, so a restart serves them without redrawing until a file they draw from changes.
|
|
49
|
+
- `et design complete` names a `hover:`, `active:` or `focus:` class no stylesheet defines instead of reporting an unchanged box, and the Studio Tailwind theme gains the `surface-pressed` colour.
|
|
50
|
+
- A design frame no longer grows below a scroll container for an absolute element that the container clips, such as the scroll sentinels of an overlay body.
|
|
51
|
+
- A design frame no longer grows to the viewport for a fixed element on all four edges, such as an overlay backdrop.
|
|
52
|
+
- A part frame now grows to cover absolutely or fixed positioned content, so an open overlay panel no longer gets cut off at the trigger's height.
|
|
53
|
+
- Design tool: a forced `focus-visible` now also matches `:focus-within` on the element and its ancestors, so floating labels and focus rings draw as they do in a real focus.
|
|
54
|
+
- A shot of a missing part, state or sketch variant now always answers 404, a queued shot of a removed sketch is dropped, and the render queue no longer lists failures of removed sketches.
|
|
55
|
+
- A part shot is cut from the page with a clip of the root's box instead of an element screenshot, and the thumb queue draws eight shots at once instead of four, so a grid of forty states draws faster.
|
|
56
|
+
- `et design shot --crop changed` also crops the states the merged part does not draw, comparing them with its first state, so a new hover state is no longer cut off.
|
|
57
|
+
- `et design sketch check` and `sketch ready` print one count line per variant for the problems that were there before, instead of repeating each of them per variant.
|
|
58
|
+
- The design server lists sketches about three times faster: each part source is parsed once, and the product's rule problems are found once per list. A run of merges no longer stalls frames and Studio requests for seconds.
|
|
59
|
+
- The sketch check warns of an export in a sketch's `shared/` folder only when no variant imports it. An export that only some variants use no longer warns.
|
|
60
|
+
- The `STALE-REPLACE` design check no longer warns on a `.replace` on a function parameter that a call fills with markup from a package.
|
|
61
|
+
- `et design check` no longer reports an `rgb()` or `rgba()` as a hard-coded colour when every channel comes from `var()`, for example `rgb(var(--shadow-rgb) / 0.25)`. A literal channel is still reported.
|
|
62
|
+
- Draw part and call shots on a transparent page in both colour schemes, as a live frame does.
|
|
63
|
+
- `et design check` reports `CONTROL-PART-INPUT` again: an import cycle left the plain-control selector undefined, so the rule matched no element.
|
|
64
|
+
- The CONTROL-PART-INPUT hint now covers the same plain controls as `design complete`: `summary`, focusable `[tabindex]` and every interactive role.
|
|
65
|
+
- CONTROL-STATE counts an interaction word only as the last word of a state name, drops modifier words from the element name, and asks a component without controls to declare them (Q164).
|
|
66
|
+
- Design commands that run at the same time no longer share one Vite cache folder: the first keeps the shared cache, every other one draws from a folder of its own that it removes when it closes.
|
|
67
|
+
- The design contrast check no longer reports decorative separators, toolbar outlines or hidden svg lines, and measures text on a frame's page fill.
|
|
68
|
+
- The contrast check measures the border of a segmented group: a bordered wrapper counts as a control frame when all its controls together fill at least half of it. Toolbars stay excluded.
|
|
69
|
+
- Design checks report a drawn button, link, input or control role without `data-et-control` as `CONTROL-PART-INPUT`: a hint on merged parts, an error when a sketch adds one.
|
|
70
|
+
- Design checks now list existing interaction states on non-control parts as open points while blocking new or changed states in sketches.
|
|
71
|
+
- Design sketch checks accept unchanged interaction states when unrelated styles change.
|
|
72
|
+
- The design server no longer crashes when a sketch folder disappears while sketches are listed.
|
|
73
|
+
- The design server's parts list names each part's module file, relative to the workspace.
|
|
74
|
+
- The design server no longer pre-bundles its own authoring modules (`@design-system`, `@design-system/kit`, `@design-explore`), so a cold start no longer reloads every frame when Vite finds one of them late.
|
|
75
|
+
- The design server draws a rebuilt `render.packages` folder (such as `dist/libs/components`) without a restart, instead of serving the modules it loaded before the rebuild.
|
|
76
|
+
- Serve a shot from the disk cache after the design server restarts, instead of drawing it again
|
|
77
|
+
- Every design shot response carries an `x-design-shot` header (`disk`, `memory` or `drawn`) that says where the picture came from.
|
|
78
|
+
- `sketch check` no longer prints Angular's development mode line per render, and prints a finding that repeats across states, schemes and variants once, with a count.
|
|
79
|
+
- `et design sketch merge` formats the files it writes with the Prettier of the target repo. A format failure prints a warning and keeps the merge.
|
|
80
|
+
- `et design sketch ready` blocks only on open points a variant adds to the merged part; inherited points are listed as a note. A new part still blocks on all of its points.
|
|
81
|
+
- `et design sketch variant --from` keeps relative symlinks in the copied variant, so its `shared/` links point at the same files as the source variant.
|
|
82
|
+
- `et design` no longer rewrites an unchanged tsconfig, so a `design sketch check` stops redrawing every frame in Studio.
|
|
83
|
+
- A rule in a sketch's `shared/` stylesheet counts as used when any variant of the sketch draws it, so `sketch ready` no longer fails a variant on `UNUSED` for a rule only another variant uses.
|
|
84
|
+
- A design frame asked for a `fill` token its product lacks keeps the product's page colour, so an sdk grid cell in the light column draws on the light surface instead of on Studio's dark canvas.
|
|
85
|
+
- `et design shot <part> --variants` finds a sketch by an extra target, `sketch list` shows extra targets, and new `sketch target add <sketch> <part>` adds a target to an open sketch.
|
|
86
|
+
- `et design sketch report` on a ready sketch moves it back to in progress. It still refuses a decided sketch.
|
|
87
|
+
- STALE-REPLACE no longer flags a replace whose regex is anchored with `^` or `$`; the rule searches the source, not the markup string.
|
|
88
|
+
- A static shot draws in the colour scheme Studio prefers, like the live frames next to it, so a dark Studio no longer mixes light shots into a grid.
|
|
89
|
+
- Studio draws every part grid twice, under a Light and a Dark head, and `frame.html?scheme=` pins a live frame to one scheme.
|
|
90
|
+
- Swap a canvas frame between its shot and its live page without a blank frame: the frame page reports when it painted, and Studio keeps the old view until the new one is ready.
|
|
91
|
+
|
|
92
|
+
## 3.0.0-next.25
|
|
93
|
+
|
|
94
|
+
### Minor Changes
|
|
95
|
+
|
|
96
|
+
- `et design mcp` serves the design tools to an MCP client over stdio: parts, check, shot, motion_shots, the sketch commands and the inbox. `shot` and `motion_shots` return the sheet image; `motion_shots` also returns its JSON.
|
|
97
|
+
- The design server answers `GET /__design/motion` with the keyframes and tracks of one motion mode, and each keyframe as a PNG with `frame=<ms>`. The draw is kept until the part changes.
|
|
98
|
+
- `embedPage` takes `mark` and `css`, both applied inside the embedded page: every element matching `mark` gets an accent outline, and `css` is added as a stylesheet there.
|
|
99
|
+
- `et design check` warns with `EMBED-SCALE` when an element around an `embedPage` frame with a `scale` scales it again, so the page shrinks twice.
|
|
100
|
+
- `et design complete` accepts a `field` control kind for text fields: it needs a drawn hover and focus-visible, but no active.
|
|
101
|
+
- The shot sheet draws an empty "not drawn" cell for each state combo a grid row leaves out, and `sketch check` lists those cells on the grid line.
|
|
102
|
+
- `et design motion flow <flow> <line>` draws a flow line with `"transition": "view"` as a stepped View Transition from the `from` page to the `to` page, shot in the preset viewport. Lines are named by `id` or `from→to`.
|
|
103
|
+
- `et design motion shots` clips each shot to the part and every animated element over all keyframes, so an overshoot is not cut off, and names a track's element by `data-et-key` where it has one.
|
|
104
|
+
- Add `et design motion shots <part> <motion>`: it draws a motion at its keyframes in full and reduced motion into one sheet and writes its tracks as JSON.
|
|
105
|
+
- `spring({ damping, frequency })` from `@design-system` returns a `linear()` easing and its duration for a motion token. A motion's `interrupt` now switches back to its `from` state at that time in `motion shots`.
|
|
106
|
+
- `design sketch` merges a sketch of a base part into the base. `sketch new --override` opens a product override; `--into-base` moves an existing override into the base. `sketch ready` refuses a variant that does not load.
|
|
107
|
+
- Add `design sketch hold <sketch> --until <text>` and `design sketch release <sketch>`: a held sketch goes back to in progress, leaves the merge queue, and `sketch ready` refuses with the `--until` text until it is released.
|
|
108
|
+
- A sketch may carry an edit to a host of its target in a higher layer when the edit only changes the host's imports, so a rename across layers merges in one sketch.
|
|
109
|
+
- `sketch ready` refuses a live variant whose files, or the `shared/` files they import, keep an unused import, parameter or CSS class.
|
|
110
|
+
- Add `tokenSheet` to `@design-system`: it draws a foundations part as a sheet of its CSS custom properties, labelled with the values the drawing resolves, so every product draws its foundations the same way.
|
|
111
|
+
- `design complete` checks the `button`, `select`, `menu`, `row` and `tooltip` control kinds and a declared `disabled` look, and reads a base part's review from its base's `merged.json`.
|
|
112
|
+
- `design complete` fails a page as `INVENTORY-UNAPPROVED` until its control inventory is approved; the design server answers `/__design/inventory` and `/__design/inventory-approve`, which writes `inventory.json` and refuses pages with undeclared or unclassified controls.
|
|
113
|
+
- `design complete` gives each control inside an embedded part's `data-et-part` root to that part, fails the host while that part is incomplete, and fails a host CSS rule restyling it as `CONTROL-OVERRIDDEN`.
|
|
114
|
+
- `et design mcp` gains the tools `sketch_variant`, `sketch_round`, `sketch_list`, `sketch_check`, `sketch_preview`, `sketch_rebase` and `comment_reply`.
|
|
115
|
+
|
|
116
|
+
### Patch Changes
|
|
117
|
+
|
|
118
|
+
- `design complete` accepts an option named by `aria-activedescendant` as focus-visible evidence when it looks different from a plain option in its surface, and its gap says which evidence is missing.
|
|
119
|
+
- `design complete --request <part>.<control>.<requirement> --reason` writes a pending exception that still fails. Studio's approve endpoint records by, at and the part hash; then the gap passes until the part changes.
|
|
120
|
+
- `design complete <layer>/<name> --variant <key>` checks a variant of the part's open sketch, so a sketch proves it closes its gaps before it is merged.
|
|
121
|
+
- `design complete`: a `data-et-control-part` descendant of a `data-et-control` takes the forced interactions of its control, and its `disabled` or `aria-disabled="true"` counts as the control's disabled mark.
|
|
122
|
+
- `et design complete <part> --handoff` gives a design complete part the `handoff` status; `et design complete --handoff` re-checks every `handoff` part and exits 1 when one regressed, for CI.
|
|
123
|
+
- `design` renders no longer fail with "exports is not defined": the CLI build again ships the frame runtime as source only, with no CommonJS copies next to it.
|
|
124
|
+
- `STALE-REPLACE` no longer warns on a `.replace` of markup a package draws, such as `button(options)` from `@ethlete/components`, which the rule cannot read.
|
|
125
|
+
- `tokenSheet` styles now carry static fallbacks for every custom property, so design products without the Studio tokens draw a sane sheet.
|
|
126
|
+
- `tokenSheet` takes `scope`, classes on the sheet root where values are read, so theme-scoped tokens resolve, and `note`, markup drawn under the title.
|
|
127
|
+
- A design frame retries a failed part import twice after a short back-off, so a part file that changes while its grid loads no longer leaves "Failed to fetch dynamically imported module" in the frame.
|
|
128
|
+
|
|
3
129
|
## 3.0.0-next.24
|
|
4
130
|
|
|
5
131
|
### Minor Changes
|
package/design/define-call.ts
CHANGED
|
@@ -245,6 +245,75 @@ export const placeOverlays = (root: HTMLElement) => {
|
|
|
245
245
|
}
|
|
246
246
|
};
|
|
247
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Grows `root` to cover every absolutely or fixed positioned element inside it, such as an open
|
|
250
|
+
* overlay pane, so a frame measured or shot from `root` shows them. Skips a fixed element on all four
|
|
251
|
+
* edges of the viewport, such as a backdrop, and counts an absolute element only as far as the
|
|
252
|
+
* overflow boxes that clip it. Grows sideways too with `sideways`. Safe to run again.
|
|
253
|
+
*/
|
|
254
|
+
export const fitOutOfFlow = (root: HTMLElement, options: { sideways: boolean }) => {
|
|
255
|
+
const view = root.ownerDocument.defaultView;
|
|
256
|
+
|
|
257
|
+
if (!view) return;
|
|
258
|
+
|
|
259
|
+
const box = root.getBoundingClientRect();
|
|
260
|
+
const style = view.getComputedStyle(root);
|
|
261
|
+
const fillsViewport = (style: CSSStyleDeclaration) =>
|
|
262
|
+
style.position === 'fixed' &&
|
|
263
|
+
[style.top, style.right, style.bottom, style.left].every((edge) => parseFloat(edge) === 0);
|
|
264
|
+
const clips = (style: CSSStyleDeclaration) =>
|
|
265
|
+
[style.overflow, style.overflowX, style.overflowY].some((overflow) => overflow && overflow !== 'visible');
|
|
266
|
+
const holdsAbsolute = (style: CSSStyleDeclaration) =>
|
|
267
|
+
!['static', ''].includes(style.position) || !['none', ''].includes(style.transform);
|
|
268
|
+
const clipped = (element: Element) => {
|
|
269
|
+
let { top, right, bottom, left } = element.getBoundingClientRect();
|
|
270
|
+
let escaping = true;
|
|
271
|
+
|
|
272
|
+
for (let ancestor = element.parentElement; ancestor && ancestor !== root; ancestor = ancestor.parentElement) {
|
|
273
|
+
const style = view.getComputedStyle(ancestor);
|
|
274
|
+
|
|
275
|
+
if (escaping && holdsAbsolute(style)) escaping = false;
|
|
276
|
+
if (!escaping && clips(style)) {
|
|
277
|
+
const box = ancestor.getBoundingClientRect();
|
|
278
|
+
|
|
279
|
+
top = Math.max(top, box.top);
|
|
280
|
+
right = Math.min(right, box.right);
|
|
281
|
+
bottom = Math.min(bottom, box.bottom);
|
|
282
|
+
left = Math.max(left, box.left);
|
|
283
|
+
}
|
|
284
|
+
if (style.position === 'fixed') break;
|
|
285
|
+
if (style.position === 'absolute') escaping = true;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return { top, right, bottom, left, width: right - left, height: bottom - top };
|
|
289
|
+
};
|
|
290
|
+
const outOfFlow = [...root.querySelectorAll('*')]
|
|
291
|
+
.map((element) => ({ element, style: view.getComputedStyle(element) }))
|
|
292
|
+
.filter(({ style }) => ['absolute', 'fixed'].includes(style.position) && !fillsViewport(style))
|
|
293
|
+
.map(({ element, style }) => (style.position === 'fixed' ? element.getBoundingClientRect() : clipped(element)))
|
|
294
|
+
.filter((rect) => rect.width > 0 && rect.height > 0);
|
|
295
|
+
const extentOf = ({ side, start, paddings }: { side: 'bottom' | 'right'; start: number; paddings: string[] }) => {
|
|
296
|
+
const extent = Math.max(0, ...outOfFlow.map((rect) => Math.ceil(rect[side] - start)));
|
|
297
|
+
const [before, after] = paddings.map((padding) => parseFloat(padding) || 0) as [number, number];
|
|
298
|
+
|
|
299
|
+
if (extent === 0) return '';
|
|
300
|
+
|
|
301
|
+
return `${style.boxSizing === 'border-box' ? extent + after : extent - before}px`;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
root.style.minHeight = extentOf({
|
|
305
|
+
side: 'bottom',
|
|
306
|
+
start: box.top,
|
|
307
|
+
paddings: [style.paddingTop, style.paddingBottom],
|
|
308
|
+
});
|
|
309
|
+
if (options.sideways)
|
|
310
|
+
root.style.minWidth = extentOf({
|
|
311
|
+
side: 'right',
|
|
312
|
+
start: box.left,
|
|
313
|
+
paddings: [style.paddingLeft, style.paddingRight],
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
|
|
248
317
|
/** Types a drawing where it is written, so a mistake reads in its own file and not in `call.ts`. */
|
|
249
318
|
export const drawing = (drawn: Drawing): Drawing => drawn;
|
|
250
319
|
|
package/design/define-part.ts
CHANGED
|
@@ -6,10 +6,18 @@ export { css, drawing, html, ng } from './define-call';
|
|
|
6
6
|
export type { Drawing } from './define-call';
|
|
7
7
|
export { embedPage } from './embed-page';
|
|
8
8
|
export type { EmbedPageOptions } from './embed-page';
|
|
9
|
+
export { iconSheet } from './icon-sheet';
|
|
10
|
+
export type { IconSheetIcon, IconSheetOptions } from './icon-sheet';
|
|
9
11
|
export { interactionRows } from './interaction-rows';
|
|
10
12
|
export type { InteractionRowsOptions } from './interaction-rows';
|
|
13
|
+
export { logoSheet } from './logo-sheet';
|
|
14
|
+
export type { LogoSheetMark, LogoSheetOptions } from './logo-sheet';
|
|
15
|
+
export { spring } from './spring';
|
|
16
|
+
export type { Spring } from './spring';
|
|
11
17
|
export { svgAsset } from './svg-asset';
|
|
12
18
|
export type { SvgAssetOptions } from './svg-asset';
|
|
19
|
+
export { tokenSheet } from './token-sheet';
|
|
20
|
+
export type { TokenSheetOptions } from './token-sheet';
|
|
13
21
|
|
|
14
22
|
/** An interaction Studio can force on a part's root element. Rest is always drawn and never listed. */
|
|
15
23
|
export type Interaction = 'hover' | 'focus-visible' | 'active' | 'disabled';
|
|
@@ -27,10 +35,13 @@ export type Interaction = 'hover' | 'focus-visible' | 'active' | 'disabled';
|
|
|
27
35
|
* `controls` names each control the part draws, keyed by the id its markup carries in `data-et-control`; `design
|
|
28
36
|
* complete` requires the states and surfaces its `kind` and `input` need, see `Control`.
|
|
29
37
|
* `motions` names each motion between two of its states, see `Motion`; a rule in `rules` can name one.
|
|
38
|
+
* `unreachable` lists the state pairs no user can move between, each with its `reason`; they need no motion.
|
|
39
|
+
* `omits` records mock option values deliberately left off the grid, each with a reason.
|
|
30
40
|
* `checks.skip` names the render checks of `design check` the part breaks on purpose, such as
|
|
31
41
|
* `CLIPPED` for a part that cuts its content. To skip a check for one element only, put
|
|
32
42
|
* `data-et-skip="CLIPPED"` on it; the check then ignores that element and everything inside it.
|
|
33
43
|
* `contrast` feeds the contrast check of `design check`, see `PartContrast`.
|
|
44
|
+
* `implements` links the part to the code that builds it, see `PartLink`.
|
|
34
45
|
*/
|
|
35
46
|
export type Part<TProps, TState extends string = string> = {
|
|
36
47
|
note: string;
|
|
@@ -44,10 +55,19 @@ export type Part<TProps, TState extends string = string> = {
|
|
|
44
55
|
checks?: { skip?: RenderCheck[] };
|
|
45
56
|
contrast?: PartContrast;
|
|
46
57
|
motions?: Record<string, Motion<TState>>;
|
|
58
|
+
unreachable?: { from: TState; to: TState; reason: string }[];
|
|
59
|
+
omits?: { option: keyof TProps & string; value: string; reason: string }[];
|
|
47
60
|
rules?: PartRule<TState>[];
|
|
61
|
+
implements?: PartLink;
|
|
48
62
|
render: (props: TProps, options?: RenderOptions) => DrawnPart | Promise<DrawnPart>;
|
|
49
63
|
};
|
|
50
64
|
|
|
65
|
+
/**
|
|
66
|
+
* The code a part is built in: a component or element names the repo-relative path of its `.component.ts`, a page
|
|
67
|
+
* names its route. `design check` fails on a path that does not exist, and lists a part without a link as open.
|
|
68
|
+
*/
|
|
69
|
+
export type PartLink = `${string}.component.ts` | { route: `/${string}` };
|
|
70
|
+
|
|
51
71
|
/** What `play` of a motion may use, so Studio controls time: `at` schedules `fn` at `ms` into the motion on the tool's clock, `random` is seeded, `rect` measures a selector in the `from` state. */
|
|
52
72
|
export type MotionContext = {
|
|
53
73
|
at: (ms: number, fn: () => void) => void;
|
|
@@ -56,7 +76,7 @@ export type MotionContext = {
|
|
|
56
76
|
};
|
|
57
77
|
|
|
58
78
|
/**
|
|
59
|
-
* A motion from the state `from` to the state `to
|
|
79
|
+
* A motion from the state `from` to the state `to`; with `from` equal to `to` it is the looping motion inside that state. `at` adds shot times in ms, `interrupt` starts the reverse motion
|
|
60
80
|
* at that time, `viewTransition` uses a View Transition, and `play` runs after the morph for what CSS cannot express.
|
|
61
81
|
*/
|
|
62
82
|
export type Motion<TState extends string = string> = {
|
|
@@ -83,21 +103,35 @@ export type StateGrid<TState extends string = string, TColumn extends string = s
|
|
|
83
103
|
rows: Record<string, Partial<Record<TColumn, TState>>>;
|
|
84
104
|
};
|
|
85
105
|
|
|
86
|
-
/** How a control is operated: `pointer` requires a drawn hover, `keyboard` a drawn focus-visible. */
|
|
106
|
+
/** How a control is operated: `pointer` requires a drawn hover and active (a `field` needs no active), `keyboard` a drawn focus-visible. */
|
|
87
107
|
export type ControlInput = 'pointer' | 'keyboard';
|
|
88
108
|
|
|
89
109
|
/** A component or element part that draws a control and its interactions. */
|
|
90
110
|
export type ControlPart = `components/${string}` | `elements/${string}`;
|
|
91
111
|
|
|
92
112
|
/**
|
|
93
|
-
*
|
|
94
|
-
* `
|
|
95
|
-
*
|
|
96
|
-
*
|
|
113
|
+
* What a control of each `kind` needs besides the interactions of its `input`. A surface is the element marked
|
|
114
|
+
* `data-et-surface="<id>.<surface>"`, drawn visible and filled in the state its `surfaces` entry names.
|
|
115
|
+
* - `disclosure`: a state that draws it collapsed, and `surfaces.body`.
|
|
116
|
+
* - `button`: `active`.
|
|
117
|
+
* - `field`: a text field (`<input>` or `<textarea>`); nothing beyond its inputs.
|
|
118
|
+
* - `select`, `menu`: `surfaces.open`, and options in it (`role="option"`, `role="menuitem"` or `data-et-option`)
|
|
119
|
+
* drawn hovered, focus-visible, and selected with a look of their own.
|
|
120
|
+
* - `row`: drawn selected (`aria-selected`, `aria-checked`, `aria-pressed` or `aria-current`) with a look of its own,
|
|
121
|
+
* and hovered while selected.
|
|
122
|
+
* - `tooltip`: `surfaces.tooltip`, the visible tooltip next to the control in this part.
|
|
123
|
+
*
|
|
124
|
+
* `disabled: true` also requires a state that draws it disabled with a look of its own. With `part`, the
|
|
125
|
+
* interactions, selected and disabled looks and option states come from that part, which must be design complete
|
|
126
|
+
* itself; the surfaces stay with this part. A page control needs `part`. With `input`, a state of this part draws
|
|
127
|
+
* each requirement, e.g. with `forceInteraction`.
|
|
97
128
|
*/
|
|
98
|
-
export type Control<TState extends string = string> =
|
|
99
|
-
{
|
|
100
|
-
|
|
129
|
+
export type Control<TState extends string = string> = (
|
|
130
|
+
| { kind: 'disclosure'; surfaces: { body: TState } }
|
|
131
|
+
| { kind: 'button' | 'row' | 'field'; surfaces?: never }
|
|
132
|
+
| { kind: 'select' | 'menu'; surfaces: { open: TState } }
|
|
133
|
+
| { kind: 'tooltip'; surfaces: { tooltip: TState } }
|
|
134
|
+
) & { disabled?: boolean } & ({ part: ControlPart; input?: never } | { input: ControlInput[]; part?: never });
|
|
101
135
|
|
|
102
136
|
/** A rule of the render check: content larger than its frame, cut off, wrapped onto a second line, or drawn at zero size. */
|
|
103
137
|
export type RenderCheck = 'OVERFLOW' | 'CLIPPED' | 'WRAP' | 'ZERO-SIZE';
|
package/design/embed-page.ts
CHANGED
|
@@ -2,9 +2,20 @@ import { Drawing } from './define-call';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* How a page embeds another: the `state` to draw, the viewport `width` and `height` it is drawn at (`'auto'` grows
|
|
5
|
-
* the frame to the page's content), and the `scale` the frame is shown at.
|
|
5
|
+
* the frame to the page's content), and the `scale` the frame is shown at. Inside the embedded page, `mark` rings
|
|
6
|
+
* every element matching its selector (each gets `data-et-embed-marked`), and `css` is added as a stylesheet.
|
|
6
7
|
*/
|
|
7
|
-
export type EmbedPageOptions = {
|
|
8
|
+
export type EmbedPageOptions = {
|
|
9
|
+
state?: string;
|
|
10
|
+
width?: number;
|
|
11
|
+
height?: number | 'auto';
|
|
12
|
+
scale?: number;
|
|
13
|
+
mark?: string;
|
|
14
|
+
css?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const MARK_CSS =
|
|
18
|
+
'[data-et-embed-marked] { outline: 0.2rem solid var(--et-theme-color-primary-solid, Highlight); outline-offset: 0.2rem; }';
|
|
8
19
|
|
|
9
20
|
const KEPT = ['product', 'sketch', 'variant', 'surface', 'color'];
|
|
10
21
|
|
|
@@ -33,11 +44,12 @@ const attribute = (text: string) => text.replace(/&/g, '&').replace(/"/g, '&
|
|
|
33
44
|
* Put the answer's `body` into the page's markup; it needs no styles or setup.
|
|
34
45
|
*/
|
|
35
46
|
export const embedPage = (page: `pages/${string}`, options: EmbedPageOptions = {}): Drawing => {
|
|
36
|
-
const { state = 'default', width = 1280, height = 800, scale = 1 } = options;
|
|
47
|
+
const { state = 'default', width = 1280, height = 800, scale = 1, mark, css } = options;
|
|
37
48
|
const search = embedSearchOf({ page, state, current: globalThis.location?.search ?? '' });
|
|
38
49
|
const auto = height === 'auto';
|
|
39
50
|
const frameHeight = auto ? 0 : height;
|
|
40
|
-
const
|
|
51
|
+
const inside = `${mark ? ` data-et-embed-mark="${attribute(mark)}"` : ''}${css ? ` data-et-embed-css="${attribute(css)}"` : ''}`;
|
|
52
|
+
const frame = `<iframe data-et-embed="${attribute(page)}" data-et-embed-scale="${scale}"${auto ? ' data-et-embed-height="auto"' : ''}${inside} title="${attribute(page)}" src="/frame.html?${attribute(search)}" style="display:block;border:0;width:${width}px;height:${frameHeight}px;transform:scale(${scale});transform-origin:0 0"></iframe>`;
|
|
41
53
|
|
|
42
54
|
return {
|
|
43
55
|
body:
|
|
@@ -68,3 +80,24 @@ export const fitEmbed = (embed: Element | null, height: number) => {
|
|
|
68
80
|
|
|
69
81
|
frame.dataset['etEmbedDrawn'] = '';
|
|
70
82
|
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Called by an embedded frame after it draws and on each change of its content: adds the `css` of its `embedPage`
|
|
86
|
+
* once and marks every element in `root` that matches its `mark`. Does nothing in a frame no page embeds.
|
|
87
|
+
*/
|
|
88
|
+
export const applyEmbed = (embed: Element | null, root: HTMLElement) => {
|
|
89
|
+
if (!(embed as HTMLElement | null)?.hasAttribute('data-et-embed')) return;
|
|
90
|
+
|
|
91
|
+
const { etEmbedMark: mark, etEmbedCss: css } = (embed as HTMLElement).dataset;
|
|
92
|
+
const document = root.ownerDocument;
|
|
93
|
+
|
|
94
|
+
if ((mark || css) && !document.querySelector('style[data-et-embed-css]')) {
|
|
95
|
+
const style = document.createElement('style');
|
|
96
|
+
|
|
97
|
+
style.dataset['etEmbedCss'] = '';
|
|
98
|
+
style.textContent = `${mark ? MARK_CSS : ''}\n${css ?? ''}`;
|
|
99
|
+
document.head.append(style);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (mark) for (const element of root.querySelectorAll(mark)) element.setAttribute('data-et-embed-marked', '');
|
|
103
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { Drawing, html } from './define-call';
|
|
2
|
+
import { svgAsset } from './svg-asset';
|
|
3
|
+
|
|
4
|
+
export type IconSheetIcon = {
|
|
5
|
+
name: string;
|
|
6
|
+
svg: string;
|
|
7
|
+
grid?: number;
|
|
8
|
+
padding?: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type IconSheetOptions = {
|
|
12
|
+
view: 'catalogue' | 'construction';
|
|
13
|
+
title: string;
|
|
14
|
+
note?: string;
|
|
15
|
+
icons: IconSheetIcon[];
|
|
16
|
+
sizes?: number[];
|
|
17
|
+
size?: number;
|
|
18
|
+
columns?: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const SHEET_STYLES = `
|
|
22
|
+
.ics-root { display: flex; flex-direction: column; gap: var(--space-4, 1.6rem); padding: var(--space-6, 2.4rem); background: var(--surface, oklch(0.17 0 0)); color: var(--text, oklch(0.94 0 0)); font-family: var(--font-ui, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif); font-size: var(--text-md, 1.3rem); line-height: var(--text-md-line, 1.8rem); }
|
|
23
|
+
.ics-root svg { display: block; }
|
|
24
|
+
.ics-title { margin: 0; font-size: var(--text-xl, 1.7rem); line-height: var(--text-xl-line, 2.2rem); font-weight: var(--weight-strong, 600); }
|
|
25
|
+
.ics-note { margin: 0; max-width: 64rem; color: var(--text-muted, oklch(0.72 0 0)); }
|
|
26
|
+
.ics-group { margin: 0; font-size: var(--text-lg, 1.5rem); line-height: var(--text-lg-line, 2rem); font-weight: var(--weight-strong, 600); }
|
|
27
|
+
.ics-muted { color: var(--text-muted, oklch(0.72 0 0)); }
|
|
28
|
+
.ics-name { justify-self: start; font-family: var(--font-code, ui-monospace, "SF Mono", Menlo, Consolas, monospace); color: var(--text-muted, oklch(0.72 0 0)); }
|
|
29
|
+
.ics-catalogue { display: grid; align-items: center; justify-items: center; column-gap: var(--space-4, 1.6rem); row-gap: var(--space-2, 0.8rem); }
|
|
30
|
+
.ics-size { font-size: var(--text-sm, 1.15rem); }
|
|
31
|
+
.ics-cell { display: flex; }
|
|
32
|
+
.ics-construction { display: grid; gap: var(--space-5, 2rem); }
|
|
33
|
+
.ics-item { display: flex; flex-direction: column; align-items: center; gap: var(--space-2, 0.8rem); }
|
|
34
|
+
.ics-frame { position: relative; display: flex; border: 1px solid var(--surface-border, oklch(0.34 0 0)); background: var(--surface, oklch(0.17 0 0)); }
|
|
35
|
+
.ics-icon { position: relative; }
|
|
36
|
+
.ics-guides { position: absolute; inset: 0; width: 100%; height: 100%; color: var(--accent, oklch(0.7 0.15 250)); pointer-events: none; }
|
|
37
|
+
.ics-guides path, .ics-guides rect { vector-effect: non-scaling-stroke; stroke-width: 1px; }
|
|
38
|
+
.ics-pixels { opacity: 0.18; }
|
|
39
|
+
.ics-key { opacity: 0.7; stroke-dasharray: 4 3; }
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const VIEW_BOX = /\bviewBox\s*=\s*["']\s*[-\d.]+[\s,]+[-\d.]+[\s,]+([\d.]+)[\s,]+([\d.]+)/u;
|
|
43
|
+
|
|
44
|
+
const gridOf = (icon: IconSheetIcon) => {
|
|
45
|
+
if (icon.grid) return icon.grid;
|
|
46
|
+
const [, width, height] = VIEW_BOX.exec(icon.svg) ?? [];
|
|
47
|
+
if (!width || !height) throw new Error(`iconSheet: "${icon.name}" has no viewBox, so it needs a grid`);
|
|
48
|
+
|
|
49
|
+
return Math.max(Number(width), Number(height));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const groupsOf = (icons: IconSheetIcon[]) => {
|
|
53
|
+
const groups = new Map<number, IconSheetIcon[]>();
|
|
54
|
+
for (const icon of icons) groups.set(gridOf(icon), [...(groups.get(gridOf(icon)) ?? []), icon]);
|
|
55
|
+
|
|
56
|
+
return [...groups];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const gridLabel = (grid: number) => (grid <= 48 ? `${grid} px grid` : `${grid}-unit grid`);
|
|
60
|
+
|
|
61
|
+
const guides = (grid: number, { cls, paths }: { cls: string; paths: string }) =>
|
|
62
|
+
`<svg class="ics-guides ${cls}" viewBox="0 0 ${grid} ${grid}" fill="none" stroke="currentColor" aria-hidden="true" data-grid="${grid}">${paths}</svg>`;
|
|
63
|
+
|
|
64
|
+
const pixelLines = (grid: number) => {
|
|
65
|
+
const step = grid > 48 ? grid / 16 : 1;
|
|
66
|
+
const lines = Array.from({ length: Math.round(grid / step) - 1 }, (_, index) => (index + 1) * step);
|
|
67
|
+
|
|
68
|
+
return guides(grid, {
|
|
69
|
+
cls: 'ics-pixels',
|
|
70
|
+
paths: lines.map((at) => `<path d="M${at} 0V${grid}M0 ${at}H${grid}"/>`).join(''),
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const keyLines = (grid: number, padding: number) =>
|
|
75
|
+
guides(grid, {
|
|
76
|
+
cls: 'ics-key',
|
|
77
|
+
paths: `<path d="M${grid / 2} 0V${grid}M0 ${grid / 2}H${grid}"/><rect x="${padding}" y="${padding}" width="${grid - 2 * padding}" height="${grid - 2 * padding}"/>`,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const drawIcon = (icon: IconSheetIcon, { size, cls }: { size: number; cls?: string }) =>
|
|
81
|
+
svgAsset(icon.svg, { size, class: cls, attrs: { 'aria-hidden': 'true' } });
|
|
82
|
+
|
|
83
|
+
const catalogue = (icons: IconSheetIcon[], sizes: number[]) =>
|
|
84
|
+
html`<div
|
|
85
|
+
class="ics-catalogue"
|
|
86
|
+
style="grid-template-columns: max-content repeat(${sizes.length}, minmax(var(--space-7, 3.2rem), max-content))"
|
|
87
|
+
>
|
|
88
|
+
<span></span>
|
|
89
|
+
${sizes.map((size) => html`<span class="ics-size ics-muted">${size} px</span>`).join('')}
|
|
90
|
+
${icons
|
|
91
|
+
.map(
|
|
92
|
+
(icon) =>
|
|
93
|
+
html`<span class="ics-name">${icon.name}</span>${sizes
|
|
94
|
+
.map((size) => html`<span class="ics-cell">${drawIcon(icon, { size })}</span>`)
|
|
95
|
+
.join('')}`,
|
|
96
|
+
)
|
|
97
|
+
.join('')}
|
|
98
|
+
</div>`;
|
|
99
|
+
|
|
100
|
+
const construction = (
|
|
101
|
+
icons: IconSheetIcon[],
|
|
102
|
+
{ grid, size, columns }: { grid: number; size: number; columns: number },
|
|
103
|
+
) =>
|
|
104
|
+
html`<div class="ics-construction" style="grid-template-columns: repeat(${columns}, max-content)">
|
|
105
|
+
${icons
|
|
106
|
+
.map(
|
|
107
|
+
(icon) =>
|
|
108
|
+
html`<div class="ics-item">
|
|
109
|
+
<span class="ics-frame"
|
|
110
|
+
>${pixelLines(grid)}${drawIcon(icon, { size, cls: 'ics-icon' })}${keyLines(grid, icon.padding ?? grid / 8)}</span
|
|
111
|
+
>
|
|
112
|
+
<span class="ics-name">${icon.name}</span>
|
|
113
|
+
</div>`,
|
|
114
|
+
)
|
|
115
|
+
.join('')}
|
|
116
|
+
</div>`;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Draws a foundation sheet of icons. `catalogue` shows every icon at each of `sizes` with its name; `construction` shows
|
|
120
|
+
* each icon `size` px large on its grid, with the grid lines, the centre lines and the padding box. An icon's grid is its
|
|
121
|
+
* `grid`, else the larger side of its viewBox; its padding is `padding` grid units, `grid / 8` by default.
|
|
122
|
+
* Icons on different grids are drawn in one group per grid.
|
|
123
|
+
*/
|
|
124
|
+
export const iconSheet = ({
|
|
125
|
+
view,
|
|
126
|
+
title,
|
|
127
|
+
note,
|
|
128
|
+
icons,
|
|
129
|
+
sizes = [16, 24, 32],
|
|
130
|
+
size = 96,
|
|
131
|
+
columns = 5,
|
|
132
|
+
}: IconSheetOptions): Drawing => {
|
|
133
|
+
const groups = groupsOf(icons);
|
|
134
|
+
const labelled = groups.length > 1;
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
body: html`<div class="ics-root">
|
|
138
|
+
<h2 class="ics-title">${title}</h2>
|
|
139
|
+
${note && html`<p class="ics-note">${note}</p>`}
|
|
140
|
+
${groups
|
|
141
|
+
.map(
|
|
142
|
+
([grid, members]) =>
|
|
143
|
+
html`${labelled && html`<h3 class="ics-group">${gridLabel(grid)}</h3>`}${
|
|
144
|
+
view === 'catalogue' ? catalogue(members, sizes) : construction(members, { grid, size, columns })
|
|
145
|
+
}`,
|
|
146
|
+
)
|
|
147
|
+
.join('')}
|
|
148
|
+
</div>`,
|
|
149
|
+
styles: SHEET_STYLES,
|
|
150
|
+
};
|
|
151
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Drawing, html } from './define-call';
|
|
2
|
+
import { svgAsset } from './svg-asset';
|
|
3
|
+
|
|
4
|
+
export type LogoSheetMark = {
|
|
5
|
+
title: string;
|
|
6
|
+
svg: string;
|
|
7
|
+
sizes: number[];
|
|
8
|
+
onLight?: number[];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type LogoSheetOptions = {
|
|
12
|
+
marks: LogoSheetMark[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const SHEET_STYLES = `
|
|
16
|
+
.lgs-root { display: flex; flex-direction: column; gap: var(--space-4, 1.6rem); padding: var(--space-6, 2.4rem); background: var(--surface, oklch(0.17 0 0)); color: var(--text, oklch(0.94 0 0)); font-family: var(--font-ui, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif); font-size: var(--text-md, 1.3rem); line-height: var(--text-md-line, 1.8rem); }
|
|
17
|
+
.lgs-root svg { display: block; }
|
|
18
|
+
.lgs-title { margin: 0; font-size: var(--text-xl, 1.7rem); line-height: var(--text-xl-line, 2.2rem); font-weight: var(--weight-strong, 600); }
|
|
19
|
+
.lgs-row { display: flex; align-items: flex-end; gap: var(--space-6, 2.4rem); }
|
|
20
|
+
.lgs-item { display: flex; flex-direction: column; align-items: center; gap: var(--space-2, 0.8rem); }
|
|
21
|
+
.lgs-label { font-family: var(--font-code, ui-monospace, "SF Mono", Menlo, Consolas, monospace); font-size: var(--text-xs, 1rem); line-height: var(--text-xs-line, 1.4rem); color: var(--text-muted, oklch(0.72 0 0)); }
|
|
22
|
+
.lgs-plate { display: flex; align-items: center; gap: var(--space-3, 1.2rem); padding: var(--space-2, 0.8rem) var(--space-3, 1.2rem); border-radius: var(--radius-control-md, 0.6rem); background: var(--text, oklch(0.94 0 0)); }
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
const item = (content: string, label: string) =>
|
|
26
|
+
html`<div class="lgs-item">${content}<span class="lgs-label">${label}</span></div>`;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Draws a `foundations/logo` part: one row per mark with its `title`, the mark at each of its `sizes` in px, and, when
|
|
30
|
+
* `onLight` lists sizes, the mark at those sizes on a light bar.
|
|
31
|
+
*/
|
|
32
|
+
export const logoSheet = ({ marks }: LogoSheetOptions): Drawing => ({
|
|
33
|
+
body: html`<div class="lgs-root">
|
|
34
|
+
${marks
|
|
35
|
+
.map(
|
|
36
|
+
({ title, svg, sizes, onLight }) =>
|
|
37
|
+
html`<h2 class="lgs-title">${title}</h2>
|
|
38
|
+
<div class="lgs-row">
|
|
39
|
+
${sizes.map((size) => item(svgAsset(svg, { size }), `${size} px`)).join('')}
|
|
40
|
+
${
|
|
41
|
+
!!onLight?.length &&
|
|
42
|
+
item(
|
|
43
|
+
html`<div class="lgs-plate">${onLight.map((size) => svgAsset(svg, { size })).join('')}</div>`,
|
|
44
|
+
'on a light bar',
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
</div>`,
|
|
48
|
+
)
|
|
49
|
+
.join('')}
|
|
50
|
+
</div>`,
|
|
51
|
+
styles: SHEET_STYLES,
|
|
52
|
+
});
|