@quillmark/svelte 0.7.3 → 0.9.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.
@@ -280,14 +280,19 @@
280
280
  --_qm-reveal: 500ms cubic-bezier(0.4, 0, 0.2, 1);
281
281
 
282
282
  /* ── Geometry ──────────────────────────────────────────────────────────*/
283
- /* One stroke width for every edge the chrome draws: a card's, a control's,
284
- and the verticals nested inside it alike. A threshold rather than a
285
- rhythm choice, like the tap floor: a hairline is a hairline at any
286
- `--qm-space`, so it does not derive from a dial. It exists as a rung because
287
- the nesting ladder's whole claim is that a card's edge, a section's vertical
288
- and a subform's are one stroke at three depths, and `check:style`'s
289
- border-width axis can only hold that against a token. */
283
+ /* One stroke width for every edge the chrome draws and every rule it ranks
284
+ with: a card's, a control's, the two closing a card's payload. A threshold
285
+ rather than a rhythm choice, like the tap floor: a hairline is a hairline at
286
+ any `--qm-space`, so neither derives from a dial. Both exist as rungs because
287
+ `check:style`'s border-width axis can only hold a stroke against a token. */
290
288
  --_qm-border-width: 1px;
289
+ /* Half a step over it, for the verticals alone — a section's, a subform's, a
290
+ blockquote's. What a vertical states is depth, and depth is the one thing a
291
+ reader has to find in a dense card; the strokes that close a block or rank two
292
+ stay at the hairline, so the ladder is what carries the extra weight. Half,
293
+ because a whole step reads as a different kind of line rather than the same
294
+ one doing more work. */
295
+ --_qm-vertical-width: 1.5px;
291
296
  --_qm-radius: var(--qm-radius, 8px);
292
297
  /* The interior corner, and half is the relationship rather than the number: a
293
298
  corner inside a corner reads as nested only while it is visibly tighter, and
@@ -324,17 +329,12 @@
324
329
  --_qm-tail: 40vh;
325
330
  /* WCAG 2.5.8's 24x24 pointer-target floor. A rung rather than a sixth step on
326
331
  the space scale: it is a threshold the spec fixes, not a rhythm choice, so
327
- it does not move when `--qm-space` does. Both button families read it, so the
332
+ it does not move when `--qm-space` does. Every button family reads it, so the
328
333
  floor is one number instead of one expression per control. */
329
334
  --_qm-tap-min: 24px;
330
- /* The reorder/delete glyph. A rung because the card header's
331
- right hang is half the difference between this and the tap floor: the glyph
332
- centres in a target sized by the floor, so what stands between it and the
333
- card's gutter is an arithmetic of the two, and a literal on either side is a
334
- balance that silently breaks when the glyph moves. Sized in CSS rather than
335
- through the icon's `size`, so the hang and the glyph read one number. Scoped to
336
- the control pair: the hint and the format marks take their own sizes, and a
337
- rung claiming to be every glyph would be false at three of them. */
335
+ /* The chrome glyph: a chip's, and the in-well remove's. Sized in CSS rather than
336
+ through the icon's `size`, so a stylesheet beats the SVG presentation attribute
337
+ and one rung is the whole of what a control draws. */
338
338
  --_qm-glyph-control: 14px;
339
339
 
