@junoput01/junoui 0.7.0 → 0.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,110 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 584359f: **Canvas ink — a halo pair for UI drawn over arbitrary imagery.**
8
+
9
+ junoui's contrast story assumes a controlled surface, `s0` through `s3`: every role colour's ratio is computed against a known background. Content drawn over a photo, a map, a video frame or a camera feed has no known background — in one orthophoto a black shadow and a snowfield are adjacent pixels, so no single ink colour is legible and no contrast ratio can be asserted about one.
10
+
11
+ New `ink.canvas.ink` / `ink.canvas.halo`: a pair that spans the luminance range, applied together. Over a light backing the halo carries the contrast, over a dark one the ink does. Plus `ink.vivid.*` (role hues at raised chroma, since chrome-tuned hues wash out over a saturated backdrop) and `ink.canvas.scrim` (`0.28`, for chrome floating over live content — deliberately distinct from `opacity.scrim` at `0.62`, which suppresses a modal's background and here would grey out the content being annotated).
12
+
13
+ Shipped as CSS you can apply, not only as tokens: `.juno-canvas-ink` (+ `--lg` and the role modifiers), `.juno-canvas-ink__halo` / `__stroke` for vector marks, and `.juno-canvas-scrim` with a reduced-transparency fallback. Every platform target carries the tokens — CSS, SCSS, JS, Swift, Dart, Android and Rust.
14
+
15
+ **The pair is not themed**, on purpose: a satellite image does not get lighter because the user chose light mode.
16
+
17
+ **The halo is pure black because the arithmetic requires it.** The worst backing is a mid grey where both halves are weakest; the sweep floor there is 4.61:1 against a 4.5:1 requirement, and a near-black with a blue cast measures 4.43:1 and fails. The guard sweeps all 256 grey backings rather than sampling the extremes, which are the easy cases.
18
+
19
+ Additive: no existing token or output changes.
20
+
21
+ - 63ddc0e: **Viewport orientation gizmo — compass ring, pitch arc, snap targets.**
22
+
23
+ The orientation widget any 3D or map viewport ships: a ring showing a heading with clickable snap targets, a secondary arc for a second angle (pitch, tilt, elevation) inside a clamped range, and a centre target that resets the view. For CAD and BIM viewers, product configurators, virtual tours, model previews, floor plans, and any map with a tilt.
24
+
25
+ **A ring, not a cube.** An Autodesk-style ViewCube is the wrong shape for anything with a privileged up-vector — a map, a terrain, a site plan — because there is no meaningful front, right or bottom face to click. A ring degrades to that case and generalises to free orbit; a cube does not go the other way.
26
+
27
+ **The app owns the camera.** It writes `--juno-gizmo-heading` and `--juno-gizmo-pitch`; junoui rotates the needle and the hand and never stores an angle.
28
+
29
+ The accessibility contract is why this is upstream rather than app-local: snap targets are real `<button>`s (focusable, activate on Enter _and_ Space, announced as controls, work in a screen reader's forms mode); every mark carries a real name, because `"N"` is a letter and not one; the widget is **one focus stop** with wrapping arrow keys, not eight tab stops for eight compass points; and the bearing is announced in words through a live region, because a rotating needle announces nothing and `"37deg"` is a number the listener has to convert.
30
+
31
+ ```js
32
+ import { enhanceGizmo, orientationLabel, bearingLabel } from 'junoui/gizmo';
33
+ bearingLabel(37); // → "north-east"
34
+ ```
35
+
36
+ **The ring's diameter is derived, not chosen:** `d ≥ tap · (1 / sin(π/N) + 1)`. The tap floor moves from 24px to 44px on a coarse pointer, so a hard-coded diameter ships eight overlapping targets to a phone.
37
+
38
+ - cc1e29d: **Rust token target — `junoui/rust`.**
39
+
40
+ junoui compiles one DTCG source to CSS, SCSS, JS, Android XML, iOS Swift, Flutter Dart and DTCG JSON. Rust was the one mainstream native target missing, so a Rust consumer had to hand-transcribe hex values — which drift silently on the first patch release, with no lint to catch the stale copy. That is the mirrored-constant defect; this removes the reason to commit it.
41
+
42
+ `dist/rust/juno_tokens.rs` (exported as `junoui/rust`) is a dependency-free const module for any native Rust stack — egui, iced, Slint, Bevy, Dioxus desktop, Tauri's Rust side:
43
+
44
+ ```rust
45
+ let bg = STANDARD_DARK.s0.to_f32_array(); // a whole theme, picked at runtime
46
+ let accent = STANDARD_DARK_ACTIVE; // or one role, flat
47
+ let gap = SPACE_16; // f32 px
48
+ let fade = MOTION_DURATION_BASE_MS; // f32 milliseconds
49
+ ```
50
+
51
+ Colors become an `Rgba` struct with `const fn hex()`, `to_f32_array()` and `with_alpha()`; core tokens are bucketed by the **form** of their value rather than by which file they came from — `px` → `f32`, `ms` → `f32` suffixed `_MS`, whole numbers → `i32`, ratios → `f32`, and CSS-authored strings (shadows, font stacks) shipped verbatim as `&str`.
52
+
53
+ Beyond the flat constants the Swift and Dart targets ship, there is a `Palette` struct and one const per palette/mode, because a Rust app picks a theme at runtime and wants a value it can pass around. Its fields and the constants that fill it both come from junoui's role list, so a role cannot reach one and miss the other.
54
+
55
+ `to_f32_array()` is sRGB-encoded, not linear — convert at your boundary if your pipeline wants linear.
56
+
57
+ Additive: no existing output changes.
58
+
59
+ - cd1dd93: **Colour swatch + palette.**
60
+
61
+ `classes.json` 0.7.0 had no class matching _swatch_ or _color_: no way to show a user-chosen colour or let someone pick one. For diagrams, calendars, tag and label systems, chart series colours, annotation tools, theming UIs and kanban boards.
62
+
63
+ **The hard part is not the square.** A swatch shows an arbitrary colour, so its border, its focus ring and its checked indicator all have to stay visible against a colour junoui has never seen. The border is a **pair** of hairlines that do different jobs: the inset one composites over the _swatch_ and edges it against its own fill; the outset one composites over the _panel_ and separates it from the surface. With the swatch's own contrast against that panel, the boundary has three ways to be visible and needs only one.
64
+
65
+ The alphas are measured, not chosen: at 0.45 / 0.35 a mid-grey swatch on a light panel leaves the best of the three at **2.57:1**, under the 3:1 non-text floor. At 0.65 the worst case is 3.39:1. The guard sweeps the swatch colour against both panels and keeps the old values as its control.
66
+
67
+ **Focus rings sit outside the swatch**, so their contrast is against the panel — a known surface. A ring drawn _on_ the swatch has the same unsolvable problem as the border, and a thicker ring does not fix a hue collision.
68
+
69
+ **Colour is never the only signal**, which is junoui's own rule and exactly what a bare swatch violates: every swatch carries an accessible name, the checked state is a **glyph** (with a dark halo, since it sits on the arbitrary colour) _plus_ a ring, and `--none` is a slash rather than a grey — a consumer without it paints unset as mid grey and the user cannot tell _grey_ from _none_.
70
+
71
+ `.juno-palette` is the grid that goes inside an existing `.juno-popover`; the app owns the colour list and the state.
72
+
73
+ - 671db90: **Tree / outliner — nested rows, disclosure, selection, reorder.**
74
+
75
+ `.juno-list` is flat and `.juno-accordion` is single-level. `.juno-tree` nests to arbitrary depth and carries a selection, a count slot, the same trailing-control slot `.juno-list__row` has, and a reorder handle. For layer stacks, file browsers, settings trees, org charts, comment threads and nested navigation.
76
+
77
+ **Zero JS for the visuals.** Indentation comes from the nested `role="group"` lists the ARIA pattern already requires, so depth is structural — no per-row custom property, no level number to keep in sync with `aria-level`. Collapse is `[aria-expanded="false"]` on the item, which the app owns, exactly like `aria-pressed` elsewhere.
78
+
79
+ **Keyboard is not optional and is not CSS.** A tree without arrow-key traversal and a roving tabindex is a list of buttons wearing tree roles, so junoui ships a stateless enhancer at `junoui/tree`:
80
+
81
+ ```js
82
+ import { enhanceTree } from 'junoui/tree';
83
+ const stop = enhanceTree(document.querySelector('.juno-tree'));
84
+ ```
85
+
86
+ It stores nothing — expansion and selection live on the DOM and belong to you. It moves focus and dispatches `juno-tree-toggle` / `juno-tree-select` (bubbling, cancelable, `detail.item`); it does not expand, collapse, select or reorder, because expanding a node may need to load it.
87
+
88
+ **Three states that get conflated are painted separately:** `:hover` is where the pointer is, `aria-current` is which node you are on, `aria-selected` is what the next action applies to. A layer stack has all three at once.
89
+
90
+ **Touch.** The row holds `--juno-size-tap-min`. The caret and handle paint small so a dense tree stays dense and grow only their _hit area_ — a 44px painted caret would swallow its row. The handle is explicit and carries `touch-action: none` on itself alone: long-press-drag is the obvious gesture and the wrong one, because a tree beside a pan surface has to let the pan win.
91
+
92
+ Reorder _logic_ stays yours; junoui ships the affordance, its hit area, and the drop styling (`data-juno-drop="before|after|into"`, `data-juno-dragging`) — where a between-rows drop and an into-a-row drop look different, because they are different operations.
93
+
94
+ ### Patch Changes
95
+
96
+ - 4f466ff: **The touch defaults are generated from one declared set — conformance kit slice 2.**
97
+
98
+ `base.css` carried two hand-maintained `:where()` lists, and both had drifted.
99
+
100
+ **From the classes:** `.juno-seg__option` (the shipped class is `.juno-seg__opt`) and `.juno-list__item` (it is `.juno-list__row`) sat in them. `:where()` matched nothing, the rule still parsed, every other member kept working — so every segmented control and every grouped list row in every consumer kept the ~300ms double-tap delay.
101
+
102
+ **From each other:** the tap-highlight list was a strict subset of the touch-action one, missing `__overflow`, `__opt`, `chip` and `toggle-btn`.
103
+
104
+ **Consumer-visible change:** `.juno-chip`, `.juno-pillbar__overflow`, `.juno-seg__opt` and `.juno-toggle-btn` now have their UA tap-highlight square suppressed on a coarse pointer, like every other tappable primitive. Verified in Chromium on both pointer types. Nothing else moves.
105
+
106
+ `src/css/touch-surfaces.mjs` is now the source of truth; the two rules are emitted from it. Adding a component to the touch defaults is one line and gets both. `touch-action` stays outside the coarse block — a hybrid device reports a fine primary pointer while still taking touch input — and the highlight stays inside it.
107
+
3
108
  ## 0.7.0
4
109
 
5
110
  ### Minor Changes
@@ -9,6 +9,8 @@
9
9
  <dimen name="bp_lg">1024dp</dimen>
10
10
  <dimen name="bp_xl">1280dp</dimen>
11
11
  <dimen name="bp_2xl">1536dp</dimen>
12
+ <dimen name="ink_canvas_halo-width">2dp</dimen>
13
+ <dimen name="ink_canvas_halo-width-lg">3dp</dimen>
12
14
  <dimen name="radius_2">2dp</dimen>
13
15
  <dimen name="radius_3">3dp</dimen>
14
16
  <dimen name="radius_4">4dp</dimen>
package/dist/classes.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.7.0",
2
+ "version": "0.8.0",
3
3
  "all": [
4
4
  "juno--active",
5
5
  "juno--caution",
@@ -57,6 +57,16 @@
57
57
  "juno-btn--dense",
58
58
  "juno-btn--ghost",
59
59
  "juno-btn--sm",
60
+ "juno-canvas-ink",
61
+ "juno-canvas-ink--active",
62
+ "juno-canvas-ink--caution",
63
+ "juno-canvas-ink--lg",
64
+ "juno-canvas-ink--nominal",
65
+ "juno-canvas-ink--target",
66
+ "juno-canvas-ink--warning",
67
+ "juno-canvas-ink__halo",
68
+ "juno-canvas-ink__stroke",
69
+ "juno-canvas-scrim",
60
70
  "juno-card",
61
71
  "juno-card--accent",
62
72
  "juno-card--alert",
@@ -104,6 +114,14 @@
104
114
  "juno-gauge__label",
105
115
  "juno-gauge__value",
106
116
  "juno-gesture-surface",
117
+ "juno-gizmo",
118
+ "juno-gizmo__arc",
119
+ "juno-gizmo__arc-hand",
120
+ "juno-gizmo__center",
121
+ "juno-gizmo__mark",
122
+ "juno-gizmo__needle",
123
+ "juno-gizmo__readout",
124
+ "juno-gizmo__ring",
107
125
  "juno-grid-auto",
108
126
  "juno-grid-auto--tiles",
109
127
  "juno-heading",
@@ -155,6 +173,10 @@
155
173
  "juno-pagination",
156
174
  "juno-pagination__gap",
157
175
  "juno-pagination__item",
176
+ "juno-palette",
177
+ "juno-palette__check",
178
+ "juno-palette__option",
179
+ "juno-palette__option--none",
158
180
  "juno-pan-x",
159
181
  "juno-pan-y",
160
182
  "juno-pillbar",
@@ -231,6 +253,12 @@
231
253
  "juno-stepper__label",
232
254
  "juno-stepper__marker",
233
255
  "juno-stepper__step",
256
+ "juno-swatch",
257
+ "juno-swatch--button",
258
+ "juno-swatch--circle",
259
+ "juno-swatch--lg",
260
+ "juno-swatch--none",
261
+ "juno-swatch--sm",
234
262
  "juno-switch",
235
263
  "juno-switch--sm",
236
264
  "juno-switch__input",
@@ -310,6 +338,16 @@
310
338
  "juno-tooltip__bubble--bottom",
311
339
  "juno-tooltip__bubble--left",
312
340
  "juno-tooltip__bubble--right",
341
+ "juno-tree",
342
+ "juno-tree__caret",
343
+ "juno-tree__count",
344
+ "juno-tree__group",
345
+ "juno-tree__handle",
346
+ "juno-tree__icon",
347
+ "juno-tree__item",
348
+ "juno-tree__label",
349
+ "juno-tree__row",
350
+ "juno-tree__trail",
313
351
  "juno-value"
314
352
  ],
315
353
  "public": [
@@ -364,6 +402,12 @@
364
402
  "juno-btn--dense",
365
403
  "juno-btn--ghost",
366
404
  "juno-btn--sm",
405
+ "juno-canvas-ink",
406
+ "juno-canvas-ink--lg",
407
+ "juno-canvas-ink--warning",
408
+ "juno-canvas-ink__halo",
409
+ "juno-canvas-ink__stroke",
410
+ "juno-canvas-scrim",
367
411
  "juno-card",
368
412
  "juno-card--accent",
369
413
  "juno-card--alert",
@@ -410,6 +454,14 @@
410
454
  "juno-gauge__label",
411
455
  "juno-gauge__value",
412
456
  "juno-gesture-surface",
457
+ "juno-gizmo",
458
+ "juno-gizmo__arc",
459
+ "juno-gizmo__arc-hand",
460
+ "juno-gizmo__center",
461
+ "juno-gizmo__mark",
462
+ "juno-gizmo__needle",
463
+ "juno-gizmo__readout",
464
+ "juno-gizmo__ring",
413
465
  "juno-grid-auto",
414
466
  "juno-grid-auto--tiles",
415
467
  "juno-hide-below-md",
@@ -456,6 +508,10 @@
456
508
  "juno-pagination",
457
509
  "juno-pagination__gap",
458
510
  "juno-pagination__item",
511
+ "juno-palette",
512
+ "juno-palette__check",
513
+ "juno-palette__option",
514
+ "juno-palette__option--none",
459
515
  "juno-pan-x",
460
516
  "juno-pan-y",
461
517
  "juno-pillbar",
@@ -524,6 +580,12 @@
524
580
  "juno-stepper__label",
525
581
  "juno-stepper__marker",
526
582
  "juno-stepper__step",
583
+ "juno-swatch",
584
+ "juno-swatch--button",
585
+ "juno-swatch--circle",
586
+ "juno-swatch--lg",
587
+ "juno-swatch--none",
588
+ "juno-swatch--sm",
527
589
  "juno-switch",
528
590
  "juno-switch--sm",
529
591
  "juno-switch__input",
@@ -589,6 +651,16 @@
589
651
  "juno-tooltip__bubble--bottom",
590
652
  "juno-tooltip__bubble--left",
591
653
  "juno-tooltip__bubble--right",
654
+ "juno-tree",
655
+ "juno-tree__caret",
656
+ "juno-tree__count",
657
+ "juno-tree__group",
658
+ "juno-tree__handle",
659
+ "juno-tree__icon",
660
+ "juno-tree__item",
661
+ "juno-tree__label",
662
+ "juno-tree__row",
663
+ "juno-tree__trail",
592
664
  "juno-value"
593
665
  ],
594
666
  "roles": [
@@ -723,6 +795,26 @@
723
795
  "juno-btn--sm"
724
796
  ]
725
797
  },
798
+ "canvas-ink": {
799
+ "block": "juno-canvas-ink",
800
+ "elements": [
801
+ "juno-canvas-ink__halo",
802
+ "juno-canvas-ink__stroke"
803
+ ],
804
+ "modifiers": [
805
+ "juno-canvas-ink--active",
806
+ "juno-canvas-ink--caution",
807
+ "juno-canvas-ink--lg",
808
+ "juno-canvas-ink--nominal",
809
+ "juno-canvas-ink--target",
810
+ "juno-canvas-ink--warning"
811
+ ]
812
+ },
813
+ "canvas-scrim": {
814
+ "block": "juno-canvas-scrim",
815
+ "elements": [],
816
+ "modifiers": []
817
+ },
726
818
  "card": {
727
819
  "block": "juno-card",
728
820
  "elements": [
@@ -847,6 +939,19 @@
847
939
  "elements": [],
848
940
  "modifiers": []
849
941
  },
942
+ "gizmo": {
943
+ "block": "juno-gizmo",
944
+ "elements": [
945
+ "juno-gizmo__arc",
946
+ "juno-gizmo__arc-hand",
947
+ "juno-gizmo__center",
948
+ "juno-gizmo__mark",
949
+ "juno-gizmo__needle",
950
+ "juno-gizmo__readout",
951
+ "juno-gizmo__ring"
952
+ ],
953
+ "modifiers": []
954
+ },
850
955
  "grid-auto": {
851
956
  "block": "juno-grid-auto",
852
957
  "elements": [],
@@ -978,6 +1083,15 @@
978
1083
  ],
979
1084
  "modifiers": []
980
1085
  },
1086
+ "palette": {
1087
+ "block": "juno-palette",
1088
+ "elements": [
1089
+ "juno-palette__check",
1090
+ "juno-palette__option",
1091
+ "juno-palette__option--none"
1092
+ ],
1093
+ "modifiers": []
1094
+ },
981
1095
  "pan-x": {
982
1096
  "block": "juno-pan-x",
983
1097
  "elements": [],
@@ -1199,6 +1313,17 @@
1199
1313
  "juno-stepper--vertical"
1200
1314
  ]
1201
1315
  },
