@usableapp/cardds 0.7.78 → 0.7.79

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 CHANGED
@@ -2,6 +2,12 @@
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.79 — 2026-10-10
6
+
7
+ ### Changed
8
+ - **A closed stack's base too short for what rests in it scrolls** (Lh 2026-10-10) — before → after: loose things in `SheetStack base` / `Home base` stood on the heads, so on a short phone a content taller than the room ran out over the room's top, cut, and nothing scrolled (two feed cards at 320×568) → nothing is squeezed, the upper thing shows whole from the room's top, the rest lies under the sheet heads, and the member scrolls the room up until the last thing stands clear of the heads by `--stack-base-gap`. A base that holds its content is unchanged, to the pixel, and still takes no scroll and no pull. One `Page` and one `Centre` in a base are unchanged. Nothing to do in an app; a base no longer needs its own short-phone layout.
9
+ - **`FeedCard foot` takes more than one button** — one button: the big card is pressed whole, as before. Two or more (a question: yes · no): only the buttons act. Small, the card hides them all. Story `CardFeed · Question`.
10
+
5
11
  ## 0.7.78 — 2026-10-10
6
12
 
7
13
  ### Added
package/README.md CHANGED
@@ -209,6 +209,13 @@ the card is the material, the sheet is what it is in the stack.
209
209
  last one as between two of them (`--stack-base-gap`); one `Page` reads from
210
210
  the top, one `Centre` stands in the middle. A card standing there is flat:
211
211
  the base clips at its edges, so a shadow would end in a straight line.
212
+ A room too short for the loose things in it squeezes nothing: the upper
213
+ thing shows whole from the room's top, the rest lies under the heads, and
214
+ the room scrolls up until the last thing stands clear of the heads by
215
+ `--stack-base-gap`. `SheetStack` measures this and renders `is-long` on the
216
+ base; only then does the base scroll and run under the first sheet's round
217
+ corners. A room that holds its content does not scroll, and a finger on it
218
+ moves nothing.
212
219
  A tap, or a pull up, opens any sheet over it. The open title or its back
213
220
  button, tapped or pulled down, drops it home. A pull down in the open
214
221
  content, already at the top, asks `onRefresh` — omit it and that pull only
@@ -511,7 +518,8 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
511
518
  rendering it. Which card is big is controlled like an input: `big` (a
512
519
  card's `id`) + `onBigChange(id, 'tap' | 'queue')`, or `defaultBig`. A
513
520
  `FeedCard` is `id` · `heading` · `caption` · `icon` (its thumb, in both
514
- sizes) · `tone` · `foot` (one `Btn`) · children (the content); its size is
521
+ sizes) · `tone` · `foot` (one `Btn`: the big card is pressed whole; two
522
+ or more: only the buttons act) · children (the content); its size is
515
523
  the feed's, never a prop. Knobs `--card-feed-gap`, `--feed-card-head-h`.
516
524
  Reduced motion: nothing slides or fades. Stories `CardFeed · Default`,
517
525
  `Home · Feed`.
package/css/base.css CHANGED
@@ -70,8 +70,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
70
70
 
71
71
  /* scrollbar hidden, scroll kept: the track scrolls by touch/drag, never by a visible bar —
72
72
  .bleed (card.css) is the one exception, its bar is designed, not hidden */
73
- :where(html, .sheet-stack--tap > .card, .step > .pages, .picker__rail, .avatar-pick) { scrollbar-width: none; }
74
- :where(html, .sheet-stack--tap > .card, .step > .pages, .picker__rail, .avatar-pick)::-webkit-scrollbar { display: none; }
73
+ :where(html, .sheet-stack--tap > .card, .sheet-stack__base, .step > .pages, .picker__rail, .avatar-pick) { scrollbar-width: none; }
74
+ :where(html, .sheet-stack--tap > .card, .sheet-stack__base, .step > .pages, .picker__rail, .avatar-pick)::-webkit-scrollbar { display: none; }
75
75
 
76
76
  /* icon from icons.svg sprite: <svg class="icon"><use href="icons.svg#bell"/></svg> */
77
77
  .icon { width: var(--icon-size, var(--icon)); height: var(--icon-size, var(--icon)); flex: none; }
package/css/stack.css CHANGED
@@ -278,7 +278,8 @@
278
278
  grid-area: 1 / 1;
279
279
  align-self: start;
280
280
  z-index: 0;
281
- height: var(--_headroom, 0px);
281
+ --_under: 0px;
282
+ height: calc(var(--_headroom, 0px) + var(--_under));
282
283
  padding-inline: var(--screen-pad);
283
284
  --_gap: var(--card-gap, var(--sp-5));
284
285
  display: grid;
@@ -298,7 +299,26 @@
298
299
  .sheet-stack__base > .screen__centre { padding-inline: 0; }
299
300
  /* loose things rest against the peeks, so the room under the last one is the room between two of them (Lh 2026-10-09: a card's
300
301
  bottom edge lay on the first sheet's top edge). A lone Page reads from the top and a lone Centre stands in the middle: neither rests there. */
