@axiapps/axi-design 1.35.0 → 1.37.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/dist/axi.css CHANGED
@@ -743,6 +743,49 @@ button.axi-panel--tile:hover,
743
743
  padding: 1px 5px;
744
744
  }
745
745
 
746
+ /* ---------- link ---------- */
747
+ /* The same trap .axi-code was pulled out of, one component later. `.axi-prose
748
+ a` was the language's only word for a link, and it is reachable only by
749
+ adopting a whole typography layer - so a consumer with a "learn more" beside
750
+ a setting, or a docs URL in a modal footer, writes its own. One real consumer
751
+ had fifteen of them, hand-spelled, and a third of those were <button>s
752
+ calling an openExternal bridge rather than anchors at all.
753
+
754
+ Both selectors, one rule, for the reason written at .axi-code: a link in
755
+ rendered markdown and a link in interface copy are the same object, and the
756
+ last time this language spelled one object twice the two copies drifted.
757
+ There is a test that they stay one rule.
758
+
759
+ The underline is stated rather than inherited. An <a href> draws one by
760
+ default and a <button> does not, so leaving it to the user agent is exactly
761
+ how the two spellings would come apart - the declaration is a no-op on the
762
+ anchor and load-bearing on the button. Same for the background, border and
763
+ padding resets: they say nothing about an anchor and are the whole reason a
764
+ button can wear this class.
765
+
766
+ `font-family` and `font-size` inherit, but the weight does not: a link is
767
+ 600 wherever it lands, and `font: inherit` would quietly take that away.
768
+
769
+ The hover is written `:where(:hover)` so it weighs one class and the ink
770
+ layer still lands on top - see rule 6's addendum. A link in a caption that
771
+ says `axi-link axi-ink-dim` must stay dim under the cursor, and the plain
772
+ `:hover` form is what made the danger button read white under one. */
773
+ .axi-link,
774
+ .axi-prose a {
775
+ padding: 0;
776
+ background: none;
777
+ border: 0;
778
+ font-family: inherit;
779
+ font-size: inherit;
780
+ font-weight: 600;
781
+ color: var(--axi-accent);
782
+ text-decoration: underline;
783
+ text-underline-offset: 2px;
784
+ cursor: pointer;
785
+ }
786
+ .axi-link:where(:hover),
787
+ .axi-prose a:where(:hover) { color: var(--axi-text); }
788
+
746
789
  /* ---------- keyboard key ---------- */
747
790
  /* A key on the keyboard, named in the interface: the Esc that closes a
748
791
  palette, the Ctrl K that opens it. Drawn as a small control rather than as a
@@ -2359,9 +2402,68 @@ textarea.axi-input {
2359
2402
  /* The row under the cursor comes forward on the neutral ramp. Not an ink:
2360
2403
  hovering a row is not a status, and forty rows that each flash a colour on
2361
2404
  the way past the one you want is the tinted-everything failure rule 2 is
2362
- about. */
2363
- .axi-table tbody tr:hover :is(td, th) { background: var(--axi-surface-raised); color: var(--axi-text); }
2405
+ about.
2406
+
2407
+ ONE SURFACE, MANY ELEMENTS - the attachment below, and on the sticky head,
2408
+ the pinned column and the prose table's head, which are the four places in
2409
+ this language where that is the shape of the problem. Everywhere else a
2410
+ themed fill lands on a single box: a panel, a rail, a toolbar, one hovered
2411
+ row of a palette. A table's surfaces are ASSEMBLED. A hovered row is every
2412
+ cell in it, a sticky head is a row of `th`, a pinned name column is one cell
2413
+ per row - one strip to the eye and N painting areas to the renderer. A theme
2414
+ is allowed to make any surface a gradient and glass makes all three of them
2415
+ one, so each cell restarts the ramp and the strip arrives as a row of
2416
+ separately lit boxes with a seam at every boundary.
2417
+
2418
+ Fixed is the same idiom as `body`, `.axi-mast` and `.axi-sheet`, for the
2419
+ reason .axi-mast gives at length: an element shows its SHARE of one light
2420
+ rather than its own private copy of it. Give the cells of a strip a shared
2421
+ painting area and they resolve back into the one surface they were always
2422
+ drawing. Note what the fix does NOT depend on: which box ends up shared. A
2423
+ filtered ancestor - any panel under a glass theme, since --axi-surface-filter
2424
+ is a backdrop-filter and establishes a containing block, the trap that
2425
+ token's own comment points at - takes the painting area from the viewport and
2426
+ makes it the panel's. Both are shared by every cell in the band, so both are
2427
+ continuous; the ancestor only decides where the light sits, which is a
2428
+ question with no wrong answer here. What is not available is the obvious
2429
+ alternative of filling the container and leaving the cells transparent: a
2430
+ sticky head has to carry its own fill, because a fill on `thead` stays
2431
+ behind while the cells travel. */
2432
+ .axi-table tbody tr:hover :is(td, th) {
2433
+ background: var(--axi-surface-raised);
2434
+ background-attachment: fixed;
2435
+ color: var(--axi-text);
2436
+ }
2364
2437
  /* The measured value in a row, as opposed to its supporting numbers. */