1316
+ "swatch": {
1317
+ "block": "juno-swatch",
1318
+ "elements": [],
1319
+ "modifiers": [
1320
+ "juno-swatch--button",
1321
+ "juno-swatch--circle",
1322
+ "juno-swatch--lg",
1323
+ "juno-swatch--none",
1324
+ "juno-swatch--sm"
1325
+ ]
1326
+ },
1202
1327
  "switch": {
1203
1328
  "block": "juno-switch",
1204
1329
  "elements": [
@@ -1363,6 +1488,21 @@
1363
1488
  ],
1364
1489
  "modifiers": []
1365
1490
  },
1491
+ "tree": {
1492
+ "block": "juno-tree",
1493
+ "elements": [
1494
+ "juno-tree__caret",
1495
+ "juno-tree__count",
1496
+ "juno-tree__group",
1497
+ "juno-tree__handle",
1498
+ "juno-tree__icon",
1499
+ "juno-tree__item",
1500
+ "juno-tree__label",
1501
+ "juno-tree__row",
1502
+ "juno-tree__trail"
1503
+ ],
1504
+ "modifiers": []
1505
+ },
1366
1506
  "value": {
1367
1507
  "block": "juno-value",
1368
1508
  "elements": [],
@@ -1458,10 +1598,27 @@
1458
1598
  "juno-gauge-size",
1459
1599
  "juno-gauge-value",
1460
1600
  "juno-gauge-width",
1601
+ "juno-gizmo-at",
1602
+ "juno-gizmo-heading",
1603
+ "juno-gizmo-marks",
1604
+ "juno-gizmo-pitch",
1605
+ "juno-gizmo-pitch-max",
1606
+ "juno-gizmo-pitch-min",
1607
+ "juno-gizmo-size",
1461
1608
  "juno-grid-min",
1462
1609
  "juno-icon-loader-ring",
1463
1610
  "juno-icon-loader-ring-width",
1464
1611
  "juno-icon-size",
1612
+ "juno-ink-canvas-halo",
1613
+ "juno-ink-canvas-halo-width",
1614
+ "juno-ink-canvas-halo-width-lg",
1615
+ "juno-ink-canvas-ink",
1616
+ "juno-ink-canvas-scrim",
1617
+ "juno-ink-vivid-active",
1618
+ "juno-ink-vivid-caution",
1619
+ "juno-ink-vivid-nominal",
1620
+ "juno-ink-vivid-target",
1621
+ "juno-ink-vivid-warning",
1465
1622
  "juno-knob-edge",
1466
1623
  "juno-knob-face-hi",
1467
1624
  "juno-knob-face-lo",
@@ -1530,11 +1687,14 @@
1530
1687
  "juno-space-8",
1531
1688
  "juno-space-96",
1532
1689
  "juno-stepper-marker",
1690
+ "juno-swatch-color",
1691
+ "juno-swatch-size",
1533
1692
  "juno-target",
1534
1693
  "juno-thumb-glyph",
1535
1694
  "juno-thumb-ratio",
1536
1695
  "juno-tile-min",
1537
1696
  "juno-touch-action",
1697
+ "juno-tree-indent",
1538
1698
  "juno-warning",
1539
1699
  "juno-z-alert",
1540
1700
  "juno-z-anchored",
@@ -16,6 +16,16 @@
16
16
  --juno-shadow-1: 0 1px 2px rgb(0 0 0 / 0.30);
17
17
  --juno-shadow-2: 0 4px 14px rgb(0 0 0 / 0.35);
18
18
  --juno-shadow-3: 0 12px 32px rgb(0 0 0 / 0.50);
19
+ --juno-ink-canvas-ink: #FFFFFF;
20
+ --juno-ink-canvas-halo: #000000;
21
+ --juno-ink-canvas-halo-width: 2px;
22
+ --juno-ink-canvas-halo-width-lg: 3px;
23
+ --juno-ink-canvas-scrim: 0.28;
24
+ --juno-ink-vivid-nominal: oklch(76% 0.28 148);
25
+ --juno-ink-vivid-active: oklch(76% 0.24 205);
26
+ --juno-ink-vivid-target: oklch(70% 0.30 328);
27
+ --juno-ink-vivid-caution: oklch(82% 0.20 82);
28
+ --juno-ink-vivid-warning: oklch(68% 0.28 25);
19
29
  --juno-motion-duration-instant: 80ms;
20
30
  --juno-motion-duration-quick: 140ms;
21
31
  --juno-motion-duration-base: 200ms;