@usableapp/cardds 0.7.87 → 0.7.88
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 +13 -0
- package/css/card.css +9 -0
- package/css/finish.css +2 -2
- package/css/post.css +1 -3
- package/css/step.css +8 -3
- package/dist/cardds.css +20 -8
- package/dist/cards/Card.d.ts +10 -2
- package/dist/cards/Card.js +18 -4
- package/dist/catalog.json +1 -1
- package/dist/lists/Row.d.ts +2 -0
- package/dist/scaffold/EmptyState.d.ts +2 -1
- package/dist/scaffold/EmptyState.js +2 -1
- package/lint/rules.mjs +7 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
> For: a project that consumes `@usableapp/cardds` and its AI — what changed between versions, written as before → after, so a screen can be brought up to date without reading the source. Newest first. A consumer never edits cardds; if something here leaves you without a way to do what you did before, report the gap.
|
|
4
4
|
|
|
5
|
+
## 0.7.88 — 2026-10-13
|
|
6
|
+
|
|
7
|
+
Nothing here breaks an existing screen: a prop pair (`Card leave` + `onLeft`) and three knobs are added and none removed, no class or markup of an existing screen changed (a `Card` without `leave` is as before), and an `EmptyState` with its knob unset stands where it did in 0.7.87.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **`Card leave` + `onLeft`: the card floats away, and the app is told when it has gone** (asked after a send: one `Card` standing in a `Page`, with a `StampOn main arrive` that has just landed on it, leaves, and the app then changes the page). Before → after: the only thing that left was the `PostDesk`'s own card (`state="sent"`), and a `Card` could be hidden only by the app taking it out of the tree, with no motion and no word of when → `leave` (a boolean, the app's, controlled like an input) sends the card up and out with a small turn, all it holds with it, and `onLeft` is called once when the motion has ended (at once with reduced motion, or when the card has no motion). Set back to `false` the card stands again where it was, and a leave set back before it ended says nothing. A card that has left takes no taps and is `inert` (out of the accessibility tree); it moves by `translate` and `rotate` only, so the room round it does not change. If a send fails the app never sets `leave` and the card stays. A card in a box that clips (a scrolling `StackSheet` body clips at its edge) is cut there as it goes. A card that is a `SheetStack` sheet (`stack`) ignores `leave`. A card given `leave` (true or false) wears `card--leaver`, and `card--away` while it is asked to go: two new classes, on no other card. Knobs `--leave-rise` (160%, how far it goes up, a share of its own height) and `--leave-turn` (-5deg); the time is `--motion-sheet`. Under `data-finish="sticker"` a leaving lone card turns to `--leave-turn` from its lean and back. Stories `Card · Leaves`, `Controlled` and `AfterTheSend` (the `StampOn arrive` → `leave` → `onLeft` sequence); the `Card` JSDoc, and so `cardds get Card` and `cardds find "card flies away"`, name it.
|
|
11
|
+
- **`Row htmlFor`: a row that is a label can name the control it presses.** Before → after: `Row as="label"` typed a div's attributes, so `htmlFor` was a type error (it worked when run) → `htmlFor` is a prop. It is needed when the row's `end` holds a button before its `Check` (a picked row's own move, "open the map"): a label presses the first control inside it, so without `htmlFor` a touch on the row's words pressed that button. Story `Row · PickShowsItsMove`: pick one row, the picked row shows its button, a second touch keeps the pick.
|
|
12
|
+
- **`cardds check` warns on a label row that holds a button and names no control** (`row-label-button`): `<Row as="label">` with an `IconBtn`, `Btn` or `Link` inside and no `htmlFor`. Before, the check was silent and the row's words pressed the button.
|
|
13
|
+
- **`--empty-state-at`: where an `EmptyState` stands in the free room of what holds it.** A number from 0 to 1, the share of the free room that stands above it. Before → after: alone in a `Step` it stood two parts from the top and three from the bottom (40%) and a theme could not move it → that is the knob's default, 0.4, and `:root { --empty-state-at: 0.2 }` in a project's theme puts it a fifth down on every screen (or set it on a `Screen` / `Step` for the ones inside). Alone in a `SheetBody` (a `Sheet`, or a `StackSheet` of a `SheetStack`) it stood at the top of the body with no way to lower it → still at the top by default (0), and the knob lowers it by that share of the body's free room, 0.4 putting it where a `Step` puts it. The body is a flex column only while an `EmptyState` is its only child; the geometry of every other body is as before. Stories `EmptyState · HigherUp` and `EmptyState · InASheet`; the `EmptyState` JSDoc, and so `cardds get EmptyState`, names the knob.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **The `PostDesk`'s leave and the `Card`'s are one mechanic.** Before → after: `post.css` wrote the sent card's `--_y: -160%`, `rotate: -5deg` and its `translate` / `rotate` transition for itself → one rule in card.css (LEAVE) holds the transition and reads `--leave-rise` and `--leave-turn` for both holders, and post.css keeps only what is its own (the half's `--_x`). Nothing changes for a screen: a `PostDesk` sent draws, moves and times as in 0.7.87 (computed style of all 449 stories identical before and after), and a theme that set neither knob sees no difference.
|
|
17
|
+
|
|
5
18
|
## 0.7.87 — 2026-10-12
|
|
6
19
|
|
|
7
20
|
Nothing here breaks an existing screen: no class and no markup changed, six knobs are added and none removed; three mechanics that two files each wrote twice are now one rule each, and every screen without `data-finish` draws as in 0.7.86.
|
package/css/card.css
CHANGED
|
@@ -25,6 +25,15 @@
|
|
|
25
25
|
screens. Children shrink to the track; they never size the card. */
|
|
26
26
|
.card > * { min-width: 0; }
|
|
27
27
|
|
|
28
|
+
/* LEAVE — a card that goes: up and out with a small turn, as one piece (translate and rotate only, so nothing round it moves). The post desk's
|
|
29
|
+
card once `sent` and a `Card` with `leave` read the same two knobs; the time is --motion-sheet. `.card--leaver` is a card whose app may ask it
|
|
30
|
+
to go (it holds the transition, so it comes back as it went); `.card--away` is one that has been asked. A knob of the time would read dead:
|
|
31
|
+
the knob walk switches motion off */
|
|
32
|
+
:is(.card--leaver, .post-desk__card) { transition: translate var(--motion-sheet) ease, rotate var(--motion-sheet) ease; }
|
|
33
|
+
:is(.card--away, .post-desk--sent .post-desk__card) { --_y: calc(var(--leave-rise, 160%) * -1); rotate: var(--leave-turn, -5deg); }
|
|
34
|
+
.card--away { translate: 0 var(--_y); }
|
|
35
|
+
@media (prefers-reduced-motion: reduce) { :is(.card--leaver, .post-desk__card) { transition: none; } }
|
|
36
|
+
|
|
28
37
|
.card--2 { --card-bg: var(--card-2-bg); --card-ink: var(--card-2-ink); --card-muted: var(--card-2-muted); }
|
|
29
38
|
/* dark/accent slot: accent-toned controls would vanish — the slot re-points
|
|
30
39
|
the accent tokens, so every accent consumer (btn--primary, chip--on,
|
package/css/finish.css
CHANGED
|
@@ -145,8 +145,8 @@
|
|
|
145
145
|
0 calc(var(--sticker-cut, 5px) + 4px) 0 0 color-mix(in srgb, var(--on-surface) 16%, transparent),
|
|
146
146
|
0 calc(var(--sticker-cut, 5px) + 12px) 20px -8px color-mix(in srgb, var(--on-surface) 22%, transparent);
|
|
147
147
|
}
|
|
148
|
-
[data-finish="sticker"] .card:not(.sheet, .modal, .sheet-stack *, .card-list > *, .card-feed > *) { rotate: var(--sticker-tilt, 0.8deg); }
|
|
149
|
-
[data-finish="sticker"] .card:not(.sheet, .modal, .sheet-stack *, .card-list > *, .card-feed > *):nth-of-type(even) { rotate: calc(-1 * var(--sticker-tilt, 0.8deg)); }
|
|
148
|
+
[data-finish="sticker"] .card:not(.sheet, .modal, .card--away, .sheet-stack *, .card-list > *, .card-feed > *) { rotate: var(--sticker-tilt, 0.8deg); }
|
|
149
|
+
[data-finish="sticker"] .card:not(.sheet, .modal, .card--away, .sheet-stack *, .card-list > *, .card-feed > *):nth-of-type(even) { rotate: calc(-1 * var(--sticker-tilt, 0.8deg)); }
|
|
150
150
|
[data-finish="sticker"] .card-list { gap: var(--sp-5); }
|
|
151
151
|
[data-finish="sticker"] .btn {
|
|
152
152
|
box-shadow: 0 3px 0 0 color-mix(in srgb, currentColor 30%, transparent);
|
package/css/post.css
CHANGED
|
@@ -199,10 +199,9 @@
|
|
|
199
199
|
width: calc(var(--_p) * 2);
|
|
200
200
|
aspect-ratio: 3 / 2;
|
|
201
201
|
translate: var(--_x) var(--_y);
|
|
202
|
-
transition: translate var(--motion-sheet) ease, rotate var(--motion-sheet) ease;
|
|
203
202
|
}
|
|
204
203
|
.post-desk--address .post-desk__card { --_x: calc(100cqi - var(--_p) * 2); }
|
|
205
|
-
|
|
204
|
+
/* sent: the card leaves by card.css LEAVE, as a `Card` with `leave` does */
|
|
206
205
|
.post-desk__half { min-width: 0; min-height: 0; padding: var(--post-desk-pad, var(--sp-5)); display: flex; flex-direction: column; }
|
|
207
206
|
.post-desk__half--address { border-inline-start: var(--rule-w) solid var(--_rule); gap: var(--sp-4); }
|
|
208
207
|
.post-desk__write {
|
|
@@ -271,7 +270,6 @@
|
|
|
271
270
|
.post-desk__note { margin: 0; font: var(--type-caption); color: var(--post-desk-note-ink, var(--on-surface-muted)); min-height: 1.35em; }
|
|
272
271
|
/* what stands where the card was, once it has left */
|
|
273
272
|
.post-desk__gone { position: absolute; inset: 0; display: grid; grid-template-columns: minmax(0, 1fr); align-content: center; gap: var(--sp-2); }
|
|
274
|
-
@media (prefers-reduced-motion: reduce) { .post-desk__card { transition: none; } }
|
|
275
273
|
|
|
276
274
|
/* ============================================================
|
|
277
275
|
post-box — KEEPING many. The cards whole and landscape, standing one behind another; each shows its top edge: who, where,
|
package/css/step.css
CHANGED
|
@@ -185,7 +185,12 @@
|
|
|
185
185
|
object-fit: cover;
|
|
186
186
|
}
|
|
187
187
|
/* an EMPTY STATE is a component in the content box too (Lh 2026-09-23 — it no longer floats in a Centre over the screen): alone in a
|
|
188
|
-
Step it stands at the human centre of the content, 40% down (two parts of room above it, three below)
|
|
189
|
-
|
|
190
|
-
.step:has(> .empty-state)::
|
|
188
|
+
Step it stands at the human centre of the content, 40% down (two parts of room above it, three below). --empty-state-at is the share of the
|
|
189
|
+
free room that stands above it (a theme's 0.2 puts it a fifth down); a sheet's body holds an only EmptyState by the same share, but defaults to 0: at the top, as it always stood */
|
|
190
|
+
.step:has(> .empty-state)::before { content: ""; flex: calc(var(--empty-state-at, 0.4) * 5) 1 0; }
|
|
191
|
+
.step:has(> .empty-state)::after { content: ""; flex: calc(5 - var(--empty-state-at, 0.4) * 5) 1 0; }
|
|
191
192
|
.step > .empty-state { flex: none; }
|
|
193
|
+
.sheet__body:has(> .empty-state:only-child) { display: flex; flex-direction: column; gap: 0; }
|
|
194
|
+
.sheet__body:has(> .empty-state:only-child)::before { content: ""; flex: calc(var(--empty-state-at, 0) * 5) 1 0; }
|
|
195
|
+
.sheet__body:has(> .empty-state:only-child)::after { content: ""; flex: calc(5 - var(--empty-state-at, 0) * 5) 1 0; }
|
|
196
|
+
.sheet__body > .empty-state:only-child { flex: none; }
|
package/dist/cardds.css
CHANGED
|
@@ -717,6 +717,15 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
717
717
|
screens. Children shrink to the track; they never size the card. */
|
|
718
718
|
.card > * { min-width: 0; }
|
|
719
719
|
|
|
720
|
+
/* LEAVE — a card that goes: up and out with a small turn, as one piece (translate and rotate only, so nothing round it moves). The post desk's
|
|
721
|
+
card once `sent` and a `Card` with `leave` read the same two knobs; the time is --motion-sheet. `.card--leaver` is a card whose app may ask it
|
|
722
|
+
to go (it holds the transition, so it comes back as it went); `.card--away` is one that has been asked. A knob of the time would read dead:
|
|
723
|
+
the knob walk switches motion off */
|
|
724
|
+
:is(.card--leaver, .post-desk__card) { transition: translate var(--motion-sheet) ease, rotate var(--motion-sheet) ease; }
|
|
725
|
+
:is(.card--away, .post-desk--sent .post-desk__card) { --_y: calc(var(--leave-rise, 160%) * -1); rotate: var(--leave-turn, -5deg); }
|
|
726
|
+
.card--away { translate: 0 var(--_y); }
|
|
727
|
+
@media (prefers-reduced-motion: reduce) { :is(.card--leaver, .post-desk__card) { transition: none; } }
|
|
728
|
+
|
|
720
729
|
.card--2 { --card-bg: var(--card-2-bg); --card-ink: var(--card-2-ink); --card-muted: var(--card-2-muted); }
|
|
721
730
|
/* dark/accent slot: accent-toned controls would vanish — the slot re-points
|
|
722
731
|
the accent tokens, so every accent consumer (btn--primary, chip--on,
|
|
@@ -2287,10 +2296,15 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2287
2296
|
object-fit: cover;
|
|
2288
2297
|
}
|
|
2289
2298
|
/* an EMPTY STATE is a component in the content box too (Lh 2026-09-23 — it no longer floats in a Centre over the screen): alone in a
|
|
2290
|
-
Step it stands at the human centre of the content, 40% down (two parts of room above it, three below)
|
|
2291
|
-
|
|
2292
|
-
.step:has(> .empty-state)::
|
|
2299
|
+
Step it stands at the human centre of the content, 40% down (two parts of room above it, three below). --empty-state-at is the share of the
|
|
2300
|
+
free room that stands above it (a theme's 0.2 puts it a fifth down); a sheet's body holds an only EmptyState by the same share, but defaults to 0: at the top, as it always stood */
|
|
2301
|
+
.step:has(> .empty-state)::before { content: ""; flex: calc(var(--empty-state-at, 0.4) * 5) 1 0; }
|
|
2302
|
+
.step:has(> .empty-state)::after { content: ""; flex: calc(5 - var(--empty-state-at, 0.4) * 5) 1 0; }
|
|
2293
2303
|
.step > .empty-state { flex: none; }
|
|
2304
|
+
.sheet__body:has(> .empty-state:only-child) { display: flex; flex-direction: column; gap: 0; }
|
|
2305
|
+
.sheet__body:has(> .empty-state:only-child)::before { content: ""; flex: calc(var(--empty-state-at, 0) * 5) 1 0; }
|
|
2306
|
+
.sheet__body:has(> .empty-state:only-child)::after { content: ""; flex: calc(5 - var(--empty-state-at, 0) * 5) 1 0; }
|
|
2307
|
+
.sheet__body > .empty-state:only-child { flex: none; }
|
|
2294
2308
|
|
|
2295
2309
|
/* ---- css/forms.css — outlined fields, add-row ---- */
|
|
2296
2310
|
/* ============================================================
|
|
@@ -4627,10 +4641,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
4627
4641
|
width: calc(var(--_p) * 2);
|
|
4628
4642
|
aspect-ratio: 3 / 2;
|
|
4629
4643
|
translate: var(--_x) var(--_y);
|
|
4630
|
-
transition: translate var(--motion-sheet) ease, rotate var(--motion-sheet) ease;
|
|
4631
4644
|
}
|
|
4632
4645
|
.post-desk--address .post-desk__card { --_x: calc(100cqi - var(--_p) * 2); }
|
|
4633
|
-
|
|
4646
|
+
/* sent: the card leaves by card.css LEAVE, as a `Card` with `leave` does */
|
|
4634
4647
|
.post-desk__half { min-width: 0; min-height: 0; padding: var(--post-desk-pad, var(--sp-5)); display: flex; flex-direction: column; }
|
|
4635
4648
|
.post-desk__half--address { border-inline-start: var(--rule-w) solid var(--_rule); gap: var(--sp-4); }
|
|
4636
4649
|
.post-desk__write {
|
|
@@ -4699,7 +4712,6 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
4699
4712
|
.post-desk__note { margin: 0; font: var(--type-caption); color: var(--post-desk-note-ink, var(--on-surface-muted)); min-height: 1.35em; }
|
|
4700
4713
|
/* what stands where the card was, once it has left */
|
|
4701
4714
|
.post-desk__gone { position: absolute; inset: 0; display: grid; grid-template-columns: minmax(0, 1fr); align-content: center; gap: var(--sp-2); }
|
|
4702
|
-
@media (prefers-reduced-motion: reduce) { .post-desk__card { transition: none; } }
|
|
4703
4715
|
|
|
4704
4716
|
/* ============================================================
|
|
4705
4717
|
post-box — KEEPING many. The cards whole and landscape, standing one behind another; each shows its top edge: who, where,
|
|
@@ -5554,8 +5566,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
5554
5566
|
0 calc(var(--sticker-cut, 5px) + 4px) 0 0 color-mix(in srgb, var(--on-surface) 16%, transparent),
|
|
5555
5567
|
0 calc(var(--sticker-cut, 5px) + 12px) 20px -8px color-mix(in srgb, var(--on-surface) 22%, transparent);
|
|
5556
5568
|
}
|
|
5557
|
-
[data-finish="sticker"] .card:not(.sheet, .modal, .sheet-stack *, .card-list > *, .card-feed > *) { rotate: var(--sticker-tilt, 0.8deg); }
|
|
5558
|
-
[data-finish="sticker"] .card:not(.sheet, .modal, .sheet-stack *, .card-list > *, .card-feed > *):nth-of-type(even) { rotate: calc(-1 * var(--sticker-tilt, 0.8deg)); }
|
|
5569
|
+
[data-finish="sticker"] .card:not(.sheet, .modal, .card--away, .sheet-stack *, .card-list > *, .card-feed > *) { rotate: var(--sticker-tilt, 0.8deg); }
|
|
5570
|
+
[data-finish="sticker"] .card:not(.sheet, .modal, .card--away, .sheet-stack *, .card-list > *, .card-feed > *):nth-of-type(even) { rotate: calc(-1 * var(--sticker-tilt, 0.8deg)); }
|
|
5559
5571
|
[data-finish="sticker"] .card-list { gap: var(--sp-5); }
|
|
5560
5572
|
[data-finish="sticker"] .btn {
|
|
5561
5573
|
box-shadow: 0 3px 0 0 color-mix(in srgb, currentColor 30%, transparent);
|
package/dist/cards/Card.d.ts
CHANGED
|
@@ -40,6 +40,14 @@ export interface CardProps extends ComponentProps<'article'> {
|
|
|
40
40
|
footLink?: ReactNode;
|
|
41
41
|
/** set by `SheetStack` from the app's `open` and `refreshing`. The card renders its place, its grip and its pull. Leave it unset on any other card */
|
|
42
42
|
stack?: StackPlace;
|
|
43
|
+
/** THE CARD LEAVES — the card floats away: up and out with a small turn, as one piece with all it holds (a stamp that has just landed on it goes with it).
|
|
44
|
+
* The app sets it when its own move begins (the send went out) and does what follows in `onLeft`; if the send fails the app never sets it and the card
|
|
45
|
+
* stays. Set back to false the card stands again where it was. Controlled like an input: the app owns it, the card only moves. A card that has left
|
|
46
|
+
* takes no taps and is hidden from assistive tech (`inert`); the room round it does not change, so nothing jumps. A card in a box that clips (a
|
|
47
|
+
* scrolling `StackSheet` body clips at its edge) is cut there as it goes. A card that is a `SheetStack` sheet (`stack`) ignores it. Knobs `--leave-rise` · `--leave-turn` */
|
|
48
|
+
leave?: boolean;
|
|
49
|
+
/** 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 */
|
|
50
|
+
onLeft?: () => void;
|
|
43
51
|
}
|
|
44
52
|
/**
|
|
45
53
|
* Card — ONE boundary: head · content · foot. Cards are content the system offers (the words, the thing to read).
|
|
@@ -50,8 +58,9 @@ export interface CardProps extends ComponentProps<'article'> {
|
|
|
50
58
|
* 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
|
|
51
59
|
* 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.
|
|
52
60
|
*/
|
|
53
|
-
export declare function Card({ tone, paper, sm, row, cover, fold, centre, band, as, heading, caption, end, level, foot, footLink, stack, className, style, children, onClick, ref, ...rest }: CardProps): import("react").DetailedReactHTMLElement<{
|
|
61
|
+
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<{
|
|
54
62
|
onClick: (e: MouseEvent<HTMLElement>) => void;
|
|
63
|
+
inert?: boolean | undefined;
|
|
55
64
|
"aria-busy"?: (boolean | "true" | "false") | undefined;
|
|
56
65
|
ref: (node: HTMLElement | null) => void;
|
|
57
66
|
className: string;
|
|
@@ -965,7 +974,6 @@ export declare function Card({ tone, paper, sm, row, cover, fold, centre, band,
|
|
|
965
974
|
popover?: "" | "auto" | "manual" | "hint" | undefined;
|
|
966
975
|
popoverTargetAction?: "toggle" | "show" | "hide" | undefined;
|
|
967
976
|
popoverTarget?: string | undefined;
|
|
968
|
-
inert?: boolean | undefined;
|
|
969
977
|
inputMode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search" | undefined;
|
|
970
978
|
is?: string | undefined;
|
|
971
979
|
exportparts?: string | undefined;
|
package/dist/cards/Card.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Children, createElement, isValidElement, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Children, createElement, isValidElement, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
3
|
import { cx } from '../cx.js';
|
|
4
|
-
import { warnOnce } from '../state.js';
|
|
4
|
+
import { useLatest, warnOnce } from '../state.js';
|
|
5
5
|
import { CardHead } from './CardHead.js';
|
|
6
6
|
import { CardFoot } from './CardFoot.js';
|
|
7
7
|
import { goControl } from './cardGo.js';
|
|
@@ -15,7 +15,7 @@ import { StackHeadCtx, StackPullCtx, ownHead, stackPaint, useStackCard } from '.
|
|
|
15
15
|
* 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
16
|
* 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.
|
|
17
17
|
*/
|
|
18
|
-
export function Card({ tone, paper, sm, row, cover, fold, centre, band, as = 'article', heading, caption, end, level, foot, footLink, stack, className, style, children, onClick, ref, ...rest }) {
|
|
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 }) {
|
|
19
19
|
const head = heading != null || caption != null || end != null;
|
|
20
20
|
const tail = foot != null || footLink != null;
|
|
21
21
|
if (head || tail) {
|
|
@@ -37,6 +37,19 @@ export function Card({ tone, paper, sm, row, cover, fold, centre, band, as = 'ar
|
|
|
37
37
|
else if (passed)
|
|
38
38
|
passed.current = node;
|
|
39
39
|
};
|
|
40
|
+
const leaver = leave != null && !stack;
|
|
41
|
+
const away = leaver && leave;
|
|
42
|
+
const left = useLatest(onLeft);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const card = own.current;
|
|
45
|
+
if (!away || !card)
|
|
46
|
+
return;
|
|
47
|
+
let live = true;
|
|
48
|
+
const moving = card.getAnimations().filter((a) => a instanceof CSSTransition && (a.transitionProperty === 'translate' || a.transitionProperty === 'rotate')); // the leave's own; none where motion is off
|
|
49
|
+
Promise.all(moving.map((a) => a.finished)).then(() => { if (live)
|
|
50
|
+
left.current?.(); }, () => { }); // a transition cancelled by `leave` set back rejects
|
|
51
|
+
return () => { live = false; };
|
|
52
|
+
}, [away]);
|
|
40
53
|
const [go, setGo] = useState(false);
|
|
41
54
|
useLayoutEffect(() => { setGo(own.current != null && goControl(own.current) != null); }); // every render: what the card holds is its children's
|
|
42
55
|
const pressing = useRef(false);
|
|
@@ -77,9 +90,10 @@ export function Card({ tone, paper, sm, row, cover, fold, centre, band, as = 'ar
|
|
|
77
90
|
const props = {
|
|
78
91
|
...rest,
|
|
79
92
|
ref: setRef,
|
|
80
|
-
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', painted.className, className),
|
|
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),
|
|
81
94
|
style: painted.style || style ? { ...painted.style, ...style } : undefined,
|
|
82
95
|
...(stack ? { 'aria-busy': painted.busy } : {}),
|
|
96
|
+
...(away ? { inert: true } : {}),
|
|
83
97
|
onClick: click,
|
|
84
98
|
};
|
|
85
99
|
if (!head && !tail)
|