2438
+ /* A row that is the one currently shown somewhere else -- a detail pane, a
2439
+ chart, a second table. `aria-current`, for the same reason `aria-sort` above
2440
+ carries the sorted column and `.axi-rail__item[aria-current]` carries the
2441
+ chosen place: the attribute a screen reader already needs in order to announce
2442
+ the row is the whole fact, and a class beside it would be a second source of
2443
+ truth for one thing.
2444
+
2445
+ The fill is the float step, not the raised one, and the reason is the hover
2446
+ directly above: hovering a row ALREADY raises it, so a selection drawn at the
2447
+ raised step is indistinguishable from the row under the cursor -- and every
2448
+ consumer who has hand-written this state has hit that and reached for a hue
2449
+ instead, which is rule 2's tinted-everything failure arrived at honestly.
2450
+ Hover is a transient lift and selection is a held one, so selection sits one
2451
+ step beyond it and hovering a selected row must not pull it back down.
2452
+
2453
+ No accent, and no leading edge either. The accent is for one claim per screen
2454
+ and a selected row spends it on something a detail pane usually spends again;
2455
+ an edge would need a border reserved on every cell of every row, which is the
2456
+ grid-of-boxes rule 8 refuses. The attachment is the one-surface-many-elements
2457
+ case, same as the hover. */
2458
+ .axi-table tbody tr[aria-current] :is(td, th) {
2459
+ background: var(--axi-surface-float);
2460
+ background-attachment: fixed;
2461
+ color: var(--axi-text);
2462
+ }
2463
+ .axi-table tbody tr[aria-current]:hover :is(td, th) {
2464
+ background: var(--axi-surface-float);
2465
+ background-attachment: fixed;
2466
+ }
2365
2467
  .axi-table__num { color: var(--axi-text); }
2366
2468
  /* A name cell: an icon, a diamond or a rank beside the label. */
2367
2469
  .axi-table__who { display: flex; align-items: center; gap: 9px; }
@@ -2410,8 +2512,14 @@ textarea.axi-input {
2410
2512
  --axi-surface-float exists: rows travel behind both of these, and a glass
2411
2513
  theme's ordinary surfaces are alpha, so a head at --axi-surface-raised
2412
2514
  would have numbers sliding through it. Opaque is not a look here, it is
2413
- the requirement. */
2515
+ the requirement.
2516
+
2517
+ Opaque and a gradient, under glass, which is why the attachment is here:
2518
+ the head is a row of cells and this is the one-surface-many-elements case
2519
+ the hover rule explains. Nothing about the sticky positioning changes it -
2520
+ a sticky box is still its own painting area. */
2414
2521
  background: var(--axi-surface-float);
2522
+ background-attachment: fixed;
2415
2523
  border-bottom: var(--axi-border-control) solid var(--axi-ink-line);
2416
2524
  }
2417
2525
  /* The first column stays. It holds the row's name, and a name scrolled out of
@@ -2423,7 +2531,10 @@ textarea.axi-input {
2423
2531
  position: sticky;
2424
2532
  left: 0;
2425
2533
  z-index: 1;
2534
+ /* The same strip as the head, turned ninety degrees, so the same attachment:
2535
+ one cell per row means forty painting areas down a single column. */
2426
2536
  background: var(--axi-surface-float);
2537
+ background-attachment: fixed;
2427
2538
  border-right: var(--axi-border-control) solid var(--axi-ink-line);
2428
2539
  }
2429
2540
  /* The corner belongs to both and has to outrank both. */