340
340
  /* ── Type ────────────────────────────────────────────────────────────────
@@ -5,7 +5,7 @@
5
5
  by `items.type`: `richtext` / `plaintext` → a prose element
6
6
  ({@link ProseValue}), `object` → a summary row that opens onto a subform,
7
7
  everything else → a text input. The
8
- add affordance sits in the label header row (space-between with the field label);
8
+ add chip sits in the label header row (space-between with the field label);
9
9
  {@link Field} skips its own label for array controls and hands this component the
10
10
  label track with it.
11
11
 
@@ -352,7 +352,7 @@
352
352
  {/if}
353
353
  <button
354
354
  type="button"
355
- class="qm-add-el qm-add-affordance qm-tap-floor"
355
+ class="qm-add-el qm-chip qm-focus-ring qm-tap-floor"
356
356
  bind:this={addEl}
357
357
  onclick={add}>{t.strings.arrayAdd}</button
358
358
  >
@@ -442,10 +442,9 @@
442
442
  gap: var(--_qm-space);
443
443
  }
444
444
  /* The array's first line is the row's label line: this component owns the label
445
- track (Field.svelte), so the header has to measure what a `.qm-field-label-row`
446
- measures or an array shares a row with a scalar and their labels sit apart. The
447
- label is the same component; what had to give was the add affordance's height,
448
- below. */
445
+ track (Field.svelte). Arrays never pack (`packable`), so the chip's tap floor is
446
+ free to size the header; a caption on this line would have had to match
447
+ `.qm-field-label-row`. */
449
448
  .qm-array-header {
450
449
  display: flex;
451
450
  align-items: center;
@@ -584,29 +583,22 @@
584
583
  color: var(--_qm-ink-label);
585
584
  font-style: italic;
586
585
  }
587
- /* It rests dim and comes up on hover of the field rather than of itself: one trigger
588
- at the head of a row of controls is found by looking at the array, not by grazing its
589
- first line.
590
-
591
- The box is the label's line box and the target is `.qm-tap-floor`'s (controls.css): a
592
- target-sized box would stand the header 8px taller than the `.qm-field-label-row`
593
- beside it in a shared row and put the two labels on different lines.
594
-
595
- The label rung in place of the family's body rung, which the family invites: it reads
596
- as one more thing on the label line rather than as a control from the row below. */
586
+ /* The label line's own type: size, weight and leading are `.qm-field-label`'s, so
587
+ the two read as one register. Inner radius: the chip family is the card's, and this
588
+ box is a line of type. The chip's tap floor is given back (`.qm-tap-floor`),
589
+ and the padding with it, so the drawn box is the line and the target is the floor.
590
+ It rests dim and comes up under the pointer; empty, it is the way in. */
597
591
  .qm-add-el {
598
592
  padding: 0 var(--_qm-space);
599
- font-size: var(--_qm-text-label);
593
+ font-weight: var(--_qm-weight-mid);
594
+ line-height: var(--_qm-leading-tight);
595
+ border-radius: var(--_qm-radius-inner);
600
596
  opacity: var(--_qm-opacity-idle);
601
597
  }
602
- .qm-array:hover .qm-add-el,
598
+ .qm-add-el:hover,
603
599
  .qm-add-el:focus-visible {
604
600
  opacity: 1;
605
601
  }
606
- /* Empty, the rows box is `display: none` and the field is a label and this trigger:
607
- nothing left for it to compete with, and nothing else in the field to act on. So
608
- it rests full-strength there and takes the recede back once it heads a list. A
609
- receded trigger beside no content reads as a caption rather than as a way in. */
610
602
  .qm-array.empty .qm-add-el {
611
603
  opacity: 1;
612
604
  }
@@ -38,7 +38,7 @@
38
38
 
39
39
  <span class="qm-switch-wrap">
40
40
  <Switch.Root
41
- class="qm-switch qm-focus-ring"
41
+ class="qm-switch qm-focus-ring qm-tap-floor"
42
42
  checked={local.value}
43
43
  {id}
44
44
  aria-label={id ? undefined : label}
@@ -55,6 +55,11 @@
55
55
  <style>
56
56
  /* A primitive renders its own element, which a scoped selector cannot reach:
57
57
  styled through the wrapper with `:global`. */
