@usableapp/cardds 0.7.80 → 0.7.81
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 +8 -0
- package/README.md +9 -2
- package/css/actions.css +11 -0
- package/css/stack.css +5 -4
- package/dist/actions/IconBtnGroup.d.ts +13 -2
- package/dist/actions/IconBtnGroup.js +18 -5
- package/dist/cardds.css +16 -4
- package/dist/cards/Card.js +4 -3
- package/dist/catalog.json +1 -1
- package/dist/sheets/stackCard.d.ts +2 -0
- package/dist/sheets/stackCard.js +7 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
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.81 — 2026-10-10
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **`IconBtnGroup form` and `form labelled`: a small panel of its own width in the middle, with its words** (Lh 2026-10-10, from timebank's make-stamp page: two chevrons walking nine ground colours stretched over the whole column, with no words) — before → after: an `IconBtnGroup` as a child of a `Page`, a `Centre` or a `CtaPack` was always the column's full width, and its `label` was spoken only, so "words + a small control" had to be hand-laid → `form` makes the panel as wide as its buttons (each one tap, `--icon-btn-group-form-min-w`; a button with a word beside its icon is as wide as the word) and stands it in the middle of its column the way a `Btn form` is; a card's or a sheet's foot no longer stretches it. `labelled` (with `form`) SHOWS the `label` as words at the panel's left: one line, centred together, the words in the label's type and plain ink outside the frame, the gap `--icon-btn-group-gap` (default `--sp-3`), the group named by those words. Where the line is too narrow (a long label, 160% text on a 320px phone) the words wrap above the panel and both stay centred. To use it: `<IconBtnGroup form labelled label="เปลี่ยนสีพื้น"><IconBtn icon="chevron-left" label="สีก่อนหน้า" onClick={…} /><IconBtn icon="chevron-right" label="สีถัดไป" onClick={…} /></IconBtnGroup>`; without the words, `<IconBtnGroup form label="…">`. With `labelled` the root is the line that holds the words and the panel (`className`, `style`, `ref` and the knobs go there). `form` with `vertical` keeps `form` and warns; `labelled` without `form` (or without a `label`) warns and draws no words. Without `form` nothing changes. Stories `IconBtnGroup · Form`, `FormInAFoot`, `FormInASheetFoot`, `FormInACentre`, `FormInACtaPack`, `FormLongWords`; `StampScreens · MakeStamp` now walks its ground with it.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **A card in a sheet's body folded the sheet when its heading was clicked** (timebank, 2026-10-10) — before → after: a neighbour `Card heading caption foot={<Btn/>}` written inside an open `StackSheet` (or `Home` sheet) ran its button on a click on its heading, and the same click folded the sheet, because the sheet took any heading under it for its own title; a pull down that began on that heading asked for no refresh (`onRefresh`), because any head under the sheet's body was refused; and the stack's head styling reached the nested card's head (`position: relative`, a `padding-left` transition). → only the sheet's own title folds it, its own back button closes it, a pull may begin on a nested card's heading and asks for the refresh, and a nested card's head is the card's as anywhere. A click on the nested card's heading, caption or empty room presses its one button and the sheet stays open. The heads of the cards resting in a closed stack's `base` lose the same `position: relative`; nothing in them was placed by it. Nothing to change in a screen. Story `SheetStack · CardInSheet`.
|
|
12
|
+
|
|
5
13
|
## 0.7.80 — 2026-10-10
|
|
6
14
|
|
|
7
15
|
### Changed
|
package/README.md
CHANGED
|
@@ -487,7 +487,12 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
|
|
|
487
487
|
paragraph. Frameless, per rule 9.
|
|
488
488
|
- **Foot** (`.card__foot`): the card's action buttons on one line. One
|
|
489
489
|
button takes the width; up to four share it in even parts; from three
|
|
490
|
-
on they drop their side padding so the labels keep the room.
|
|
490
|
+
on they drop their side padding so the labels keep the room. A panel of
|
|
491
|
+
up to four icon buttons (`IconBtnGroup`) takes the foot the same way; with
|
|
492
|
+
`form` it is as wide as its buttons (one tap each) and stands in the middle,
|
|
493
|
+
as `Btn form` does — in a foot, a `Page`, a `Centre` or a `CtaPack` — and
|
|
494
|
+
with `form labelled` its `label` is SHOWN as words at its left, the two
|
|
495
|
+
centred as one line (Lh 2026-10-10).
|
|
491
496
|
- In React the head and the foot are the card's **slots**:
|
|
492
497
|
`<Card heading caption end foot footLink>` with the content as children —
|
|
493
498
|
the card writes the order (head first, foot last). A head of its own shape
|
|
@@ -751,7 +756,9 @@ question and hunts for its answer. Rules 11–13 serve that:
|
|
|
751
756
|
receipt: items left, prices right; `.card__head`, `.kv`, `.row`); centred
|
|
752
757
|
only what is not read — dots, a lone number, an icon, a mark. Empty room is
|
|
753
758
|
not filled with more content; a new edge is made instead (line the buttons
|
|
754
|
-
up with the avatar).
|
|
759
|
+
up with the avatar). One exception: a small control with its own words
|
|
760
|
+
(`IconBtnGroup form labelled`) is one centred line — the words name the
|
|
761
|
+
control, they are not content to be read.
|
|
755
762
|
12. **Show, don't explain.** Where a known picture carries it — an avatar for a
|
|
756
763
|
person, an icon, a `Ring`/`Track`/`NumberBand`, a `Chip` for a category — use it
|
|
757
764
|
instead of words. Never add a label, a caption line, a comparison or a
|
package/css/actions.css
CHANGED
|
@@ -114,6 +114,17 @@
|
|
|
114
114
|
.icon-btn-group--vertical { flex-direction: column; flex: none; width: var(--tap); }
|
|
115
115
|
.icon-btn-group--vertical > .icon-btn { flex: 1 0 auto; min-height: var(--tap); }
|
|
116
116
|
.icon-btn-group--vertical > .icon-btn + .icon-btn { border-inline-start: 0; border-block-start: var(--border-w) solid var(--icon-btn-group-rule, var(--icon-btn-group-border, var(--outline))); }
|
|
117
|
+
/* --form: the panel as wide as its buttons (each one --tap, or its word if wider), in the middle of its column the way `.btn--form` is, next to the
|
|
118
|
+
panel across the row (Lh 2026-10-10, from timebank: two chevrons stretched over the whole column to walk nine colours). The container
|
|
119
|
+
does not stretch it: a foot's `flex: 1 1 0` is undone for it below, as the foot undoes it for `.btn--form` (card.css). */
|
|
120
|
+
.icon-btn-group--form { flex: 0 1 auto; justify-self: center; margin-inline: auto; } /* no align-self: a sheet's foot stands its buttons on the bottom edge by `align-items: end` */
|
|
121
|
+
.icon-btn-group--form > .icon-btn { flex: 0 1 auto; width: auto; min-width: var(--icon-btn-group-form-min-w, var(--tap)); }
|
|
122
|
+
.card__foot > :is(.icon-btn-group--form, .icon-btn-line) { flex: 0 1 auto; }
|
|
123
|
+
/* --labelled: the label's words, then the panel, ONE line in the middle (the words are read, not pressed: no frame, plain ink); too narrow → the words wrap
|
|
124
|
+
above the panel, both centred */
|
|
125
|
+
.icon-btn-line { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--icon-btn-group-gap, var(--sp-3)); min-width: 0; justify-self: center; margin-inline: auto; }
|
|
126
|
+
.icon-btn-line__words { font: var(--type-label); text-align: center; min-width: 0; }
|
|
127
|
+
.icon-btn-line > .icon-btn-group--form { margin-inline: 0; }
|
|
117
128
|
.icon-btn-group > .icon-btn[aria-pressed="true"] { background: var(--icon-btn-group-on-bg, var(--accent)); color: var(--icon-btn-group-on-ink, var(--on-accent)); } /* `select`: a pressed button STAYS filled (Lh 2026-09-29) */
|
|
118
129
|
.icon-btn-group > .icon-btn:active:not(:disabled) { scale: 1; opacity: var(--press-dim); } /* a part of one frame DIMS, it never shrinks inside it */
|
|
119
130
|
|
package/css/stack.css
CHANGED
|
@@ -369,12 +369,13 @@
|
|
|
369
369
|
into it — an opacity fade in place, never a slide in from the left (the
|
|
370
370
|
slide read as a second thing moving while the sheet itself moved; Lh,
|
|
371
371
|
2026-09-11). Only the sheet travels; the button appears where it is. */
|
|
372
|
-
|
|
372
|
+
/* a sheet's OWN head and back: the card is the stack's child or, with one host wrapper, the wrapper's. A card standing in the sheet's body wears its own head */
|
|
373
|
+
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head {
|
|
373
374
|
position: relative;
|
|
374
375
|
padding-left: 0;
|
|
375
376
|
transition: padding-left var(--motion-sheet) ease;
|
|
376
377
|
}
|
|
377
|
-
.sheet-stack--tap .sheet-stack__back {
|
|
378
|
+
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head > .sheet-stack__back {
|
|
378
379
|
position: absolute;
|
|
379
380
|
left: 0;
|
|
380
381
|
top: 50%;
|
|
@@ -389,7 +390,7 @@
|
|
|
389
390
|
min-height: 0;
|
|
390
391
|
padding: 0;
|
|
391
392
|
}
|
|
392
|
-
.sheet-stack--tap > .card.is-open .sheet-stack__back {
|
|
393
|
+
.sheet-stack--tap > .card.is-open > .card__head > .sheet-stack__back {
|
|
393
394
|
opacity: 1;
|
|
394
395
|
visibility: visible;
|
|
395
396
|
}
|
|
@@ -512,7 +513,7 @@
|
|
|
512
513
|
/* AT THE END (Lh 2026-09-29, the home): after the tabs, chevron down — the open sheet folds down. The head's end gives it its room
|
|
513
514
|
while open; the last tab then sits on the inset (no reach into the padding) */
|
|
514
515
|
.sheet-stack--tap .sheet-stack__back--end { left: auto; right: 0; }
|
|
515
|
-
.sheet-stack--tap .card__head:has(> .sheet-stack__back--end) { transition: padding-right var(--motion-sheet) ease; }
|
|
516
|
+
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head:has(> .sheet-stack__back--end) { transition: padding-right var(--motion-sheet) ease; }
|
|
516
517
|
.sheet-stack--tap > .card.is-open > .card__head:has(> .sheet-stack__back--end) { padding-right: var(--stack-back-room, calc(var(--_back-size) + var(--sp-2))); }
|
|
517
518
|
.sheet-stack--tap > .card.is-open > .card__head:has(> .sheet-stack__back--end) > .stack-sheet__tabs { margin-right: 0; }
|
|
518
519
|
.sheet-stack--tap .sheet-stack__back > .icon { width: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); height: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); flex: none; } /* 26: the chevron fills the small disc (Lh 2026-09-23) */
|
|
@@ -14,11 +14,22 @@ export interface IconBtnGroupProps extends Omit<ComponentProps<'div'>, 'onChange
|
|
|
14
14
|
onChange?: (value: Picked) => void;
|
|
15
15
|
/** the buttons one OVER the other, the panel one button wide — at a `Row`'s `end`, where a panel across would take the words' room */
|
|
16
16
|
vertical?: boolean;
|
|
17
|
+
/** the panel as wide as its buttons (each one tap wide, or as wide as its word), in the middle of its column the way a `Btn form` is —
|
|
18
|
+
* in a `Page`, a `Centre`, a `CtaPack`, and in a card's or a sheet's foot, where it keeps that width instead of filling the row.
|
|
19
|
+
* Without it the panel is the width of what it stands in. Not with `vertical`: that is already one button wide, at a row's end —
|
|
20
|
+
* `form` wins and warns */
|
|
21
|
+
form?: boolean;
|
|
22
|
+
/** with `form`: SHOW the `label` as words at the panel's left, on one line, the two centred together — plain ink in the label's type,
|
|
23
|
+
* outside the frame, and the words are the group's name (`aria-labelledby`). Where the line is too narrow (a big text size on a small
|
|
24
|
+
* phone) the words wrap ABOVE the panel and both stay centred */
|
|
25
|
+
labelled?: boolean;
|
|
17
26
|
}
|
|
18
27
|
/** IconBtnGroup — a panel of up to FOUR icon-only buttons in ONE frame, sharing its width, a rule between them in the frame's
|
|
19
28
|
* colour (Lh 2026-09-29). Each is an `IconBtn` (its `label` is its spoken name); a WORD may stand beside the icon as its child
|
|
20
29
|
* (`<IconBtn label="ชาย"><Icon name="man" />ชาย</IconBtn>`) — two named sides of a `Panes`. Plain: each does its own thing on its onClick.
|
|
21
30
|
* With `select`, a pressed button stays filled (aria-pressed): `one` or `many`. At home in a card's or a sheet's foot;
|
|
22
|
-
* `vertical` at a row's end
|
|
23
|
-
|
|
31
|
+
* `vertical` at a row's end; `form` as a small control of its own in the middle of a `Page` or a `Centre`
|
|
32
|
+
* (`<IconBtnGroup form labelled label="เปลี่ยนสีพื้น">` = the words, then the narrow panel, both centred). With `labelled` the root
|
|
33
|
+
* is the line holding the words and the panel — `className`, `style` and `ref` go there, the knobs too. */
|
|
34
|
+
export declare function IconBtnGroup({ label, labelled, form, select, value, defaultValue, onChange, vertical, className, children, ...rest }: IconBtnGroupProps): import("react").JSX.Element;
|
|
24
35
|
export {};
|
|
@@ -1,15 +1,25 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Children, cloneElement, isValidElement } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, cloneElement, isValidElement, useId } from 'react';
|
|
3
3
|
import { cx } from '../cx.js';
|
|
4
4
|
import { useControlled, warnOnce } from '../state.js';
|
|
5
5
|
/** IconBtnGroup — a panel of up to FOUR icon-only buttons in ONE frame, sharing its width, a rule between them in the frame's
|
|
6
6
|
* colour (Lh 2026-09-29). Each is an `IconBtn` (its `label` is its spoken name); a WORD may stand beside the icon as its child
|
|
7
7
|
* (`<IconBtn label="ชาย"><Icon name="man" />ชาย</IconBtn>`) — two named sides of a `Panes`. Plain: each does its own thing on its onClick.
|
|
8
8
|
* With `select`, a pressed button stays filled (aria-pressed): `one` or `many`. At home in a card's or a sheet's foot;
|
|
9
|
-
* `vertical` at a row's end
|
|
10
|
-
|
|
9
|
+
* `vertical` at a row's end; `form` as a small control of its own in the middle of a `Page` or a `Centre`
|
|
10
|
+
* (`<IconBtnGroup form labelled label="เปลี่ยนสีพื้น">` = the words, then the narrow panel, both centred). With `labelled` the root
|
|
11
|
+
* is the line holding the words and the panel — `className`, `style` and `ref` go there, the knobs too. */
|
|
12
|
+
export function IconBtnGroup({ label, labelled, form, select, value, defaultValue, onChange, vertical, className, children, ...rest }) {
|
|
13
|
+
const words = useId();
|
|
11
14
|
if (Children.count(children) > 4)
|
|
12
15
|
warnOnce('icon-btn-group', 'cardds IconBtnGroup: more than 4 buttons — keep 4, put the rest in a Menu behind one of them');
|
|
16
|
+
if (form && vertical)
|
|
17
|
+
warnOnce('icon-btn-group-form-vertical', 'cardds IconBtnGroup: `form` and `vertical` together — `vertical` is already one button wide at a row\'s end, `form` is the panel in the middle of its column; `form` is kept, `vertical` ignored');
|
|
18
|
+
if (labelled && !form)
|
|
19
|
+
warnOnce('icon-btn-group-labelled', 'cardds IconBtnGroup: `labelled` is for `form` (the words and the narrow panel as one centred line) — without `form` the label stays the spoken name only');
|
|
20
|
+
if (labelled && form && !label)
|
|
21
|
+
warnOnce('icon-btn-group-labelled-words', 'cardds IconBtnGroup: `labelled` shows the `label` — it has none, so no words are drawn');
|
|
22
|
+
const line = !!(form && labelled && label);
|
|
13
23
|
const [shown, request] = useControlled(value, defaultValue ?? (select === 'many' ? [] : null), onChange);
|
|
14
24
|
const on = (v) => (Array.isArray(shown) ? shown.includes(v) : shown === v);
|
|
15
25
|
const items = !select ? children : Children.map(children, (c) => {
|
|
@@ -32,5 +42,8 @@ export function IconBtnGroup({ label, select, value, defaultValue, onChange, ver
|
|
|
32
42
|
},
|
|
33
43
|
});
|
|
34
44
|
});
|
|
35
|
-
|
|
45
|
+
const panel = (_jsx("div", { role: "group", "aria-label": line ? undefined : label, "aria-labelledby": line ? words : undefined, className: cx('icon-btn-group', form && 'icon-btn-group--form', vertical && !form && 'icon-btn-group--vertical', !line && className), ...(line ? {} : rest), children: items }));
|
|
46
|
+
if (!line)
|
|
47
|
+
return panel;
|
|
48
|
+
return _jsxs("div", { className: cx('icon-btn-line', className), ...rest, children: [_jsx("span", { id: words, className: "icon-btn-line__words", children: label }), panel] });
|
|
36
49
|
}
|
package/dist/cardds.css
CHANGED
|
@@ -1435,12 +1435,13 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1435
1435
|
into it — an opacity fade in place, never a slide in from the left (the
|
|
1436
1436
|
slide read as a second thing moving while the sheet itself moved; Lh,
|
|
1437
1437
|
2026-09-11). Only the sheet travels; the button appears where it is. */
|
|
1438
|
-
|
|
1438
|
+
/* a sheet's OWN head and back: the card is the stack's child or, with one host wrapper, the wrapper's. A card standing in the sheet's body wears its own head */
|
|
1439
|
+
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head {
|
|
1439
1440
|
position: relative;
|
|
1440
1441
|
padding-left: 0;
|
|
1441
1442
|
transition: padding-left var(--motion-sheet) ease;
|
|
1442
1443
|
}
|
|
1443
|
-
.sheet-stack--tap .sheet-stack__back {
|
|
1444
|
+
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head > .sheet-stack__back {
|
|
1444
1445
|
position: absolute;
|
|
1445
1446
|
left: 0;
|
|
1446
1447
|
top: 50%;
|
|
@@ -1455,7 +1456,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1455
1456
|
min-height: 0;
|
|
1456
1457
|
padding: 0;
|
|
1457
1458
|
}
|
|
1458
|
-
.sheet-stack--tap > .card.is-open .sheet-stack__back {
|
|
1459
|
+
.sheet-stack--tap > .card.is-open > .card__head > .sheet-stack__back {
|
|
1459
1460
|
opacity: 1;
|
|
1460
1461
|
visibility: visible;
|
|
1461
1462
|
}
|
|
@@ -1578,7 +1579,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
1578
1579
|
/* AT THE END (Lh 2026-09-29, the home): after the tabs, chevron down — the open sheet folds down. The head's end gives it its room
|
|
1579
1580
|
while open; the last tab then sits on the inset (no reach into the padding) */
|
|
1580
1581
|
.sheet-stack--tap .sheet-stack__back--end { left: auto; right: 0; }
|
|
1581
|
-
.sheet-stack--tap .card__head:has(> .sheet-stack__back--end) { transition: padding-right var(--motion-sheet) ease; }
|
|
1582
|
+
:is(.sheet-stack--tap > .card, .sheet-stack--tap > :not(.card, .sheet-stack__base) > .card) > .card__head:has(> .sheet-stack__back--end) { transition: padding-right var(--motion-sheet) ease; }
|
|
1582
1583
|
.sheet-stack--tap > .card.is-open > .card__head:has(> .sheet-stack__back--end) { padding-right: var(--stack-back-room, calc(var(--_back-size) + var(--sp-2))); }
|
|
1583
1584
|
.sheet-stack--tap > .card.is-open > .card__head:has(> .sheet-stack__back--end) > .stack-sheet__tabs { margin-right: 0; }
|
|
1584
1585
|
.sheet-stack--tap .sheet-stack__back > .icon { width: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); height: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); flex: none; } /* 26: the chevron fills the small disc (Lh 2026-09-23) */
|
|
@@ -2542,6 +2543,17 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2542
2543
|
.icon-btn-group--vertical { flex-direction: column; flex: none; width: var(--tap); }
|
|
2543
2544
|
.icon-btn-group--vertical > .icon-btn { flex: 1 0 auto; min-height: var(--tap); }
|
|
2544
2545
|
.icon-btn-group--vertical > .icon-btn + .icon-btn { border-inline-start: 0; border-block-start: var(--border-w) solid var(--icon-btn-group-rule, var(--icon-btn-group-border, var(--outline))); }
|
|
2546
|
+
/* --form: the panel as wide as its buttons (each one --tap, or its word if wider), in the middle of its column the way `.btn--form` is, next to the
|
|
2547
|
+
panel across the row (Lh 2026-10-10, from timebank: two chevrons stretched over the whole column to walk nine colours). The container
|
|
2548
|
+
does not stretch it: a foot's `flex: 1 1 0` is undone for it below, as the foot undoes it for `.btn--form` (card.css). */
|
|
2549
|
+
.icon-btn-group--form { flex: 0 1 auto; justify-self: center; margin-inline: auto; } /* no align-self: a sheet's foot stands its buttons on the bottom edge by `align-items: end` */
|
|
2550
|
+
.icon-btn-group--form > .icon-btn { flex: 0 1 auto; width: auto; min-width: var(--icon-btn-group-form-min-w, var(--tap)); }
|
|
2551
|
+
.card__foot > :is(.icon-btn-group--form, .icon-btn-line) { flex: 0 1 auto; }
|
|
2552
|
+
/* --labelled: the label's words, then the panel, ONE line in the middle (the words are read, not pressed: no frame, plain ink); too narrow → the words wrap
|
|
2553
|
+
above the panel, both centred */
|
|
2554
|
+
.icon-btn-line { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--icon-btn-group-gap, var(--sp-3)); min-width: 0; justify-self: center; margin-inline: auto; }
|
|
2555
|
+
.icon-btn-line__words { font: var(--type-label); text-align: center; min-width: 0; }
|
|
2556
|
+
.icon-btn-line > .icon-btn-group--form { margin-inline: 0; }
|
|
2545
2557
|
.icon-btn-group > .icon-btn[aria-pressed="true"] { background: var(--icon-btn-group-on-bg, var(--accent)); color: var(--icon-btn-group-on-ink, var(--on-accent)); } /* `select`: a pressed button STAYS filled (Lh 2026-09-29) */
|
|
2546
2558
|
.icon-btn-group > .icon-btn:active:not(:disabled) { scale: 1; opacity: var(--press-dim); } /* a part of one frame DIMS, it never shrinks inside it */
|
|
2547
2559
|
|
package/dist/cards/Card.js
CHANGED
|
@@ -5,7 +5,7 @@ import { 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';
|
|
8
|
-
import { StackHeadCtx, StackPullCtx, stackPaint, useStackCard } from '../sheets/stackCard.js';
|
|
8
|
+
import { StackHeadCtx, StackPullCtx, ownHead, stackPaint, useStackCard } from '../sheets/stackCard.js';
|
|
9
9
|
/**
|
|
10
10
|
* Card — ONE boundary: head · content · foot. Cards are content the system offers (the words, the thing to read).
|
|
11
11
|
* The head and the foot are SLOTS: `heading` · `caption` · `end` (the card writes its CardHead first) and `foot` · `footLink`
|
|
@@ -61,11 +61,12 @@ export function Card({ tone, paper, sm, row, cover, fold, centre, band, as = 'ar
|
|
|
61
61
|
}
|
|
62
62
|
if (!stack || e.defaultPrevented || swallow.current)
|
|
63
63
|
return;
|
|
64
|
-
|
|
64
|
+
const head = card && ownHead(card, target); // the sheet's own head: a card standing in its body has a heading and a back of its own
|
|
65
|
+
if (head && target.closest('.sheet-stack__back')) {
|
|
65
66
|
stack.onClose();
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
68
|
-
if (stack.open && target.closest('.card__head > :is(h1, h2, h3)')) {
|
|
69
|
+
if (head && stack.open && target.closest('.card__head > :is(h1, h2, h3)')) {
|
|
69
70
|
stack.onClose();
|
|
70
71
|
return;
|
|
71
72
|
} // the title folds it; the back stays the keyboard's way
|