@@ -2745,10 +2856,9 @@ textarea.axi-input {
2745
2856
 
2746
2857
  .axi-prose p { margin: 0 0 16px; }
2747
2858
  .axi-prose strong { color: var(--axi-text); font-weight: 800; }
2748
- /* Prose is the one place a link should announce itself: a reader scanning an
2749
- article is looking for them, and inheriting the body ink would hide them. */
2750
- .axi-prose a { color: var(--axi-accent); font-weight: 600; text-underline-offset: 2px; }
2751
- .axi-prose a:hover { color: var(--axi-text); }
2859
+ /* A link is drawn beside .axi-code in primitives.css, in one rule with
2860
+ `.axi-link`, because a link in an article and a link in interface copy are
2861
+ the same object. See the comment there. */
2752
2862
 
2753
2863
  .axi-prose ul, .axi-prose ol { margin: 0 0 16px; padding-left: 22px; }
2754
2864
  .axi-prose li { margin: 0 0 7px; }
@@ -2794,9 +2904,16 @@ textarea.axi-input {
2794
2904
  overflow: hidden;
2795
2905
  font-size: 13.5px;
2796
2906
  }
2907
+ /* A markdown table's head is a row of cells filled from a themed surface, so
2908
+ it is the one-surface-many-elements case `.axi-table tbody tr:hover` sets
2909
+ out in data.css, and it takes the same attachment. Nothing here is sticky,
2910
+ which makes no difference: the seam is per-cell repetition of the gradient,
2911
+ not anything to do with scrolling. */
2797
2912
  .axi-prose th {
2798
2913
  text-align: left; padding: 10px 12px;
2799
- background: var(--axi-surface-raised); color: var(--axi-text);
2914
+ background: var(--axi-surface-raised);
2915
+ background-attachment: fixed;
2916
+ color: var(--axi-text);
2800
2917
  font: var(--axi-t-micro); letter-spacing: var(--axi-ls-micro); text-transform: uppercase;
2801
2918
  }
2802
2919
  .axi-prose td { padding: 10px 12px; border-top: var(--axi-border-hairline) solid var(--axi-rule); }
package/docs/RULES.md CHANGED
@@ -703,6 +703,20 @@ whole section exists to prevent.
703
703
  The test to write is not "does `.axi-code` exist". It is "are both spellings in
704
704
  the same rule", because only the second one fails when someone splits them.
705
705
 
706
+ This has now happened twice. `.axi-prose a` was the language's only word for a
707
+ link, and the same consumer had fifteen hand-spelled ones — a third of them
708
+ `<button>`s calling a desktop bridge rather than anchors at all, which is what
709
+ made the drift concrete rather than theoretical: an `<a href>` draws its own
710
+ underline and a `<button>` does not, so the two spellings were not merely
711
+ allowed to come apart, they had already come apart. When you lift a
712
+ layer-scoped style out, look for the declarations the layer was getting for
713
+ free from its element. Those are the ones the new spelling silently loses.
714
+
715
+ Two instances is a pattern, so the check belongs at the top of the list when
716
+ adding anything: grep `src/` for the component's style living behind a layer
717
+ prefix. If it does, it has consumers you cannot see, and they have already
718
+ drawn their own.
719
+
706
720
  ### A refusal holds at every level, not just the one it was written for
707
721
 
708
722
  The rail refuses two accent fills inside itself. That is why
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiapps/axi-design",
3
- "version": "1.35.0",
3
+ "version": "1.37.0",
4
4
  "description": "The design language for the axi suite — flat and outlined, dark, drawn in saturated ink.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/data.css CHANGED
@@ -100,9 +100,68 @@
100
100
  /* The row under the cursor comes forward on the neutral ramp. Not an ink:
101
101
  hovering a row is not a status, and forty rows that each flash a colour on
102
102
  the way past the one you want is the tinted-everything failure rule 2 is
103
- about. */
104
- .axi-table tbody tr:hover :is(td, th) { background: var(--axi-surface-raised); color: var(--axi-text); }
103
+ about.
104
+
105
+ ONE SURFACE, MANY ELEMENTS - the attachment below, and on the sticky head,
106
+ the pinned column and the prose table's head, which are the four places in
107
+ this language where that is the shape of the problem. Everywhere else a
108
+ themed fill lands on a single box: a panel, a rail, a toolbar, one hovered
109
+ row of a palette. A table's surfaces are ASSEMBLED. A hovered row is every
110
+ cell in it, a sticky head is a row of `th`, a pinned name column is one cell
111
+ per row - one strip to the eye and N painting areas to the renderer. A theme
112
+ is allowed to make any surface a gradient and glass makes all three of them
113
+ one, so each cell restarts the ramp and the strip arrives as a row of
114
+ separately lit boxes with a seam at every boundary.
115
+
116
+ Fixed is the same idiom as `body`, `.axi-mast` and `.axi-sheet`, for the
117
+ reason .axi-mast gives at length: an element shows its SHARE of one light
118
+ rather than its own private copy of it. Give the cells of a strip a shared
119
+ painting area and they resolve back into the one surface they were always
120
+ drawing. Note what the fix does NOT depend on: which box ends up shared. A
121
+ filtered ancestor - any panel under a glass theme, since --axi-surface-filter
122
+ is a backdrop-filter and establishes a containing block, the trap that
123
+ token's own comment points at - takes the painting area from the viewport and
124
+ makes it the panel's. Both are shared by every cell in the band, so both are
125
+ continuous; the ancestor only decides where the light sits, which is a
126
+ question with no wrong answer here. What is not available is the obvious
127
+ alternative of filling the container and leaving the cells transparent: a
128
+ sticky head has to carry its own fill, because a fill on `thead` stays
129
+ behind while the cells travel. */
130
+ .axi-table tbody tr:hover :is(td, th) {
131
+ background: var(--axi-surface-raised);
132
+ background-attachment: fixed;
133
+ color: var(--axi-text);
134
+ }
105
135
  /* The measured value in a row, as opposed to its supporting numbers. */
136
+ /* A row that is the one currently shown somewhere else -- a detail pane, a
137
+ chart, a second table. `aria-current`, for the same reason `aria-sort` above
138
+ carries the sorted column and `.axi-rail__item[aria-current]` carries the
139
+ chosen place: the attribute a screen reader already needs in order to announce
140
+ the row is the whole fact, and a class beside it would be a second source of
141
+ truth for one thing.
142
+
143
+ The fill is the float step, not the raised one, and the reason is the hover
144
+ directly above: hovering a row ALREADY raises it, so a selection drawn at the
145
+ raised step is indistinguishable from the row under the cursor -- and every
146
+ consumer who has hand-written this state has hit that and reached for a hue
147
+ instead, which is rule 2's tinted-everything failure arrived at honestly.
148
+ Hover is a transient lift and selection is a held one, so selection sits one
149
+ step beyond it and hovering a selected row must not pull it back down.
150
+
151
+ No accent, and no leading edge either. The accent is for one claim per screen
152
+ and a selected row spends it on something a detail pane usually spends again;
153
+ an edge would need a border reserved on every cell of every row, which is the
154
+ grid-of-boxes rule 8 refuses. The attachment is the one-surface-many-elements
155
+ case, same as the hover. */
156
+ .axi-table tbody tr[aria-current] :is(td, th) {
157
+ background: var(--axi-surface-float);
158
+ background-attachment: fixed;
159
+ color: var(--axi-text);
160
+ }
161
+ .axi-table tbody tr[aria-current]:hover :is(td, th) {
162
+ background: var(--axi-surface-float);
163
+ background-attachment: fixed;
164
+ }
106
165
  .axi-table__num { color: var(--axi-text); }
107
166
  /* A name cell: an icon, a diamond or a rank beside the label. */
108
167
  .axi-table__who { display: flex; align-items: center; gap: 9px; }
@@ -151,8 +210,14 @@
151
210
  --axi-surface-float exists: rows travel behind both of these, and a glass
152
211
  theme's ordinary surfaces are alpha, so a head at --axi-surface-raised
153
212
  would have numbers sliding through it. Opaque is not a look here, it is
154
- the requirement. */
213
+ the requirement.
214
+
215
+ Opaque and a gradient, under glass, which is why the attachment is here:
216
+ the head is a row of cells and this is the one-surface-many-elements case
217
+ the hover rule explains. Nothing about the sticky positioning changes it -
218
+ a sticky box is still its own painting area. */
155
219
  background: var(--axi-surface-float);
220
+ background-attachment: fixed;
156
221
  border-bottom: var(--axi-border-control) solid var(--axi-ink-line);
157
222
  }