58
+ /* The track is the drawn box and the target is `.qm-tap-floor`'s (controls.css): a
59
+ switch is a mark standing in a field's row, so its height is the track's, where WCAG
60
+ 2.5.8's floor is half again that. The `<label for>` beside it forwards a press but
61
+ stands at the label rung, so it is no taller: without the floor no region on the
62
+ surface reaches this control at the size every other control on it holds. */
58
63
  .qm-switch-wrap :global(.qm-switch) {
59
64
  display: inline-flex;
60
65
  align-items: center;
@@ -408,33 +408,54 @@
408
408
 
409
409
  <style>
410
410
  /* An island: the card is the base plane, the column behind it the sunken one, and the
411
- hairline closes it (ARCHITECTURE §Styling). The inset is the same rung as the gap, so
412
- what separates the header from the fields is what separates the fields from the
413
- card's edge. The inline half reads `--_qm-nest` at that same number: this edge is the
414
- nesting ladder's first stroke, and its inset the first step. */
411
+ hairline closes it (ARCHITECTURE §Styling). The block half is the same rung as the
412
+ gap: the payload stands that far off the card's edge and off the body below it. The
413
+ header insets itself to the section-heading column (`.qm-card-header`). The inline
414
+ half stands a step tighter than the nesting rung the verticals inside keep: this
415
+ edge closes the block rather than nesting one, and the rules it holds run that much
416
+ further for it. */
415
417
  .qm-card {
416
418
  border: var(--_qm-border-width) solid var(--_qm-border);
417
419
  border-radius: var(--_qm-radius);
418
- padding: var(--_qm-space-3) var(--_qm-nest);
420
+ padding: var(--_qm-space-3) var(--_qm-space-2);
419
421
  background: var(--_qm-surface);
420
422
  display: flex;
421
423
  flex-direction: column;
422
424
  gap: var(--_qm-space-3);
423
425
  }
426
+ /* Headerless, the payload faces that edge with nothing between. A field row
427
+ leading it stands the edge's own inset off the top, as it does off the side:
428
+ this rung plus the section's block padding is the inline inset. A card that
429
+ opens on its body keeps the full inset — prose is neither a heading nor a
430
+ field row. */
431
+ .qm-card.qm-main:has(> .qm-card-body > .qm-card-meta:first-child) {
432
+ padding-top: var(--_qm-space);
433
+ }
434
+ /* A section heading leading it: the vertical starts at the content's
435
+ top-left, so the top inset is the inline one and the tip is a square
436
+ corner of the card. */
437
+ .qm-card.qm-main:has(> .qm-card-body > .qm-card-meta:first-child > .qm-groups:first-child) {
438
+ padding-top: var(--_qm-space-2);
439
+ }
424
440
  .qm-card-header {
425
441
  display: flex;
426
442
  align-items: center;
427
443
  justify-content: space-between;
428
444
  gap: var(--_qm-space-2);
445
+ /* The group's vertical plus the rung the chevron stands off it: the title
446
+ shares that column. Inline only. The block pull is half the card's block
447
+ inset, so the header sits that far off the hairline and the rule. */
448
+ padding-inline: calc(var(--_qm-space) + var(--_qm-vertical-width));
449
+ margin-block: calc(-1 * var(--_qm-space-3) / 2);
429
450
  }
430
- /* The overhang is the tap target's own centring given back, so the glyphs sit on the
431
- card's gutter rather than the targets around them. It is the fill that crosses the
432
- line, not a box: an icon button rests unboxed and fills only under the pointer. */
433
451
  .qm-card-header-right {
434
452
  display: flex;
435
453
  align-items: center;
436
454
  gap: var(--_qm-space-2);
437
- margin-right: calc(-1 * (var(--_qm-tap-min) - var(--_qm-glyph-control)) / 2);
455
+ /* The overhang is the tap target's own centring given back, so the delete
456
+ glyph sits on the header's inset rather than the pad around it. The fill is
457
+ what crosses the line. */
458
+ margin-inline-end: calc(-1 * (var(--_qm-tap-min) - var(--_qm-glyph-control)) / 2);
438
459
  }
439
460
  /* Focus as well as hover, so a caret in any leaf, the title, or a chevron holds the
440
461
  reveal: hover alone would strand the pair on keyboard and touch. */
@@ -445,7 +466,7 @@
445
466
  /* The rename hit region is the header's free width at full height, so a press
446
467
  anywhere left of the controls enters the title edit. `align-self: stretch` beats the
447
468
  header's centring, which is also what keeps the target legal with no padding: WCAG
448
- 2.5.8's floor is the row's, which the controls hold at `--_qm-tap-min`. */
469
+ 2.5.8's floor is the row's, which the chips hold at `--_qm-tap-min`. */
449
470
  .qm-card-rename {
450
471
  flex: 1;
451
472
  min-width: 0;
@@ -504,24 +525,21 @@
504
525
  flex-direction: column;
505
526
  gap: var(--_qm-space-3);
506
527
  }
507
- /* Where two sections meet the distance is three of one rung — the cap closing the one
508
- above, this gap, the cap opening the one below — the same whether either side is a
509
- group or the ungrouped block. That total is what the fields inside a section keep
510
- between their rows (`.qm-fields` row-gap), so a section boundary stands no further
511
- apart than a field boundary: what ranks them is the stroke, not the air. */
512
528
  .qm-card-meta,
513
529
  .qm-groups {
514
530
  display: flex;
515
531
  flex-direction: column;
516
- gap: var(--_qm-space);
517
532
  }
518
- /* One rung in two places: what a rule insets its own edge by, and what a vertical
519
- reaches past the box it is drawn on. `--_qm-nest` is the distance a stroke stands off
520
- the content it holds — the same one the fields keep off the vertical inline — and a
521
- section's box already spends `--_qm-space` of it capping its own ends, so both spend
522
- the difference. That is what squares the corner: the field stands the same rung off
523
- the rule above it as off the vertical beside it. */
533
+ /* One rung between the payload's two kinds of section, and none inside the accordion:
534
+ what stands one disclosure row off the next is the two headers' own block padding and
535
+ nothing else, so the rows tile and their targets with them. A run of one-line rows
536
+ wants the tighter rhythm — the `--_qm-space-3` the fields keep between their own rows
537
+ is calibrated for a label over a well, and reads as a float between two lines of text.
538
+
539
+ `--stroke-reach` is what a box standing a field row off a rule has left to spend: the
540
+ rung is `--_qm-nest`, of which its own block padding is already `--_qm-space`. */
524
541
  .qm-card-meta {
542
+ gap: var(--_qm-space);
525
543
  --stroke-reach: calc(var(--_qm-nest) - var(--_qm-space));
526
544
  }
527
545
  /* The card's two horizontals, and ranking is the one thing a horizontal is for: they
@@ -530,27 +548,35 @@
530
548
  conditional on what it ranks against: `main` is headerless, and a card with no body
531
549
  has nothing under the payload to divide it from.
532
550
 
533
- The inset is the rule's own, so it goes with it: a card drawing one horizontal insets
534
- that end and not the other. The air outside the pair is the `--_qm-space-3` the card
535
- spends between its blocks. */
551
+ Neither insets: a rule is drawn on the edge of the box it faces, and the air is that
552
+ box's own. So the section carries what its ink stands off a rule — the inset of the
553
+ ink facing it, which is what squares the corner: `--_qm-nest` for a field row,
554
+ `--_qm-space` for a heading, each the rung that ink stands off a vertical inline. The
555
+ air outside the pair is the `--_qm-space-3` the card spends between its blocks. */
536
556
  .qm-meta-top {
537
557
  border-top: var(--_qm-border-width) solid var(--_qm-border);
538
- padding-top: var(--stroke-reach);
539
558
  }
540
559
  .qm-meta-bottom {
541
560
  border-bottom: var(--_qm-border-width) solid var(--_qm-border);
542
- padding-bottom: var(--stroke-reach);
543
561
  }
544
- /* A section's share of the boundary between sections: one rung at each end, so two of
545
- them stand three of it apart whichever kind they are (above). The accordion spends the
546
- same rung from its header and its panel, where it also caps the vertical those two
547
- edges hold.
562
+ /* A section's share of the boundary between sections, one rung at each end. The
563
+ accordion spends the same rung from its header and its panel, where it also caps the
564
+ vertical those two edges hold.
548
565
 
549
- No stroke and no inset: fields declaring no `ui.group` are the card's own, at the rank
550
- its body is at, and the card's edge is the only container there is to state. A
551
- vertical here would mark a section a reader has no name for. */
566
+ No stroke: a vertical would mark a section a reader has no name for. The start
567
+ inset is the heading's — the group's vertical plus the rung the chevron stands
568
+ off it — so a top-level field and a section heading share a column. */
552
569
  .qm-section {
553
570
  padding-block: var(--_qm-space);
571
+ padding-inline-start: calc(var(--_qm-space) + var(--_qm-vertical-width));
572
+ }
573
+ /* Where it faces a rule, the rung a heading already stands off one. The fields
574
+ sit on the heading column, not the card's edge. */
575
+ .qm-meta-top > .qm-section:first-child {
576
+ padding-top: var(--_qm-space-2);
577
+ }
578
+ .qm-meta-bottom > .qm-section:last-child {
579
+ padding-bottom: var(--_qm-space-2);
554
580
  }
555
581
  /* Two query containers, one per kind of section, so capacity follows the width the
556
582
  fields actually get: a panel's inset makes that narrower than the card. */
@@ -597,47 +623,72 @@
597
623
  than absent, so a toggle moves no text — and closed, there is nothing under the header
598
624
  for it to gather.
599
625
 
600
- The stroke caps at `--_qm-nest`, at both ends and on every section alike. Its box
601
- already spends `--_qm-space` of that — the header's block padding above, the panel's
602
- below, the rung the ungrouped section spends on its own two ends — so the rest is
603
- grown here and handed straight back as margin: the stroke lengthens, and a section
604
- boundary keeps the three rungs it stood at. What the stroke reaches into is the air of
605
- that boundary, or, where the section is the payload's first or last, exactly the inset
606
- its rule took — so it lands on the rule at a corner with nothing drawn longer for it.
626
+ A free end of the stroke caps at `--_qm-space`, the rung the subform's own vertical
627
+ caps at (`ObjectField`), grown as padding and handed straight back as margin so the
628
+ stroke lengthens and no boundary moves. Above, that is also the rung the header stands
629
+ off it, so the top end is the same number whether it caps free or lands on a rule.
607
630
  One section open at a time, so no two strokes are ever visible to meet in between. */
608
631
  .qm-group {
609
632
  display: flex;
610
633
  flex-direction: column;
611
- border-left: var(--_qm-border-width) solid transparent;
612
- padding-block: var(--stroke-reach);
613
- margin-block: calc(-1 * var(--stroke-reach));
614
- transition: border-color var(--_qm-duration-slow) var(--_qm-ease-reverse);
615
- }
634
+ border-left: var(--_qm-vertical-width) solid transparent;
635
+ padding-bottom: var(--_qm-space);
636
+ margin-bottom: calc(-1 * var(--_qm-space));
637
+ transition:
638
+ border-color var(--_qm-duration-slow) var(--_qm-ease-reverse),
639
+ padding-bottom var(--_qm-duration-slow) var(--_qm-ease-reverse),
640
+ margin-bottom var(--_qm-duration-slow) var(--_qm-ease-reverse);
641
+ }
642
+ /* Open, the cap is air as well as stroke: a section ending on a field row stands the
643
+ longer rung off what follows, and three of `--_qm-space` are what that comes to — the
644
+ panel's own, this cap, and the next header's. Shut, a section ends on its header and
645
+ stands the header's rung, so there is nothing to cap and the padding is handed back. */
616
646
  .qm-group.qm-open {
617
647
  border-left-color: var(--_qm-border);
648
+ margin-bottom: 0;
649
+ }
650
+ /* The one end that lands on a rule, where the padding is real and the rule is this
651
+ box's own bottom edge — so the stroke ends on the rule at every frame of a fold rather
652
+ than at the two ends of one, and the fields inside stand `--_qm-nest` off it. Shut,
653
+ the box ends on the header's block padding and the rule takes the heading's rung
654
+ there, the same one the rule above it takes. */
655
+ .qm-meta-bottom > .qm-groups:last-child > .qm-group:last-child {
656
+ padding-bottom: 0;
657
+ margin-bottom: 0;
658
+ }
659
+ .qm-meta-bottom > .qm-groups:last-child > .qm-group:last-child.qm-open {
660
+ padding-bottom: var(--stroke-reach);
618
661
  }