301
- .sheet-stack__base:not(:has(> .page:only-child, > .screen__centre:only-child)) { padding-bottom: var(--stack-base-gap, var(--_gap)); }
302
+ .sheet-stack__base:not(:has(> .page:only-child, > .screen__centre:only-child)) {
303
+ padding-bottom: calc(var(--stack-base-gap, var(--_gap)) + var(--_under));
304
+ /* the first row takes the free room and its thing stands at that row's end: on the peeks while there is room, from the room's TOP when
305
+ there is none. `align-content: end` ran a long content out over the top, where no scroll reaches; the `safe` keyword is not relied
306
+ on, the floor is Safari 16.4. */
307
+ grid-template-rows: 1fr;
308
+ align-content: start;
309
+ align-items: end;
310
+ }
311
+ /* a room too short for what rests in it (Lh 2026-10-10; `is-long`, measured and rendered by SheetStack): nothing is squeezed, the upper
312
+ thing shows whole, the rest lies under the sheets and the room scrolls up to it. Only then: a room that holds its content keeps
313
+ touch-action: none, so a finger there pulls nothing. The box runs under the first sheet by its corner's radius, or a straight cut at
314
+ the sheet's top edge would show ground in the two round corners; the padding grows by the same, so the last thing still scrolls
315
+ clear of the heads by the gap, and the content box (cqh) is the one it was. */
316
+ .sheet-stack__base.is-long:not(:has(> .page:only-child, > .screen__centre:only-child)) {
317
+ --_under: var(--card-radius, var(--r-card));
318
+ overflow-y: auto;
319
+ overscroll-behavior: contain;
320
+ touch-action: pan-y;
321
+ }
302
322
  /* a card standing in this room is FLAT (Lh 2026-10-09): the room clips at its own edges, and a card's shadow cut there drew a straight
303
323
  line under the bar and along the first sheet. Its ground and its radius are what say "card" here. */
304
324
  :is([data-palette], [data-finish="lit"]) .sheet-stack__base .card { box-shadow: none; }
@@ -333,7 +353,7 @@
333
353
  .sheet-stack--flood { --_bar: calc(var(--top-h, var(--header-h)) + var(--_bar-gap)); }
334
354
  .sheet-stack--flood > .sheet-stack__base {
335
355
  margin-top: var(--_bar);
336
- height: calc(var(--_headroom, 0px) - var(--_bar));
356
+ height: calc(var(--_headroom, 0px) - var(--_bar) + var(--_under));
337
357
  }