158
223
  /* The first column stays. It holds the row's name, and a name scrolled out of
@@ -164,7 +229,10 @@
164
229
  position: sticky;
165
230
  left: 0;
166
231
  z-index: 1;
232
+ /* The same strip as the head, turned ninety degrees, so the same attachment:
233
+ one cell per row means forty painting areas down a single column. */
167
234
  background: var(--axi-surface-float);
235
+ background-attachment: fixed;
168
236
  border-right: var(--axi-border-control) solid var(--axi-ink-line);
169
237
  }
170
238
  /* The corner belongs to both and has to outrank both. */
@@ -450,6 +450,49 @@ button.axi-panel--tile:hover,
450
450
  padding: 1px 5px;
451
451
  }
452
452
 
453
+ /* ---------- link ---------- */
454
+ /* The same trap .axi-code was pulled out of, one component later. `.axi-prose
455
+ a` was the language's only word for a link, and it is reachable only by
456
+ adopting a whole typography layer - so a consumer with a "learn more" beside
457
+ a setting, or a docs URL in a modal footer, writes its own. One real consumer
458
+ had fifteen of them, hand-spelled, and a third of those were <button>s
459
+ calling an openExternal bridge rather than anchors at all.
460
+
461
+ Both selectors, one rule, for the reason written at .axi-code: a link in
462
+ rendered markdown and a link in interface copy are the same object, and the
463
+ last time this language spelled one object twice the two copies drifted.
464
+ There is a test that they stay one rule.
465
+
466
+ The underline is stated rather than inherited. An <a href> draws one by
467
+ default and a <button> does not, so leaving it to the user agent is exactly
468
+ how the two spellings would come apart - the declaration is a no-op on the
469
+ anchor and load-bearing on the button. Same for the background, border and
470
+ padding resets: they say nothing about an anchor and are the whole reason a
471
+ button can wear this class.
472
+
473
+ `font-family` and `font-size` inherit, but the weight does not: a link is
474
+ 600 wherever it lands, and `font: inherit` would quietly take that away.
475
+
476
+ The hover is written `:where(:hover)` so it weighs one class and the ink
477
+ layer still lands on top - see rule 6's addendum. A link in a caption that
478
+ says `axi-link axi-ink-dim` must stay dim under the cursor, and the plain
479
+ `:hover` form is what made the danger button read white under one. */
480
+ .axi-link,
481
+ .axi-prose a {
482
+ padding: 0;
483
+ background: none;
484
+ border: 0;
485
+ font-family: inherit;
486
+ font-size: inherit;
487
+ font-weight: 600;
488
+ color: var(--axi-accent);
489
+ text-decoration: underline;
490
+ text-underline-offset: 2px;
491
+ cursor: pointer;
492
+ }
493
+ .axi-link:where(:hover),
494
+ .axi-prose a:where(:hover) { color: var(--axi-text); }
495
+
453
496
  /* ---------- keyboard key ---------- */