619
- /* Symmetric vertical padding at the tightest rung that still clears WCAG 2.5.8's
620
- 24×24 floor: the header is the whole row, so adjacent labels share one rhythm with no
621
- dead strip outside the button. Horizontal is the nesting step left and zero right —
622
- left, because the chevron opening the heading starts where the fields under it start,
623
- so everything the section holds stands off its vertical by one rung and the corner the
624
- stroke closes against a rule reads square; right, because the row is the target and an
625
- inset there is target given back. What the glyph gives up by taking the step is the
626
- gutter: at a tighter rung it stands in the clearance itself, with only its own bearing
627
- between ink and stroke, and the rotation swaps which bearing faces which.
662
+ /* One rung on three sides and zero on the end, the end because the row is the target
663
+ and an inset there is target given back. The rung is `--_qm-space` in both axes, and
664
+ neither is free: the block padding is the tightest that still clears WCAG 2.5.8's
665
+ 24×24 floor, and it is the whole air a heading opening the payload keeps under its
666
+ rule, which a rule cannot inset past — so the inline inset is that same number or the
667
+ corner is square on one axis alone. The heading rides the vertical there, one
668
+ nesting step out from the fields under it, which is what states that it holds them.
669
+ What the glyph gives up for the step is the gutter: it stands in the clearance itself,
670
+ with only its own bearing between ink and stroke, and the rotation swaps which bearing
671
+ faces which.
628
672
 
629
673
  `font: inherit` because a UA button inherits no face, then the body rung, one step
630
674
  over the field labels beneath it: at the label rung the two read as one register and
631
675
  the accordion stops looking like structure. Size, weight and leading sit after
632
676
  `font`, a shorthand that carries `line-height`. */