338
358
  .screen--fill:has(> .sheet-stack--flood > .stack-card.is-open, > .sheet-stack--flood > :not(.stack-card) > .stack-card.is-open) > :is(.appbar, .topbar):first-child {
339
359
  visibility: hidden;
package/dist/cardds.css CHANGED
@@ -540,8 +540,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
540
540
 
541
541
  /* scrollbar hidden, scroll kept: the track scrolls by touch/drag, never by a visible bar —
542
542
  .bleed (card.css) is the one exception, its bar is designed, not hidden */
543
- :where(html, .sheet-stack--tap > .card, .step > .pages, .picker__rail, .avatar-pick) { scrollbar-width: none; }
544
- :where(html, .sheet-stack--tap > .card, .step > .pages, .picker__rail, .avatar-pick)::-webkit-scrollbar { display: none; }
543
+ :where(html, .sheet-stack--tap > .card, .sheet-stack__base, .step > .pages, .picker__rail, .avatar-pick) { scrollbar-width: none; }
544
+ :where(html, .sheet-stack--tap > .card, .sheet-stack__base, .step > .pages, .picker__rail, .avatar-pick)::-webkit-scrollbar { display: none; }
545
545
 
546
546
  /* icon from icons.svg sprite: <svg class="icon"><use href="icons.svg#bell"/></svg> */
547
547
  .icon { width: var(--icon-size, var(--icon)); height: var(--icon-size, var(--icon)); flex: none; }
@@ -1341,7 +1341,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
1341
1341
  grid-area: 1 / 1;
1342
1342
  align-self: start;
1343
1343
  z-index: 0;
1344
- height: var(--_headroom, 0px);
1344
+ --_under: 0px;
1345
+ height: calc(var(--_headroom, 0px) + var(--_under));
1345
1346
  padding-inline: var(--screen-pad);
1346
1347
  --_gap: var(--card-gap, var(--sp-5));
1347
1348
  display: grid;
@@ -1361,7 +1362,26 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
1361
1362
  .sheet-stack__base > .screen__centre { padding-inline: 0; }
1362
1363
  /* loose things rest against the peeks, so the room under the last one is the room between two of them (Lh 2026-10-09: a card's
1363
1364
  bottom edge lay on the first sheet's top edge). A lone Page reads from the top and a lone Centre stands in the middle: neither rests there. */
1364
- .sheet-stack__base:not(:has(> .page:only-child, > .screen__centre:only-child)) { padding-bottom: var(--stack-base-gap, var(--_gap)); }
1365
+ .sheet-stack__base:not(:has(> .page:only-child, > .screen__centre:only-child)) {
1366
+ padding-bottom: calc(var(--stack-base-gap, var(--_gap)) + var(--_under));
1367
+ /* the first row takes the free room and its thing stands at that row's end: on the peeks while there is room, from the room's TOP when
1368
+ there is none. `align-content: end` ran a long content out over the top, where no scroll reaches; the `safe` keyword is not relied
1369
+ on, the floor is Safari 16.4. */
1370
+ grid-template-rows: 1fr;
1371
+ align-content: start;
1372
+ align-items: end;
1373
+ }
1374
+ /* a room too short for what rests in it (Lh 2026-10-10; `is-long`, measured and rendered by SheetStack): nothing is squeezed, the upper
1375
+ thing shows whole, the rest lies under the sheets and the room scrolls up to it. Only then: a room that holds its content keeps
1376
+ touch-action: none, so a finger there pulls nothing. The box runs under the first sheet by its corner's radius, or a straight cut at
1377
+ the sheet's top edge would show ground in the two round corners; the padding grows by the same, so the last thing still scrolls
1378
+ clear of the heads by the gap, and the content box (cqh) is the one it was. */
1379
+ .sheet-stack__base.is-long:not(:has(> .page:only-child, > .screen__centre:only-child)) {
1380
+ --_under: var(--card-radius, var(--r-card));
1381
+ overflow-y: auto;
1382
+ overscroll-behavior: contain;
1383
+ touch-action: pan-y;
1384
+ }
1365
1385
  /* a card standing in this room is FLAT (Lh 2026-10-09): the room clips at its own edges, and a card's shadow cut there drew a straight
1366
1386
  line under the bar and along the first sheet. Its ground and its radius are what say "card" here. */
1367
1387
  :is([data-palette], [data-finish="lit"]) .sheet-stack__base .card { box-shadow: none; }
@@ -1396,7 +1416,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
1396
1416
  .sheet-stack--flood { --_bar: calc(var(--top-h, var(--header-h)) + var(--_bar-gap)); }
1397
1417
  .sheet-stack--flood > .sheet-stack__base {
1398
1418
  margin-top: var(--_bar);
1399
- height: calc(var(--_headroom, 0px) - var(--_bar));
1419
+ height: calc(var(--_headroom, 0px) - var(--_bar) + var(--_under));
1400
1420
  }
1401
1421
  .screen--fill:has(> .sheet-stack--flood > .stack-card.is-open, > .sheet-stack--flood > :not(.stack-card) > .stack-card.is-open) > :is(.appbar, .topbar):first-child {
1402
1422
  visibility: hidden;
@@ -32,7 +32,7 @@ export interface FeedCardProps extends Omit<ComponentProps<'article'>, 'id' | 'r
32
32
  icon?: IconName;
33
33
  /** the tone slot — see `Tone` */
34
34
  tone?: Tone;
35
- /** the card's button (one `Btn`) — shown only while the card is big, and then the card is pressed whole: a touch anywhere on it runs this button */
35
+ /** the card's button, or its buttons — shown only while the card is big. ONE `Btn`: the big card is pressed whole, a touch anywhere on it runs that button. Two or more (a question: yes · no): only the buttons act, a touch elsewhere on the card does nothing */
36
36
  foot?: ReactNode;
37
37
  /** the content — words, a picture, a Stamp. Shown only while the card is big */
38
38
  children?: ReactNode;
@@ -41,6 +41,6 @@ export interface FeedCardProps extends Omit<ComponentProps<'article'>, 'id' | 'r
41
41
  * FeedCard — ONE card of a `CardFeed`'s queue, the same markup in both sizes: its head (thumb · heading · caption) always shows, its
42
42
  * content and its button are one part that opens while the card is big. The SIZE IS THE FEED'S, never a prop. Small, the card itself
43
43
  * is the control (a button named by its heading; Enter and Space) and its closed part cannot be pressed, focused or read. Big, it is a
44
- * plain card: its one control is its foot's button, so it is pressed whole.
44
+ * plain card: with one button in its foot it is pressed whole; with two or more only the buttons act.
45
45
  */
46
46
  export declare function FeedCard({ id, heading, caption, icon, tone, foot, className, children, onClick, onKeyDown, ref, ...rest }: FeedCardProps): import("react").JSX.Element;
@@ -74,7 +74,7 @@ const onSmallKey = (e) => {
74
74
  * FeedCard — ONE card of a `CardFeed`'s queue, the same markup in both sizes: its head (thumb · heading · caption) always shows, its
75
75
  * content and its button are one part that opens while the card is big. The SIZE IS THE FEED'S, never a prop. Small, the card itself
76
76
  * is the control (a button named by its heading; Enter and Space) and its closed part cannot be pressed, focused or read. Big, it is a
77
- * plain card: its one control is its foot's button, so it is pressed whole.
77
+ * plain card: with one button in its foot it is pressed whole; with two or more only the buttons act.
78
78
  */
79
79
  export function FeedCard({ id, heading, caption, icon, tone, foot, className, children, onClick, onKeyDown, ref, ...rest }) {
80
80
  const feed = useContext(FeedCtx);