@usableapp/cardds 0.7.28 → 0.7.30
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 +10 -0
- package/dist/sheets/Sheet.js +31 -20
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
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.30 — 2026-09-29
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- `Sheet`: **the sheet comes up → its field has the cursor, always** (Lh 2026-09-29 — a habit of the Sheet, not of a screen). Whenever it rises — onto the screen (mounted up, or from `away`), or from peek to any open state, by a button, the handle, a drag or the app — once it has finished sliding the cursor goes to the field marked `data-autofocus` (SheetSteps marks its open step's first field), else the first field in its `SheetBody`. With no field, a sheet that came onto the screen gives it to its handle. A field that already has the cursor keeps it. Before: only an arrival from `away` (0.7.29: and a raise from peek) moved it, and only to a marked field. Nothing to change in a screen — remove any focus code of your own for a sheet's field.
|
|
9
|
+
|
|
10
|
+
## 0.7.29 — 2026-09-29
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `Sheet`: lowered to peek by its handle and raised again, the field marked to take the cursor (`data-autofocus` — the open `SheetStep`'s first field, e.g. the first `Pin` cell) takes it back after the slide. Before: only the first arrival did; raised again, the cursor stayed on the handle (timebank login, Lh 2026-09-29). A field that already has the cursor keeps it. Nothing to change in a screen.
|
|
14
|
+
|
|
5
15
|
## 0.7.28 — 2026-09-29
|
|
6
16
|
|
|
7
17
|
### Added
|
package/dist/sheets/Sheet.js
CHANGED
|
@@ -3,6 +3,7 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react
|
|
|
3
3
|
import { useControlled, useLatest, warnOnce, watchSize } from '../state.js';
|
|
4
4
|
import { cx } from '../cx.js';
|
|
5
5
|
const ORDER = ['peek', 'half', '3q', 'full']; // low → high; `away` is below them all and outside them
|
|
6
|
+
const RANK = { away: 0, peek: 1, half: 2, '3q': 3, full: 4 };
|
|
6
7
|
const TAP_REM = 0.5; // = --sp-2: a move shorter than this is a tap, not a drag
|
|
7
8
|
const ms = (t) => Math.max(0, ...t.split(',').map((v) => parseFloat(v) * (v.trim().endsWith('ms') ? 1 : 1000) || 0)); // a computed `0.35s, 0s` list → its longest, in ms
|
|
8
9
|
/**
|
|
@@ -26,7 +27,7 @@ export function Sheet({ state, defaultState = 'half', onStateChange, onSettled,
|
|
|
26
27
|
const ref = useRef(null);
|
|
27
28
|
const handleRef = useRef(null);
|
|
28
29
|
const home = useRef(current !== 'peek' && current !== 'away' ? current : 'half'); // where a tap from peek returns to
|
|
29
|
-
const was = useRef(
|
|
30
|
+
const was = useRef('away'); // mounted up = it came onto the screen: it rises from away like any arrival
|
|
30
31
|
const skipClick = useRef(false);
|
|
31
32
|
const [entered, setEntered] = useState(false); // false for the first frame: the class says `away`, so a sheet mounted up has somewhere to slide from
|
|
32
33
|
const shown = entered ? current : 'away';
|
|
@@ -77,29 +78,39 @@ export function Sheet({ state, defaultState = 'half', onStateChange, onSettled,
|
|
|
77
78
|
});
|
|
78
79
|
};
|
|
79
80
|
}, [latest]);
|
|
81
|
+
/* THE SHEET COMES UP → ITS FIELD HAS THE CURSOR, always (Lh 2026-09-29): whenever it rises — onto the screen (mounted up, or from `away`),
|
|
82
|
+
or from peek to any open state, by a button, the handle, a drag or the app — once it has finished sliding (Lh 2026-09-24: the keyboard
|
|
83
|
+
must not come up while it still moves) the cursor goes to the field marked `data-autofocus` (a SheetSteps marks its open step's first
|
|
84
|
+
field), else the body's first field; with no field, a sheet that came onto the screen gives it to its handle. A field that already
|
|
85
|
+
has the cursor keeps it. */
|
|
80
86
|
useEffect(() => {
|
|
81
87
|
if (current !== 'peek' && current !== 'away')
|
|
82
88
|
home.current = current;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const el = ref.current;
|
|
86
|
-
let done = false;
|
|
87
|
-
const land = () => {
|
|
88
|
-
if (done)
|
|
89
|
-
return;
|
|
90
|
-
done = true;
|
|
91
|
-
el?.removeEventListener('transitionend', onEnd);
|
|
92
|
-
const first = el?.querySelector('[data-autofocus]:not([inert] *)');
|
|
93
|
-
(first ?? handleRef.current)?.focus({ preventScroll: true });
|
|
94
|
-
};
|
|
95
|
-
const onEnd = (e) => { if (e.target === el && e.propertyName === 'translate')
|
|
96
|
-
land(); };
|
|
97
|
-
el?.addEventListener('transitionend', onEnd);
|
|
98
|
-
const t = setTimeout(land, 600); // no transition (reduced motion): land anyway
|
|
99
|
-
was.current = current;
|
|
100
|
-
return () => { clearTimeout(t); el?.removeEventListener('transitionend', onEnd); };
|
|
101
|
-
}
|
|
89
|
+
const rose = current !== 'peek' && current !== 'away' && RANK[current] > RANK[was.current];
|
|
90
|
+
const from = was.current;
|
|
102
91
|
was.current = current;
|
|
92
|
+
if (!rose)
|
|
93
|
+
return;
|
|
94
|
+
const el = ref.current;
|
|
95
|
+
let done = false;
|
|
96
|
+
const land = () => {
|
|
97
|
+
if (done || !el)
|
|
98
|
+
return;
|
|
99
|
+
done = true;
|
|
100
|
+
el.removeEventListener('transitionend', onEnd);
|
|
101
|
+
const at = document.activeElement;
|
|
102
|
+
if (at && el.contains(at) && at !== handleRef.current && at.matches('input, textarea, select, [contenteditable]'))
|
|
103
|
+
return;
|
|
104
|
+
const field = el.querySelector('[data-autofocus]:not([inert] *)')
|
|
105
|
+
?? el.querySelector(':scope > .sheet__body :is(input:not([type=checkbox], [type=radio], [type=hidden], [disabled]), textarea, select):not([inert] *)');
|
|
106
|
+
(field ?? (from === 'away' ? handleRef.current : null))?.focus({ preventScroll: true });
|
|
107
|
+
};
|
|
108
|
+
const onEnd = (e) => { if (e.target === el && e.propertyName === 'translate')
|
|
109
|
+
land(); };
|
|
110
|
+
el?.addEventListener('transitionend', onEnd);
|
|
111
|
+
const t = setTimeout(land, 600); // no transition (reduced motion): land anyway
|
|
112
|
+
return () => { clearTimeout(t); el?.removeEventListener('transitionend', onEnd); if (!done)
|
|
113
|
+
was.current = from; }; // cut short (StrictMode's trial run, or a new state mid-slide): the rise still counts from where it began
|
|
103
114
|
}, [current]);
|
|
104
115
|
/* geometry, read once per gesture — never stored: how far it has slid, and where each state rests, in px from its top at full */
|
|
105
116
|
const slid = (el) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.30",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "card-first mobile design system, React-first: the components in src/ are thin wrappers over the CSS contract (css/*.css stays the only truth); gallery/ shows every story live (npm run dev), tests/ measures the geometry.",
|