633
677
  .qm-group-header {
678
+ /* Positioned, and paint is not what it buys: a group's box grows by
679
+ `--stroke-reach` at both ends and hands the growth back as margin, so
680
+ consecutive boxes overlap in a band at every seam, and a closed group's header
681
+ ends inside the next group's box. In flow, the later sibling takes the presses
682
+ that land in the band and the bottom of the header is a dead strip; positioned,
683
+ the control holds its own box, and the band paints the same either way. */
684
+ position: relative;
634
685
  display: flex;
635
686
  align-items: center;
636
687
  gap: var(--_qm-space);
637
688
  width: 100%;
638
689
  border: none;
639
690
  background: transparent;
640
- padding: var(--_qm-space) 0 var(--_qm-space) var(--_qm-nest);
691
+ padding: var(--_qm-space) 0 var(--_qm-space) var(--_qm-space);
641
692
  font: inherit;
642
693
  font-size: var(--_qm-text-body);
643
694
  font-weight: var(--_qm-weight-mid);
@@ -672,11 +723,13 @@
672
723
  .qm-group.qm-open .qm-group-panel {
673
724
  grid-template-rows: 1fr;
674
725
  }
675
- /* The track is the accordion's only stroke that moves layout, and a reveal's landing
676
- measures its target one flush after asking — where an animating track still reads its
677
- start value. Both panels of the move are inside the group box, so the opening one and
678
- the closing one above it stand still together (`revealLeaf`). */
679
- .qm-groups.qm-instant .qm-group-panel {
726
+ /* What a toggle moves is the panel's track, and the box's own bottom edge where the
727
+ last section is the one toggling. A reveal's landing measures its target one flush
728
+ after asking — where either, mid-flight, still reads its start value — so nothing
729
+ about the accordion animates for it. Both panels of the move are inside the group box,
730
+ so the opening one and the closing one above it stand still together (`revealLeaf`). */
731
+ .qm-groups.qm-instant .qm-group-panel,
732
+ .qm-groups.qm-instant .qm-group {
680
733
  transition: none;
681
734
  }
682
735
  /* Two boxes because the axes clip differently, and the track is the only thing that
@@ -1,12 +1,11 @@
1
1
  <!--
2
2
  Card controls, composable cards only.
3
3
  Right-aligned in the header: a move-up/move-down chevron pair revealed while the
4
- pointer or the caret is in the card (each disabled at its edge), then an
5
- always-visible delete. Reorder is buttons, not drag (V1). The card scopes that
6
- reveal, so its condition lives in the parent's CSS; the pair keeps the narrower
7
- guarantee here, that a focused chevron is a visible one. The card also hangs this
8
- cluster to land these glyphs on its right gutter, and reads `--_qm-glyph-control`
9
- to do it: the size is the stylesheet's below, never an attribute.
4
+ pointer or the caret is in the card (each disabled at its edge), then delete.
5
+ Reorder is buttons, not drag (V1). All three are chips (controls.css): fill arrives
6
+ on hover, and delete rests faded danger, full under the pointer. The card scopes
7
+ that reveal, so its condition lives in the parent's CSS; the pair keeps the narrower
8
+ guarantee here, that a focused chevron is a visible one.
10
9
  -->
11
10
  <script lang="ts">
12
11
  import { wording } from './strings.js';
@@ -31,14 +30,14 @@
31
30
  <div class="qm-card-reorder">
32
31
  <button
33
32
  type="button"
34
- class="qm-icon-btn"
33
+ class="qm-chip qm-focus-ring"
35
34
  title={t.strings.cardMoveUp}
36
35
  disabled={isFirst}
37
36
  onclick={onMoveUp}><Icon name="chevron-up" /></button
38
37
  >
39
38
  <button
40
39
  type="button"
41
- class="qm-icon-btn"
40
+ class="qm-chip qm-focus-ring"
42
41
  title={t.strings.cardMoveDown}
43
42
  disabled={isLast}
44
43
  onclick={onMoveDown}><Icon name="chevron-down" /></button
@@ -46,9 +45,9 @@
46
45
  </div>
47
46
  <button
48
47
  type="button"
49
- class="qm-icon-btn qm-card-delete"
48
+ class="qm-chip qm-card-delete qm-focus-ring"
50
49
  title={t.strings.cardDelete}
51
- onclick={onDelete}><Icon name="x" /></button
50
+ onclick={onDelete}><Icon name="trash-2" /></button
52
51
  >
53
52
  </div>
54
53
 
@@ -56,20 +55,14 @@
56
55
  .qm-card-controls {
57
56
  display: flex;
58
57
  align-items: center;
59
- gap: var(--_qm-space);
60
- }
61
- /* The glyph rung in CSS rather than through the icon's `size`, because the card
62
- header's right hang is arithmetic over this number and the tap floor: an
63
- attribute set in the script is a second place the size lives, and the balance
64
- breaks silently when the two disagree. A stylesheet beats an SVG presentation
65
- attribute, so the rung wins without the attribute being removed from the icon. */
66
- .qm-card-controls :global(svg) {
67
- width: var(--_qm-glyph-control);
68
- height: var(--_qm-glyph-control);
58
+ gap: var(--_qm-space-half);
59
+ /* One compositor layer for the row. Opacity on the pair otherwise snaps
60
+ that box off the header's half-pixel, against delete which never leaves
61
+ the document paint. */
62
+ transform: translateZ(0);
69
63
  }
70
64
  .qm-card-reorder {
71
65
  display: flex;
72
- gap: var(--_qm-space-half);
73
66
  opacity: 0;
74
67
  transition: opacity var(--_qm-duration-fast) var(--_qm-ease-reverse);
75
68
  }
@@ -80,13 +73,25 @@
80
73
  .qm-card-reorder:focus-within {
81
74
  opacity: 1;
82
75
  }
83
- /* Box and disabled state come from `.qm-icon-btn` (controls.css); only the ink
84
- is this component's: the reorder pair recedes to the label tone, delete
85
- carries the danger hue (scoped, so both beat the layered base). */
86
- .qm-icon-btn {
87
- color: var(--_qm-ink-label);
76
+ /* Squares: the chip's inline pad is for a label. Width and height are one rung
77
+ so the hover fill is a square and the glyph sits in the middle. */
78
+ .qm-card-controls button {
79
+ width: var(--_qm-tap-min);
80
+ height: var(--_qm-tap-min);
81
+ padding: 0;
88
82
  }
83
+ /* Hover fill comes from `.qm-chip` (controls.css). Idle recede is on the ink,
84
+ not the box: a layer on the chip is two rasters of the same stroke, and the
85
+ fill has to stay opaque. Danger mixed to the idle rung; full under the pointer. */
89
86
  .qm-card-delete {
87
+ color: color-mix(
88
+ in oklab,
89
+ var(--_qm-danger) calc(100% * var(--_qm-opacity-idle)),
90
+ var(--_qm-surface)
91
+ );
92
+ }
93
+ .qm-card-delete:hover,
94
+ .qm-card-delete:focus-visible {
90
95
  color: var(--_qm-danger);
91
96
  }
92
97
  </style>
@@ -104,8 +104,8 @@
104
104
  aria-describedby={describedBy}
105
105
  data-ghosted={unset ? '' : undefined}
106
106
  >
107
- {shown}
108
- <Icon name="chevron-down" size={14} />
107
+ <span class="qm-select-shown">{shown}</span>
108
+ <Icon name="chevron-down" class="qm-select-chevron" size={14} />
109
109
  </Select.Trigger>
110
110
  <Select.Portal to={portalTarget}>
111
111
  <Select.Content collisionBoundary={portalTarget ?? []} sideOffset={4}>
@@ -176,6 +176,21 @@
176
176
  text-align: left;
177
177
  cursor: pointer;
178
178
  }
179
+ /* The value is one line however long it runs, and the glyph beside it keeps its rung:
180
+ the box is an inset plus one line box (controls.css), which a value that wraps
181
+ stands two lines tall, and a flex glyph with nothing holding it gives its width up
182
+ to the text first and is gone by the time the row is tight. The pair is the same
183
+ guard the element summary takes over its own title, and `min-width` is the half
184
+ that lets the text shrink at all. */
185
+ .qm-select-shown {
186
+ min-width: 0;
187
+ overflow: hidden;
188
+ text-overflow: ellipsis;
189
+ white-space: nowrap;
190
+ }
191
+ .qm-select-wrap :global(.qm-select-chevron) {
192
+ flex-shrink: 0;
193
+ }
179
194
  /* The focus ring rides `.qm-focus-ring` on the trigger (controls.css). */
180
195
  /* Shown-never-written: the closed control reads muted while unset, matching the
181
196
  ghosted placeholder the text/number controls show. */
@@ -260,7 +260,7 @@
260
260
  display: grid;
261
261
  grid-template-columns: repeat(var(--cols), 1fr);
262
262
  gap: var(--_qm-space-2);
263
- border-inline-start: var(--_qm-border-width) solid var(--_qm-border);
263
+ border-inline-start: var(--_qm-vertical-width) solid var(--_qm-border);
264
264
  padding-inline-start: var(--_qm-nest);
265
265
  padding-block: var(--_qm-space);
266
266
  }