@usableapp/cardds 0.7.100 → 0.7.102
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 +35 -0
- package/css/base.css +20 -0
- package/css/card.css +93 -0
- package/css/choice.css +22 -0
- package/css/forms.css +15 -3
- package/css/journey.css +23 -1
- package/css/media.css +1 -10
- package/css/sheet.css +21 -1
- package/css/tokens.css +2 -2
- package/dist/cardds.css +197 -17
- package/dist/cards/Card.d.ts +31 -3
- package/dist/cards/Card.js +33 -8
- package/dist/cards/CardParts.d.ts +9 -3
- package/dist/cards/CardParts.js +41 -3
- package/dist/cards/cardGo.d.ts +2 -0
- package/dist/cards/cardGo.js +6 -1
- package/dist/catalog.json +1 -1
- package/dist/choice/Calendar.js +5 -1
- package/dist/choice/CalendarWeeks.d.ts +41 -0
- package/dist/choice/CalendarWeeks.js +64 -0
- package/dist/choice/calendarDay.d.ts +13 -0
- package/dist/choice/calendarDay.js +6 -0
- package/dist/forms/Composer.d.ts +1 -1
- package/dist/forms/Composer.js +1 -1
- package/dist/forms/Field.d.ts +9 -1
- package/dist/forms/Field.js +35 -24
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/journey/JourneyBadge.d.ts +5 -2
- package/dist/journey/JourneyBadge.js +3 -3
- package/dist/media/MapArea.d.ts +10 -4
- package/dist/media/MapArea.js +8 -3
- package/dist/media/Postcard.d.ts +1 -1
- package/dist/media/Postcard.js +2 -2
- package/dist/post/PostDesk.d.ts +2 -1
- package/dist/post/PostDesk.js +11 -13
- package/dist/room.d.ts +12 -0
- package/dist/room.js +38 -0
- package/dist/sticky/Sticky.js +5 -38
- package/dist/type/Icon.d.ts +1 -1
- package/dist/type/Icon.js +1 -1
- package/dist/type/icons.d.ts +1 -1
- package/dist/type/icons.js +2 -2
- package/dist/words.d.ts +1 -1
- package/dist/words.js +2 -2
- package/icons.svg +1 -0
- package/lint/rules.mjs +2 -2
- package/package.json +1 -1
package/dist/cardds.css
CHANGED
|
@@ -322,8 +322,8 @@
|
|
|
322
322
|
surface turns everything on it to ondark with no rule per piece. A piece with no fill that stands in for a surface (a stat
|
|
323
323
|
tile) takes its ground's side: `--dark: inherit`. inkGrounds() measures it; docs/reference.md "Ink follows its ground" */
|
|
324
324
|
:where(body, [data-palette], [data-mode], .card, .tile, .modal__drawer, .postcard__front, .postcard__back, .sticky, .post-paper,
|
|
325
|
-
.float-bar__pill, .snackbar__body, .delta, .track, .tile-badge, .avatar, .bubble__body, .map__hint, .sheet--paper > .sheet__body,
|
|
326
|
-
.postcard__band, .track__step, .toggle, .chip) {
|
|
325
|
+
.float-bar__pill, .snackbar__body, .delta, .track, .tile-badge, .avatar, .bubble__body, .map__hint, .map__pin, .sheet--paper > .sheet__body,
|
|
326
|
+
.postcard__band, .track__step, .toggle, .chip, .card__face, .card--bands > :is(.card__head, .card__foot), .card--bands > .card__face > :is(.card__head, .card__foot)) {
|
|
327
327
|
--card-ink: color-mix(in srgb, var(--ink-ondark) calc(var(--dark) * 100%), var(--ink-onlight));
|
|
328
328
|
--card-muted: color-mix(in srgb, var(--ink-ondark-muted) calc(var(--dark) * 100%), var(--ink-onlight-muted));
|
|
329
329
|
--on-accent: color-mix(in srgb, var(--ink-ondark) calc(var(--accent-dark) * 100%), var(--ink-onlight));
|
|
@@ -571,6 +571,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
571
571
|
.icon--sm { width: var(--icon-sm-size, var(--icon-sm)); height: var(--icon-sm-size, var(--icon-sm)); }
|
|
572
572
|
.icon--xs { width: var(--icon-xs-size, var(--icon-xs)); height: var(--icon-xs-size, var(--icon-xs)); } /* inside captions, chips, links */
|
|
573
573
|
.icon--lg { width: var(--icon-lg-size, var(--icon-lg)); height: var(--icon-lg-size, var(--icon-lg)); } /* empty states */
|
|
574
|
+
/* an icon among words sits ON the line: `img, svg { display: block }` above would put it on a line of its own. Only a direct child of a
|
|
575
|
+
type level is running text; an icon in a button, chip, link or row is a flex/grid item and never matches. */
|
|
576
|
+
:is(.t-display, .t-h1, .t-h2, .t-title, .t-body, .t-label, .t-caption) > .icon { display: inline-block; vertical-align: middle; }
|
|
574
577
|
|
|
575
578
|
/* mobile screen column — the standard horizontal inset, and nothing vertical.
|
|
576
579
|
Top is zero on purpose: room there is only ever a RESERVE for something
|
|
@@ -703,6 +706,23 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
703
706
|
/* inside a card (a card's own "nothing yet"): the card's ink */
|
|
704
707
|
.card .empty-state { color: var(--card-ink); padding-block: var(--sp-6); }
|
|
705
708
|
|
|
709
|
+
/* ---- the turn: one flip mechanic for everything with two faces (a Postcard, a Card's `back`).
|
|
710
|
+
The faces share one grid cell and turn on Y at --motion-flip; where motion is off they swap at once.
|
|
711
|
+
The owner sets the box's size and the faces' look; .is-flipped on .flip shows the back. */
|
|
712
|
+
.flip { perspective: 60rem; display: grid; }
|
|
713
|
+
.flip__face {
|
|
714
|
+
grid-area: 1 / 1;
|
|
715
|
+
backface-visibility: hidden;
|
|
716
|
+
-webkit-backface-visibility: hidden;
|
|
717
|
+
transition: transform var(--motion-flip) ease-in-out;
|
|
718
|
+
}
|
|
719
|
+
.flip__face--back { transform: rotateY(180deg); }
|
|
720
|
+
.flip.is-flipped > .flip__face--front { transform: rotateY(-180deg); }
|
|
721
|
+
.flip.is-flipped > .flip__face--back { transform: rotateY(0); }
|
|
722
|
+
@media (prefers-reduced-motion: reduce) {
|
|
723
|
+
.flip__face { transition: none; }
|
|
724
|
+
}
|
|
725
|
+
|
|
706
726
|
/* ---- css/card.css — card boundary + list variants ---- */
|
|
707
727
|
/* ============================================================
|
|
708
728
|
cardds/card.css — the card boundary + list variants
|
|
@@ -802,6 +822,56 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
802
822
|
.card__head:has(> .stamp) > :is(.t-caption, .t-body) { grid-column: 1; grid-row: 2; align-self: start; }
|
|
803
823
|
.card__head > .stamp { grid-column: 2; grid-row: 1 / span 2; margin-inline: 0; }
|
|
804
824
|
|
|
825
|
+
/* a JourneyBadge at the edge of the card's own first head (the date box in the top-left or top-right corner) keeps its corner concentric
|
|
826
|
+
with the card's: the card's corner (--r-card) less the room the padding leaves round it. journey.css reads it behind the theme's --tile-badge-radius,
|
|
827
|
+
so a theme that sets that knob still wins. */
|
|
828
|
+
.card > .card__head:first-child > .tile-badge:is(:first-child, :last-child) { --_badge-radius: calc(var(--r-card) - var(--card-pad)); }
|
|
829
|
+
|
|
830
|
+
/* ---- paper bands: the two-tone card (`Card paper="bands"`, Lh 2026-10-19): the head and the foot are white paper bands edge to
|
|
831
|
+
edge over the card's padding, no hairline; the middle between them keeps the card's tone, lightened (--card-middle-bg). The card
|
|
832
|
+
clips, so the bands keep its corners. A two-faced card (below) carries the look on each face. */
|
|
833
|
+
:is(.card--bands, .card--bands > .card__face) {
|
|
834
|
+
background: var(--card-middle-bg, color-mix(in srgb, var(--card-bg) 35%, var(--card-1-bg)));
|
|
835
|
+
--dark: var(--card-1-dark); /* the middle is mostly paper: its words take the paper's side */
|
|
836
|
+
overflow: hidden;
|
|
837
|
+
}
|
|
838
|
+
:is(.card--bands, .card--bands > .card__face) > :is(.card__head, .card__foot) {
|
|
839
|
+
--card-bg: var(--card-1-bg);
|
|
840
|
+
--dark: var(--card-1-dark);
|
|
841
|
+
background: var(--card-bg);
|
|
842
|
+
color: var(--card-ink);
|
|
843
|
+
margin-inline: calc(-1 * var(--card-pad));
|
|
844
|
+
padding-inline: var(--card-pad);
|
|
845
|
+
}
|
|
846
|
+
:is(.card--bands, .card--bands > .card__face) > .card__head { margin-top: calc(-1 * var(--card-pad)); padding-block: var(--card-pad); }
|
|
847
|
+
:is(.card--bands, .card--bands > .card__face) > .card__foot { margin-bottom: calc(-1 * var(--card-pad)); padding-block: var(--sp-3); } /* the foot band is slim */
|
|
848
|
+
|
|
849
|
+
/* ---- two faces: the WHOLE card turns (`Card back`, Lh 2026-10-19). The article is the turning box (.flip, base.css) and only that;
|
|
850
|
+
each face is the card's paper — head · middle · foot — and both lie in one grid cell. The head and the foot are the same on both
|
|
851
|
+
faces; the middle is one fixed height (--card-middle-h, the theme's call), so the card never changes height as it turns. The hidden
|
|
852
|
+
face is inert. Each face holds an unseen turn button: the keyboard's way to turn it; its ring is drawn on the face. */
|
|
853
|
+
.card--turns { background: none; border: 0; padding: 0; cursor: pointer; touch-action: manipulation; }
|
|
854
|
+
.card__face {
|
|
855
|
+
display: grid;
|
|
856
|
+
align-content: start;
|
|
857
|
+
gap: var(--card-gap);
|
|
858
|
+
min-width: 0;
|
|
859
|
+
padding: var(--card-pad);
|
|
860
|
+
background: var(--card-bg);
|
|
861
|
+
border: var(--border-w) solid var(--card-border);
|
|
862
|
+
border-radius: inherit;
|
|
863
|
+
overflow: hidden;
|
|
864
|
+
position: relative;
|
|
865
|
+
}
|
|
866
|
+
.card__face > * { min-width: 0; }
|
|
867
|
+
.card__middle { display: grid; align-content: start; gap: var(--card-gap); height: var(--card-middle-h, 18.75rem); overflow: hidden; }
|
|
868
|
+
.card__turn { position: absolute; inset: 0; width: 1px; height: 1px; padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); }
|
|
869
|
+
.card__face:has(> .card__turn:focus-visible) { outline: var(--focus); outline-offset: calc(-1 * var(--sp-1)); }
|
|
870
|
+
/* a head's ChipRow takes a line of its own under the title: at most two lines of chips, a chip's words cut with an ellipsis */
|
|
871
|
+
.card__head > .chip-row { flex: 1 1 100%; order: 2; max-height: calc(2 * (var(--fs-sm) * 1.5 + 2 * var(--chip-sm-pad-y, var(--sp-0)) + 2 * var(--border-w)) + var(--sp-2)); overflow: hidden; }
|
|
872
|
+
.card__head > .chip-row > .chip { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.5; }
|
|
873
|
+
.card__head > .chip-row > .chip > .icon { display: inline-block; vertical-align: -0.15em; margin-inline-end: var(--chip-sm-gap, var(--sp-1)); }
|
|
874
|
+
|
|
805
875
|
/* ---- bleed: a scrolling track that runs to the container's edges ----
|
|
806
876
|
For a horizontal strip inside a padded container — .avatar-pick, .chip-row,
|
|
807
877
|
a tile grid: the TRACK reaches the edges so it reads as scrollable, while
|
|
@@ -1008,6 +1078,49 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1008
1078
|
}
|
|
1009
1079
|
.card--row .card__body { flex: 1; display: grid; gap: var(--sp-0); }
|
|
1010
1080
|
|
|
1081
|
+
/* ---- a short window of past entries (`CardList show`, Lh 2026-10-19): the list scrolls IN ITSELF, a few rows in sight and the next one cut
|
|
1082
|
+
in half to say there is more. The height is counted in rows by the component (`--_card-list-h`, px: rows wrap to any height); the room
|
|
1083
|
+
round the rows (`--card-list-window-pad`) keeps a picked row's focus ring and a card's shadow inside the scroll box, and the margin gives
|
|
1084
|
+
that room back so the rows stand where an ordinary list's would. ---- */
|
|
1085
|
+
.card-list--window {
|
|
1086
|
+
--_pad: var(--card-list-window-pad, var(--sp-2));
|
|
1087
|
+
max-height: var(--_card-list-h, none);
|
|
1088
|
+
overflow-y: auto;
|
|
1089
|
+
overscroll-behavior: contain;
|
|
1090
|
+
scrollbar-width: none;
|
|
1091
|
+
padding: var(--_pad);
|
|
1092
|
+
margin: calc(-1 * var(--_pad));
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/* ---- the cross that takes a row off the list (`Card row onRemove`): small, at the row's top right corner, inside it ----
|
|
1096
|
+
<label class="card card--row"><input type="radio">…<div class="card__body">…</div><button class="card__remove" aria-label="Remove"><svg class="icon">x</svg></button></label>
|
|
1097
|
+
Smaller than any button (a ring half a thing's size, `--tap-base` / 2 = 24) and it grows with the size of things; the touch target is an
|
|
1098
|
+
unseen ring round it, never under --tap-floor (44). It stands in the row's flow, so the title wraps before it, tucked into the corner
|
|
1099
|
+
by the row's own padding; it sits over the row's radio (which covers the whole card), so a touch on it removes and never picks. */
|
|
1100
|
+
.card__remove {
|
|
1101
|
+
--_size: var(--card-remove-size, calc(var(--tap-base) / 2));
|
|
1102
|
+
position: relative;
|
|
1103
|
+
z-index: 1;
|
|
1104
|
+
flex: none;
|
|
1105
|
+
align-self: flex-start;
|
|
1106
|
+
display: grid;
|
|
1107
|
+
place-items: center;
|
|
1108
|
+
width: var(--_size);
|
|
1109
|
+
height: var(--_size);
|
|
1110
|
+
margin-block-start: calc(var(--card-remove-inset, var(--sp-2)) - var(--card-row-pad, var(--sp-4)));
|
|
1111
|
+
margin-inline-end: calc(var(--card-remove-inset, var(--sp-2)) - var(--card-row-pad, var(--sp-4)));
|
|
1112
|
+
padding: 0;
|
|
1113
|
+
border: var(--border-w) solid currentColor;
|
|
1114
|
+
border-radius: 50%;
|
|
1115
|
+
background: var(--card-remove-bg, transparent);
|
|
1116
|
+
color: var(--card-remove-ink, currentColor);
|
|
1117
|
+
font: inherit;
|
|
1118
|
+
cursor: pointer;
|
|
1119
|
+
}
|
|
1120
|
+
.card__remove::after { content: ""; position: absolute; inset: 50% auto auto 50%; translate: -50% -50%; width: max(var(--tap-floor), var(--_size)); aspect-ratio: 1; border-radius: 50%; }
|
|
1121
|
+
.card__remove > .icon { width: 62%; height: 62%; }
|
|
1122
|
+
.card__remove:focus-visible { outline: var(--focus); outline-offset: var(--sp-0); }
|
|
1123
|
+
|
|
1011
1124
|
/* leading thumb/icon in a row */
|
|
1012
1125
|
.card__thumb {
|
|
1013
1126
|
width: var(--card-thumb-size, var(--tap-sm)); height: var(--card-thumb-size, var(--tap-sm));
|
|
@@ -1975,7 +2088,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1975
2088
|
wireframe's street pattern, so a map is never a hole. The ring marks the
|
|
1976
2089
|
rough area the member is choosing: fixed at the box's centre, so dragging
|
|
1977
2090
|
the map underneath is what moves it (the centre IS the choice), never a
|
|
1978
|
-
pin to place. The hint rides at the top, under the bar's line.
|
|
2091
|
+
pin to place. The hint rides at the top, under the bar's line.
|
|
2092
|
+
For a MEETING place (Lh 2026-10-19) the centre mark is a pin instead of the ring: `.map__pin`, below the ring's rule,
|
|
2093
|
+
its tip on the point the ring's centre sits. */
|
|
1979
2094
|
.map {
|
|
1980
2095
|
position: absolute;
|
|
1981
2096
|
inset: 0;
|
|
@@ -2003,6 +2118,24 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2003
2118
|
pointer-events: none;
|
|
2004
2119
|
z-index: 1; /* above the engine's panes (Leaflet's tiles sit at 200, but inside their own stacking context) */
|
|
2005
2120
|
}
|
|
2121
|
+
.map__pin { /* mark="pin": an exact point — the pin's TIP is where the ring's centre sits, so the engine reads the element's bottom-centre */
|
|
2122
|
+
position: absolute;
|
|
2123
|
+
left: 50%;
|
|
2124
|
+
top: var(--_band-centre);
|
|
2125
|
+
width: var(--map-pin-size, var(--tap));
|
|
2126
|
+
height: var(--map-pin-size, var(--tap));
|
|
2127
|
+
translate: -50% -100%;
|
|
2128
|
+
/* ink follows its ground: the pin stands on the map's own tone-2 fill, not on the sheet round it — in a dark sheet the inherited
|
|
2129
|
+
--accent is the card's ondark ink, cream on clay's orange map (2.8:1); so the pin takes the tone's side and its ink (tokens.css) */
|
|
2130
|
+
--dark: var(--card-2-dark);
|
|
2131
|
+
color: var(--map-pin-ink, var(--card-ink));
|
|
2132
|
+
filter: drop-shadow(var(--map-pin-drop, var(--shadow-float)));
|
|
2133
|
+
pointer-events: none;
|
|
2134
|
+
z-index: 1;
|
|
2135
|
+
}
|
|
2136
|
+
/* the lucide tip stops 2.2 of 24 short of the box's bottom: lowered so the tip IS the bottom-centre; filled with the surface so the
|
|
2137
|
+
street pattern never shows through the pin's body */
|
|
2138
|
+
.map__pin > .icon { width: 100%; height: 100%; translate: 0 9%; fill: var(--surface); }
|
|
2006
2139
|
.map__credit { /* the tile provider's credit — required by OSM; a caption, not the engine's own control */
|
|
2007
2140
|
position: absolute;
|
|
2008
2141
|
right: var(--sp-2);
|
|
@@ -2388,6 +2521,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2388
2521
|
cardds/forms.css — inputs + inline form actions
|
|
2389
2522
|
.field: flat outlined input, the label IS the placeholder.
|
|
2390
2523
|
Markup: <label class="field"><svg?/><input placeholder="Passport Number"></label>
|
|
2524
|
+
Several lines (Field rows): <label class="field field--rows"><svg?/><textarea rows="4" placeholder="…"></textarea></label>
|
|
2391
2525
|
============================================================ */
|
|
2392
2526
|
|
|
2393
2527
|
.field {
|
|
@@ -2405,17 +2539,28 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2405
2539
|
}
|
|
2406
2540
|
|
|
2407
2541
|
.field input,
|
|
2408
|
-
.field select
|
|
2542
|
+
.field select,
|
|
2543
|
+
.field textarea,
|
|
2544
|
+
.field--rows {
|
|
2545
|
+
font: var(--field-font, 400 var(--fs-lg) / 1.4 var(--font-ui)); /* 26: the words being typed read a size up from body (Lh 2026-09-22); the Composer already does. .field--rows too, so 1lh is the textarea's line for its icon */
|
|
2546
|
+
}
|
|
2547
|
+
.field input,
|
|
2548
|
+
.field select,
|
|
2549
|
+
.field textarea {
|
|
2409
2550
|
border: 0;
|
|
2410
2551
|
background: transparent;
|
|
2411
2552
|
color: inherit;
|
|
2412
|
-
font: var(--field-font, 400 var(--fs-lg) / 1.4 var(--font-ui)); /* 26: the words being typed read a size up from body (Lh 2026-09-22); the Composer already does */
|
|
2413
2553
|
width: 100%;
|
|
2414
2554
|
min-width: 0;
|
|
2415
2555
|
outline: none; /* the browser's ring drew a second box inside the field (Lh 2026-09-22) — the field itself tells focus */
|
|
2416
2556
|
caret-color: currentColor;
|
|
2417
2557
|
}
|
|
2418
|
-
.field input::placeholder
|
|
2558
|
+
.field input::placeholder,
|
|
2559
|
+
.field textarea::placeholder { color: inherit; opacity: 0.55; font-size: calc(1em * var(--_ph-fit, 1)); } /* --_ph-fit: Field's measure — a placeholder longer than the box shrinks to show whole (Lh 2026-09-25) */
|
|
2560
|
+
/* several lines in the same frame (Field rows): the textarea is as tall as its `rows` × line, the frame grows round it, the icon and the action stay at the top (Lh 2026-10-19) */
|
|
2561
|
+
.field--rows { align-items: flex-start; padding-block: var(--field-rows-pad-y, var(--sp-3)); }
|
|
2562
|
+
.field textarea { flex: 1; resize: none; display: block; }
|
|
2563
|
+
.field--rows > .icon { margin-top: calc((1lh - var(--icon-sm)) / 2); } /* centred on the first line */
|
|
2419
2564
|
/* the field the cursor is in: its own border goes full ink, in every palette (the coloured ones fade it to 25% at rest) */
|
|
2420
2565
|
.field:has(:focus, [data-cursor]) { border-color: var(--field-active-border, currentColor); } /* [data-cursor]: SheetSteps holds the phone's keyboard for this field */
|
|
2421
2566
|
|
|
@@ -2504,7 +2649,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2504
2649
|
justify-items: center;
|
|
2505
2650
|
gap: var(--tile-badge-gap, var(--sp-0));
|
|
2506
2651
|
padding: var(--tile-badge-pad-y, var(--sp-1)) var(--tile-badge-pad-x, var(--sp-2));
|
|
2507
|
-
border-radius: var(--tile-badge-radius, var(--r-badge));
|
|
2652
|
+
border-radius: var(--tile-badge-radius, var(--_badge-radius, var(--r-badge))); /* --_badge-radius: card.css, a badge in a card's corner */
|
|
2508
2653
|
background: var(--tile-badge-bg, var(--card-3-bg));
|
|
2509
2654
|
--dark: var(--card-3-dark);
|
|
2510
2655
|
color: var(--tile-badge-ink, var(--card-ink));
|
|
@@ -2518,6 +2663,28 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2518
2663
|
.tile-badge--plain { background: transparent; --dark: inherit; color: inherit; padding: 0; min-width: var(--tile-badge-plain-min-w, var(--tap-sm)); }
|
|
2519
2664
|
.tile-badge--plain .tile-badge__value { font: 800 var(--fs-h1)/1 var(--font-display); }
|
|
2520
2665
|
|
|
2666
|
+
/* stretch: a date in a head (Lh 2026-10-19) — as tall as the title block beside it and no taller (the head stretches it; size
|
|
2667
|
+
containment keeps its own words from adding height). Its height picks the form: short (a one-line title) = two lines, the day name
|
|
2668
|
+
over the number and the month on one line; tall (a two-line title) = three lines, day · number · month. */
|
|
2669
|
+
.tile-badge--stretch {
|
|
2670
|
+
align-self: stretch;
|
|
2671
|
+
container-type: size;
|
|
2672
|
+
inline-size: calc(var(--fs-lg) * 3.25);
|
|
2673
|
+
grid-template-columns: auto auto;
|
|
2674
|
+
align-content: center;
|
|
2675
|
+
justify-content: center;
|
|
2676
|
+
align-items: baseline;
|
|
2677
|
+
column-gap: var(--sp-1);
|
|
2678
|
+
row-gap: 0;
|
|
2679
|
+
padding-block: var(--sp-3);
|
|
2680
|
+
line-height: var(--tile-badge-line, 0.95);
|
|
2681
|
+
}
|
|
2682
|
+
.tile-badge--stretch > * { line-height: inherit; }
|
|
2683
|
+
.tile-badge--stretch > :first-child { grid-column: 1 / -1; }
|
|
2684
|
+
@container (min-height: 5.5rem) {
|
|
2685
|
+
.tile-badge--stretch > * { grid-column: 1 / -1; }
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2521
2688
|
/* corner badge: pin a tile badge on media (rating, price) — .card__media is relative */
|
|
2522
2689
|
.tile-badge--corner { position: absolute; right: var(--sp-3); bottom: var(--sp-3); }
|
|
2523
2690
|
|
|
@@ -3493,6 +3660,28 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3493
3660
|
.calendar__day--mark2 { background: var(--calendar-day-mark2-bg, color-mix(in srgb, var(--accent) 12%, transparent)); color: var(--calendar-day-mark2-ink, inherit); box-shadow: var(--calendar-day-mark2-shadow, inset 0 0 0 var(--border-w) var(--accent)); }
|
|
3494
3661
|
.calendar__day[aria-selected="true"] { background: var(--calendar-day-selected-bg, var(--accent)); color: var(--calendar-day-selected-ink, var(--on-accent)); font-weight: 700; }
|
|
3495
3662
|
.calendar__day:disabled { opacity: 0.3; }
|
|
3663
|
+
.calendar__day[aria-current="date"] { text-decoration: underline; text-underline-offset: 0.2em; }
|
|
3664
|
+
|
|
3665
|
+
/* ---- calendar weeks: a run of week rows that scrolls week by week, `show` rows in sight; the head sticks at the top of the box
|
|
3666
|
+
<div class="calendar calendar--weeks"><div class="calendar__weeks-head"><span class="calendar__label">June 2026</span><div class="calendar__grid">…wd×7</div></div>
|
|
3667
|
+
<div class="calendar__grid calendar__week"><button class="calendar__day">…</button>×7</div>…</div>
|
|
3668
|
+
--_weeks-h (head + `show` rows) and --_weeks-lead (the head and the gap under it, where a row snaps) are measured by src/choice/CalendarWeeks.tsx; the cells and every --calendar-* knob are Calendar's */
|
|
3669
|
+
.calendar--weeks {
|
|
3670
|
+
position: relative;
|
|
3671
|
+
max-height: var(--_weeks-h, none);
|
|
3672
|
+
overflow-y: auto;
|
|
3673
|
+
overscroll-behavior: contain;
|
|
3674
|
+
scroll-snap-type: y mandatory;
|
|
3675
|
+
scroll-padding-block-start: var(--_weeks-lead, 0px);
|
|
3676
|
+
overflow-anchor: none; /* a pick rerenders the rows; the browser must not shift the scroll to keep an anchor */
|
|
3677
|
+
scrollbar-width: none;
|
|
3678
|
+
}
|
|
3679
|
+
.calendar--weeks::-webkit-scrollbar { display: none; }
|
|
3680
|
+
.calendar__weeks-head { position: sticky; inset-block-start: 0; z-index: 1; display: grid; gap: var(--calendar-head-gap, var(--sp-2)); background: var(--calendar-weeks-head-bg, var(--card-bg)); }
|
|
3681
|
+
.calendar__label { font: var(--calendar-label-font, var(--type-label)); color: var(--calendar-label-ink, inherit); justify-self: start; }
|
|
3682
|
+
button.calendar__label { appearance: none; border: 0; padding: 0; background: none; cursor: pointer; border-radius: var(--sp-1); }
|
|
3683
|
+
button.calendar__label:focus-visible { outline: var(--focus); outline-offset: var(--sp-0); }
|
|
3684
|
+
.calendar__week { scroll-snap-align: start; }
|
|
3496
3685
|
|
|
3497
3686
|
/* ---- mood: five dots in a pill, the picked one grows ----
|
|
3498
3687
|
<fieldset class="mood"><label class="mood__opt"><input type="radio" name="mood"><span class="mood__dot"></span></label>×5</fieldset> */
|
|
@@ -5118,8 +5307,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
5118
5307
|
liked it, a voice note, a reply live OUTSIDE the postcard, in a Row under it. */
|
|
5119
5308
|
.postcard {
|
|
5120
5309
|
position: relative;
|
|
5310
|
+
display: block; /* its faces lie absolute over the box (.flip's grid cell is not used) */
|
|
5121
5311
|
min-height: var(--postcard-min-h, var(--_cover-min-h));
|
|
5122
|
-
perspective: 60rem;
|
|
5123
5312
|
border-radius: var(--postcard-radius, var(--r-card));
|
|
5124
5313
|
isolation: isolate;
|
|
5125
5314
|
}
|
|
@@ -5132,9 +5321,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
5132
5321
|
border-radius: inherit;
|
|
5133
5322
|
overflow: hidden;
|
|
5134
5323
|
isolation: isolate;
|
|
5135
|
-
backface-visibility: hidden;
|
|
5136
|
-
-webkit-backface-visibility: hidden;
|
|
5137
|
-
transition: transform var(--motion-flip) ease-in-out;
|
|
5138
5324
|
box-shadow: var(--postcard-face-shadow, var(--shadow-float));
|
|
5139
5325
|
}
|
|
5140
5326
|
/* the turn: the button that fills the face, under the band */
|
|
@@ -5174,7 +5360,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
5174
5360
|
background: var(--postcard-back-bg, var(--card-2-bg));
|
|
5175
5361
|
--dark: var(--card-2-dark);
|
|
5176
5362
|
color: var(--postcard-back-ink, var(--card-ink));
|
|
5177
|
-
transform: rotateY(180deg);
|
|
5178
5363
|
}
|
|
5179
5364
|
.postcard__back .postcard__turn { align-content: start; padding-top: var(--postcard-back-top, 20%); gap: var(--postcard-back-gap, var(--sp-5)); } /* the name a fifth down the paper, the words after it */
|
|
5180
5365
|
.postcard__caption { margin: 0; font: var(--postcard-caption-font, var(--type-title)); text-wrap: pretty; }
|
|
@@ -5193,16 +5378,11 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
5193
5378
|
.postcard__words { display: grid; gap: var(--postcard-words-gap, var(--sp-1)); flex: 1; min-width: 0; pointer-events: none; }
|
|
5194
5379
|
.postcard__front .postcard__band { --accent: var(--postcard-front-ink, var(--card-ink)); --accent-dark: calc(1 - var(--dark)); }
|
|
5195
5380
|
.postcard__band .icon-btn[aria-pressed="true"] .icon { fill: currentColor; }
|
|
5196
|
-
.postcard.is-flipped > .postcard__front { transform: rotateY(-180deg); }
|
|
5197
|
-
.postcard.is-flipped > .postcard__back { transform: rotateY(0); }
|
|
5198
5381
|
/* a voice note on the back: no words to read — its timeline (a Progress), its length and the control to hear it
|
|
5199
5382
|
under them, at the middle over the turn, on the face's inset; the app plays and keeps the time */
|
|
5200
5383
|
.postcard__voice { position: absolute; inset: 0; z-index: 2; display: grid; align-content: center; justify-items: center; gap: var(--postcard-voice-gap, var(--sp-3)); padding: var(--postcard-face-pad, var(--card-pad-rest)); pointer-events: none; }
|
|
5201
5384
|
.postcard__voice > * { pointer-events: auto; }
|
|
5202
5385
|
.postcard__voice > .progress { justify-self: stretch; pointer-events: none; }
|
|
5203
|
-
@media (prefers-reduced-motion: reduce) {
|
|
5204
|
-
.postcard__face { transition: none; }
|
|
5205
|
-
}
|
|
5206
5386
|
|
|
5207
5387
|
/* ---- css/layover.css — ask drawer, popover menu, banner, float bar, callout ---- */
|
|
5208
5388
|
/* ============================================================
|
package/dist/cards/Card.d.ts
CHANGED
|
@@ -2,13 +2,17 @@ import { type ComponentProps, type MouseEvent, type ReactNode } from 'react';
|
|
|
2
2
|
import { type CardHeadProps } from './CardHead.js';
|
|
3
3
|
import { type StackPlace } from '../sheets/stackCard.js';
|
|
4
4
|
export type { StackPlace };
|
|
5
|
+
/** which side of a two-faced card shows */
|
|
6
|
+
export type CardFace = 'front' | 'back';
|
|
5
7
|
/** a card's paper: 1 (default, light) · 2 (tinted) · 3 (dark / accent — controls invert) · 4 · 5 (more colours, from the palette) */
|
|
6
8
|
export type Tone = 1 | 2 | 3 | 4 | 5;
|
|
7
9
|
export interface CardProps extends ComponentProps<'article'> {
|
|
8
10
|
/** the tone slot — see `Tone` */
|
|
9
11
|
tone?: Tone;
|
|
10
|
-
/** the content is white paper and the head keeps the card's colour
|
|
11
|
-
|
|
12
|
+
/** `true`: the content is white paper and the head keeps the card's colour (the look of `Sheet paper`). `'bands'`: the opposite, two-tone —
|
|
13
|
+
* the head and the foot are white paper bands edge to edge, the middle between them keeps the card's tone, lightened (knob `--card-middle-bg`).
|
|
14
|
+
* Omit it and the content matches the head */
|
|
15
|
+
paper?: boolean | 'bands';
|
|
12
16
|
/** compact: a small card inside a card or a sheet body (a note, a promo) */
|
|
13
17
|
sm?: boolean;
|
|
14
18
|
/** a list row that is its own card: thumb · body · end, in a CardList. Its corner is the big card's PROPORTION, not its size (`--r-card-row`, 13; knob `--card-row-radius`).
|
|
@@ -49,6 +53,28 @@ export interface CardProps extends ComponentProps<'article'> {
|
|
|
49
53
|
leave?: boolean;
|
|
50
54
|
/** a card that was asked to `leave` HAS left (its motion ended; at once where motion is off, or when it has no motion) — the app does what follows: changes the page, closes. Once per leave */
|
|
51
55
|
onLeft?: () => void;
|
|
56
|
+
/** a `row` card the member can take off its list: a small cross at the row's top right corner (smaller than a button, growing with the size of
|
|
57
|
+
* things; its touch target stays 44). A touch on it calls this and never picks the row. The app takes the row out of its own list. Knobs
|
|
58
|
+
* `--card-remove-size` · `--card-remove-inset` · `--card-remove-ink` · `--card-remove-bg` */
|
|
59
|
+
onRemove?: () => void;
|
|
60
|
+
/** THE BACK — the card has two faces and the member turns the WHOLE card over: a tap anywhere on it that is not on a control asks for the other
|
|
61
|
+
* face. The head and the foot (`heading`/`caption`/`end` or a CardHead child, `foot`) are given once and drawn on BOTH faces, so they read the same
|
|
62
|
+
* either way; the children are the front's middle, `back` the back's. The middle is one fixed height on both faces (knob `--card-middle-h`), so the
|
|
63
|
+
* card never changes height when it turns. The hidden face is `inert`; an `id` in the head or the foot would be drawn twice, so give it none.
|
|
64
|
+
* Not on a `row`, `sm` or `stack` card */
|
|
65
|
+
back?: ReactNode;
|
|
66
|
+
/** CONTROLLED: the face that shows — pair it with `onFaceChange`. The app owns it */
|
|
67
|
+
face?: CardFace;
|
|
68
|
+
/** UNCONTROLLED: the face it starts on (default `'front'`) */
|
|
69
|
+
defaultFace?: CardFace;
|
|
70
|
+
/** a tap asked for the other face */
|
|
71
|
+
onFaceChange?: (face: CardFace) => void;
|
|
72
|
+
/** the spoken name of the front's hidden turn-over button (default: the words' `turnToBack`) */
|
|
73
|
+
toBackLabel?: string;
|
|
74
|
+
/** the spoken name of the back's hidden turn-over button (default: the words' `turnToFront`) */
|
|
75
|
+
toFrontLabel?: string;
|
|
76
|
+
/** the cross's spoken name (default: the words' `remove`) — name what goes: "Remove 'buy medicine'" */
|
|
77
|
+
removeLabel?: string;
|
|
52
78
|
}
|
|
53
79
|
/**
|
|
54
80
|
* Card — ONE boundary: head · content · foot. Cards are content the system offers (the words, the thing to read).
|
|
@@ -58,8 +84,10 @@ export interface CardProps extends ComponentProps<'article'> {
|
|
|
58
84
|
* A page with one feature and one CTA may be a single card. Keep everything big.
|
|
59
85
|
* PRESSED WHOLE: a card whose ONLY control is its foot's button, or a row card's end button, is pressed anywhere — a touch off the
|
|
60
86
|
* button runs that button's own action once, and the card dims under the finger. The app writes nothing; any other card is only read.
|
|
87
|
+
* TWO FACES (`back`): the whole card turns over on a tap off a control (the flip mechanic it shares with Postcard); the head and the foot
|
|
88
|
+
* read the same on both faces, the middle is one fixed height. `paper="bands"`: white head and foot bands edge to edge, the tint between.
|
|
61
89
|
*/
|
|
62
|
-
export declare function Card({ tone, paper, sm, row, cover, fold, centre, band, as, heading, caption, end, level, foot, footLink, stack, leave, onLeft, className, style, children, onClick, ref, ...rest }: CardProps): import("react").DetailedReactHTMLElement<{
|
|
90
|
+
export declare function Card({ tone, paper, sm, row, cover, fold, centre, band, as, heading, caption, end, level, foot, footLink, stack, leave, onLeft, onRemove, removeLabel, back, face, defaultFace, onFaceChange, toBackLabel, toFrontLabel, className, style, children, onClick, ref, ...rest }: CardProps): import("react").DetailedReactHTMLElement<{
|
|
63
91
|
onClick: (e: MouseEvent<HTMLElement>) => void;
|
|
64
92
|
inert?: boolean | undefined;
|
|
65
93
|
"aria-busy"?: (boolean | "true" | "false") | undefined;
|
package/dist/cards/Card.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Children, createElement, isValidElement, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
3
|
import { cx } from '../cx.js';
|
|
4
|
-
import { useLatest, warnOnce } from '../state.js';
|
|
4
|
+
import { useControlled, useLatest, warnOnce } from '../state.js';
|
|
5
5
|
import { CardHead } from './CardHead.js';
|
|
6
6
|
import { CardFoot } from './CardFoot.js';
|
|
7
|
-
import { goControl } from './cardGo.js';
|
|
7
|
+
import { goControl, onControl } from './cardGo.js';
|
|
8
|
+
import { Icon } from '../type/Icon.js';
|
|
9
|
+
import { word } from '../words.js';
|
|
8
10
|
import { StackHeadCtx, StackPullCtx, ownHead, stackPaint, useStackCard } from '../sheets/stackCard.js';
|
|
9
11
|
/**
|
|
10
12
|
* Card — ONE boundary: head · content · foot. Cards are content the system offers (the words, the thing to read).
|
|
@@ -14,8 +16,17 @@ import { StackHeadCtx, StackPullCtx, ownHead, stackPaint, useStackCard } from '.
|
|
|
14
16
|
* A page with one feature and one CTA may be a single card. Keep everything big.
|
|
15
17
|
* PRESSED WHOLE: a card whose ONLY control is its foot's button, or a row card's end button, is pressed anywhere — a touch off the
|
|
16
18
|
* button runs that button's own action once, and the card dims under the finger. The app writes nothing; any other card is only read.
|
|
19
|
+
* TWO FACES (`back`): the whole card turns over on a tap off a control (the flip mechanic it shares with Postcard); the head and the foot
|
|
20
|
+
* read the same on both faces, the middle is one fixed height. `paper="bands"`: white head and foot bands edge to edge, the tint between.
|
|
17
21
|
*/
|
|
18
|
-
export function Card({ tone, paper, sm, row, cover, fold, centre, band, as = 'article', heading, caption, end, level, foot, footLink, stack, leave, onLeft, className, style, children, onClick, ref, ...rest }) {
|
|
22
|
+
export function Card({ tone, paper, sm, row, cover, fold, centre, band, as = 'article', heading, caption, end, level, foot, footLink, stack, leave, onLeft, onRemove, removeLabel, back, face, defaultFace = 'front', onFaceChange, toBackLabel, toFrontLabel, className, style, children, onClick, ref, ...rest }) {
|
|
23
|
+
if (onRemove && !row)
|
|
24
|
+
warnOnce('card-remove-row', 'cardds Card: `onRemove` draws its cross on a `row` card only');
|
|
25
|
+
if (back != null && (row || sm || stack))
|
|
26
|
+
warnOnce('card-back-kind', 'cardds Card: `back` turns a whole card — not a `row`, `sm` or `stack` card; the back is ignored');
|
|
27
|
+
const turns = back != null && !row && !sm && !stack;
|
|
28
|
+
const [shown, ask] = useControlled(face, defaultFace, onFaceChange);
|
|
29
|
+
const flipped = turns && shown === 'back';
|
|
19
30
|
const head = heading != null || caption != null || end != null;
|
|
20
31
|
const tail = foot != null || footLink != null;
|
|
21
32
|
if (head || tail) {
|
|
@@ -51,7 +62,7 @@ export function Card({ tone, paper, sm, row, cover, fold, centre, band, as = 'ar
|
|
|
51
62
|
return () => { live = false; };
|
|
52
63
|
}, [away]);
|
|
53
64
|
const [go, setGo] = useState(false);
|
|
54
|
-
useLayoutEffect(() => { setGo(own.current != null && goControl(own.current) != null); }); // every render: what the card holds is its children's
|
|
65
|
+
useLayoutEffect(() => { setGo(!turns && own.current != null && goControl(own.current) != null); }); // every render: what the card holds is its children's
|
|
55
66
|
const pressing = useRef(false);
|
|
56
67
|
const click = (e) => {
|
|
57
68
|
if (pressing.current)
|
|
@@ -59,6 +70,11 @@ export function Card({ tone, paper, sm, row, cover, fold, centre, band, as = 'ar
|
|
|
59
70
|
onClick?.(e);
|
|
60
71
|
const target = e.target;
|
|
61
72
|
const card = own.current;
|
|
73
|
+
if (turns) {
|
|
74
|
+
if (!e.defaultPrevented && card && !onControl(card, target))
|
|
75
|
+
ask(flipped ? 'front' : 'back');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
62
78
|
if (!e.defaultPrevented && card?.contains(target)) {
|
|
63
79
|
const control = goControl(card);
|
|
64
80
|
if (control && !control.contains(target)) {
|
|
@@ -90,13 +106,22 @@ export function Card({ tone, paper, sm, row, cover, fold, centre, band, as = 'ar
|
|
|
90
106
|
const props = {
|
|
91
107
|
...rest,
|
|
92
108
|
ref: setRef,
|
|
93
|
-
className: cx('card', tone && tone !== 1 && `card--${tone}`, paper && 'sheet--paper', sm && 'card--sm', row && 'card--row', cover && 'card--cover', fold && 'card--fold', centre && 'card--centre', band && 'card--band', go && 'card--go', leaver && 'card--leaver', away && 'card--away', painted.className, className),
|
|
109
|
+
className: cx('card', tone && tone !== 1 && `card--${tone}`, paper === true && 'sheet--paper', paper === 'bands' && 'card--bands', turns && 'card--turns flip', flipped && 'is-flipped', sm && 'card--sm', row && 'card--row', cover && 'card--cover', fold && 'card--fold', centre && 'card--centre', band && 'card--band', go && 'card--go', leaver && 'card--leaver', away && 'card--away', painted.className, className),
|
|
94
110
|
style: painted.style || style ? { ...painted.style, ...style } : undefined,
|
|
95
111
|
...(stack ? { 'aria-busy': painted.busy } : {}),
|
|
96
112
|
...(away ? { inert: true } : {}),
|
|
97
113
|
onClick: click,
|
|
98
114
|
};
|
|
115
|
+
const cross = row && onRemove ? (_jsx("button", { type: "button", className: "card__remove", "aria-label": removeLabel ?? word('remove'), onClick: (e) => { e.preventDefault(); e.stopPropagation(); onRemove(); }, children: _jsx(Icon, { name: "x" }) }, "remove")) : null;
|
|
116
|
+
if (turns) {
|
|
117
|
+
const kids = Children.toArray(children);
|
|
118
|
+
const ownHeadKid = kids.find((c) => isValidElement(c) && c.type === CardHead);
|
|
119
|
+
const ownFootKid = kids.find((c) => isValidElement(c) && c.type === CardFoot);
|
|
120
|
+
const front = kids.filter((c) => c !== ownHeadKid && c !== ownFootKid);
|
|
121
|
+
const side = (which, middle) => (_jsxs("div", { className: cx('card__face flip__face', `flip__face--${which}`), ...((which === 'back') !== flipped ? { inert: true } : {}), children: [_jsx("button", { type: "button", className: "card__turn", "aria-label": which === 'front' ? toBackLabel ?? word('turnToBack') : toFrontLabel ?? word('turnToFront'), onClick: () => ask(which === 'front' ? 'back' : 'front') }), head ? _jsx(CardHead, { heading: heading, caption: caption, end: end, level: level }) : ownHeadKid, _jsx("div", { className: "card__middle", children: which === 'front' ? front : middle }), tail ? _jsx(CardFoot, { link: footLink, children: foot }) : ownFootKid] }, which));
|
|
122
|
+
return createElement(as, props, inner(_jsxs(_Fragment, { children: [side('front', null), side('back', back)] })));
|
|
123
|
+
}
|
|
99
124
|
if (!head && !tail)
|
|
100
|
-
return createElement(as, props, inner(children));
|
|
101
|
-
return createElement(as, props, inner(_jsxs(_Fragment, { children: [head && _jsx(CardHead, { heading: heading, caption: caption, end: end, level: level }, "head"), Children.toArray(children), tail && _jsx(CardFoot, { link: footLink, children: foot }, "foot")] })));
|
|
125
|
+
return createElement(as, props, inner(_jsxs(_Fragment, { children: [children, cross] })));
|
|
126
|
+
return createElement(as, props, inner(_jsxs(_Fragment, { children: [head && _jsx(CardHead, { heading: heading, caption: caption, end: end, level: level }, "head"), Children.toArray(children), tail && _jsx(CardFoot, { link: footLink, children: foot }, "foot"), cross] })));
|
|
102
127
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentProps } from 'react';
|
|
2
2
|
export interface CardMediaProps extends ComponentProps<'div'> {
|
|
3
3
|
}
|
|
4
4
|
/** CardMedia — rounded media inside a card (a Placeholder or img); a corner JourneyBadge pins to it. */
|
|
@@ -16,10 +16,16 @@ export interface CardEndProps extends ComponentProps<'div'> {
|
|
|
16
16
|
/** CardEnd — the trailing value + caption, right-aligned (in a head or a row card). */
|
|
17
17
|
export declare function CardEnd({ className, ...rest }: CardEndProps): import("react").JSX.Element;
|
|
18
18
|
export interface CardListProps extends ComponentProps<'div'> {
|
|
19
|
+
/** how many rows show before the list scrolls IN ITSELF, counted in rows, not a height: `2.5` = two whole rows and half of the third, the cut row
|
|
20
|
+
* saying there is more. The list measures its own rows (they wrap to any height) and keeps the window right when they change. Leave it out and the
|
|
21
|
+
* list is as long as its rows and scrolls with what holds it */
|
|
22
|
+
show?: number;
|
|
19
23
|
}
|
|
20
24
|
/** CardList — a stack of `Card row`s, each its own boundary: a list of things to open or to pick (past requests to reuse). It stands on the base or straight in a sheet's
|
|
21
|
-
* body and scrolls with it; never inside a card (a card never wraps a card — `cardds check` rule `card-in-card`). Rows inside a card are `Rows`.
|
|
22
|
-
|
|
25
|
+
* body and scrolls with it; never inside a card (a card never wraps a card — `cardds check` rule `card-in-card`). Rows inside a card are `Rows`.
|
|
26
|
+
* A SHORT WINDOW of past entries under a form (`show={2.5}`): the list scrolls in itself, a few rows in sight; each row a `Card row as="label"` round a radio
|
|
27
|
+
* (pick one) with `onRemove` for its corner cross (take it off the list) — the app owns the list, the picked one and what a removal does. */
|
|
28
|
+
export declare function CardList({ show, className, style, ref, ...rest }: CardListProps): import("react").JSX.Element;
|
|
23
29
|
export interface IconRowProps extends ComponentProps<'span'> {
|
|
24
30
|
}
|
|
25
31
|
/** IconRow — a trailing cluster of Icons in a card or stack head. */
|
package/dist/cards/CardParts.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
2
3
|
import { cx } from '../cx.js';
|
|
3
4
|
/** CardMedia — rounded media inside a card (a Placeholder or img); a corner JourneyBadge pins to it. */
|
|
4
5
|
export function CardMedia({ className, ...rest }) {
|
|
@@ -17,9 +18,46 @@ export function CardEnd({ className, ...rest }) {
|
|
|
17
18
|
return _jsx("div", { className: cx('card__end', className), ...rest });
|
|
18
19
|
}
|
|
19
20
|
/** CardList — a stack of `Card row`s, each its own boundary: a list of things to open or to pick (past requests to reuse). It stands on the base or straight in a sheet's
|
|
20
|
-
* body and scrolls with it; never inside a card (a card never wraps a card — `cardds check` rule `card-in-card`). Rows inside a card are `Rows`.
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
* body and scrolls with it; never inside a card (a card never wraps a card — `cardds check` rule `card-in-card`). Rows inside a card are `Rows`.
|
|
22
|
+
* A SHORT WINDOW of past entries under a form (`show={2.5}`): the list scrolls in itself, a few rows in sight; each row a `Card row as="label"` round a radio
|
|
23
|
+
* (pick one) with `onRemove` for its corner cross (take it off the list) — the app owns the list, the picked one and what a removal does. */
|
|
24
|
+
export function CardList({ show, className, style, ref, ...rest }) {
|
|
25
|
+
const own = useRef(null);
|
|
26
|
+
useLayoutEffect(() => {
|
|
27
|
+
const list = own.current;
|
|
28
|
+
if (!list || show == null)
|
|
29
|
+
return;
|
|
30
|
+
const fit = () => {
|
|
31
|
+
const rows = [...list.children];
|
|
32
|
+
const whole = Math.floor(show);
|
|
33
|
+
const cut = rows[whole];
|
|
34
|
+
if (!cut) {
|
|
35
|
+
list.style.removeProperty('--_card-list-h');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const box = cut.getBoundingClientRect();
|
|
39
|
+
const at = box.top - list.getBoundingClientRect().top + list.scrollTop; // from the list's top edge, wherever it is scrolled
|
|
40
|
+
list.style.setProperty('--_card-list-h', `${at + (show - whole) * box.height}px`);
|
|
41
|
+
};
|
|
42
|
+
fit();
|
|
43
|
+
const watch = new ResizeObserver(fit);
|
|
44
|
+
watch.observe(list);
|
|
45
|
+
for (const row of list.children)
|
|
46
|
+
watch.observe(row);
|
|
47
|
+
const rows = new MutationObserver(() => { for (const row of list.children)
|
|
48
|
+
watch.observe(row); fit(); });
|
|
49
|
+
rows.observe(list, { childList: true });
|
|
50
|
+
return () => { watch.disconnect(); rows.disconnect(); };
|
|
51
|
+
}, [show]);
|
|
52
|
+
const setRef = (node) => {
|
|
53
|
+
own.current = node;
|
|
54
|
+
const passed = ref;
|
|
55
|
+
if (typeof passed === 'function')
|
|
56
|
+
passed(node);
|
|
57
|
+
else if (passed)
|
|
58
|
+
passed.current = node;
|
|
59
|
+
};
|
|
60
|
+
return _jsx("div", { ref: setRef, className: cx('card-list', show != null && 'card-list--window', className), style: style, ...rest });
|
|
23
61
|
}
|
|
24
62
|
/** IconRow — a trailing cluster of Icons in a card or stack head. */
|
|
25
63
|
export function IconRow({ className, ...rest }) {
|
package/dist/cards/cardGo.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
/** the control this card presses when it is touched off that control, or null: the card is only read */
|
|
2
2
|
export declare function goControl(card: HTMLElement): HTMLElement | null;
|
|
3
|
+
/** the press landed on something the member presses inside this card (a Card with a `back` turns only when it did not) */
|
|
4
|
+
export declare function onControl(card: HTMLElement, target: Element): boolean;
|
package/dist/cards/cardGo.js
CHANGED
|
@@ -24,6 +24,11 @@ export function goControl(card) {
|
|
|
24
24
|
return null;
|
|
25
25
|
const foot = control.parentElement;
|
|
26
26
|
const inFoot = foot?.matches('.card__foot') && foot.parentElement?.closest('.card') === card; // the card's OWN foot, also when a part of the card holds it (a FeedCard's region)
|
|
27
|
-
const atRowEnd = card.matches('.card--row') && card.lastElementChild === control;
|
|
27
|
+
const atRowEnd = card.matches('.card--row') && card.lastElementChild === control && !control.matches('.card__remove'); // a row's cross takes it off its list: never pressed by a touch elsewhere
|
|
28
28
|
return inFoot || atRowEnd ? control : null;
|
|
29
29
|
}
|
|
30
|
+
/** the press landed on something the member presses inside this card (a Card with a `back` turns only when it did not) */
|
|
31
|
+
export function onControl(card, target) {
|
|
32
|
+
const control = target.closest(CONTROLS);
|
|
33
|
+
return control != null && control !== card && card.contains(control);
|
|
34
|
+
}
|