454
497
  /* A key on the keyboard, named in the interface: the Esc that closes a
455
498
  palette, the Ctrl K that opens it. Drawn as a small control rather than as a
package/src/prose.css CHANGED
@@ -28,10 +28,9 @@
28
28
 
29
29
  .axi-prose p { margin: 0 0 16px; }
30
30
  .axi-prose strong { color: var(--axi-text); font-weight: 800; }
31
- /* Prose is the one place a link should announce itself: a reader scanning an
32
- article is looking for them, and inheriting the body ink would hide them. */
33
- .axi-prose a { color: var(--axi-accent); font-weight: 600; text-underline-offset: 2px; }
34
- .axi-prose a:hover { color: var(--axi-text); }
31
+ /* A link is drawn beside .axi-code in primitives.css, in one rule with
32
+ `.axi-link`, because a link in an article and a link in interface copy are
33
+ the same object. See the comment there. */
35
34
 
36
35
  .axi-prose ul, .axi-prose ol { margin: 0 0 16px; padding-left: 22px; }
37
36
  .axi-prose li { margin: 0 0 7px; }
@@ -77,9 +76,16 @@
77
76
  overflow: hidden;
78
77
  font-size: 13.5px;
79
78
  }
79
+ /* A markdown table's head is a row of cells filled from a themed surface, so
80
+ it is the one-surface-many-elements case `.axi-table tbody tr:hover` sets
81
+ out in data.css, and it takes the same attachment. Nothing here is sticky,
82
+ which makes no difference: the seam is per-cell repetition of the gradient,
83
+ not anything to do with scrolling. */
80
84
  .axi-prose th {
81
85
  text-align: left; padding: 10px 12px;
82
- background: var(--axi-surface-raised); color: var(--axi-text);
86
+ background: var(--axi-surface-raised);
87
+ background-attachment: fixed;
88
+ color: var(--axi-text);
83
89
  font: var(--axi-t-micro); letter-spacing: var(--axi-ls-micro); text-transform: uppercase;
84
90
  }
85
91
  .axi-prose td { padding: 10px 12px; border-top: var(--axi-border-hairline) solid var(--axi-rule); }