@usableapp/cardds 0.7.52 → 0.7.54
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 +18 -0
- package/README.md +19 -9
- package/css/sheet.css +44 -14
- package/css/stack.css +29 -1
- package/css/theme.template.css +1 -1
- package/css/tokens.css +5 -5
- package/dist/cardds.css +78 -20
- package/dist/cards/Card.d.ts +868 -6
- package/dist/cards/Card.js +43 -7
- package/dist/cards/CardHead.d.ts +1 -6
- package/dist/cards/CardHead.js +26 -1
- package/dist/choice/Slider.js +9 -13
- package/dist/forms/Composer.js +3 -1
- package/dist/forms/Field.js +3 -1
- package/dist/forms/FileBtn.js +3 -1
- package/dist/forms/Pin.js +3 -1
- package/dist/sheets/Sheet.d.ts +7 -1
- package/dist/sheets/Sheet.js +2 -2
- package/dist/sheets/SheetStack.d.ts +6 -5
- package/dist/sheets/SheetStack.js +60 -410
- package/dist/sheets/SheetSteps.js +9 -13
- package/dist/sheets/StackSheet.d.ts +4 -1
- package/dist/sheets/StackSheet.js +2 -2
- package/dist/sheets/stackCard.d.ts +31 -0
- package/dist/sheets/stackCard.js +389 -0
- package/dist/sheets/stepCursor.d.ts +12 -0
- package/dist/sheets/stepCursor.js +15 -0
- package/dist/templates/Home.d.ts +3 -1
- package/dist/templates/Home.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/* one sheet of a tap stack — its own behaviour, not exported from the package.
|
|
2
|
+
The app says which sheet is open and whether its data is in (`SheetStack`'s `open` and `refreshing`).
|
|
3
|
+
This card renders what those two mean: the place classes, the grip, the pull. Nothing stamps them on afterwards. */
|
|
4
|
+
import { createContext, useEffect, useLayoutEffect, useRef } from 'react';
|
|
5
|
+
import { flushSync } from 'react-dom';
|
|
6
|
+
import { focusWhenActive } from '../state.js';
|
|
7
|
+
const SLOP_REM = 0.5;
|
|
8
|
+
const COMMIT_REM = 4.5;
|
|
9
|
+
/** the head's title is a tab stop while this sheet is the one Enter acts on: every title when none is open, only the open one otherwise */
|
|
10
|
+
export const StackHeadCtx = createContext(null);
|
|
11
|
+
export function stackPaint(place) {
|
|
12
|
+
if (!place)
|
|
13
|
+
return {};
|
|
14
|
+
const loading = place.open && place.refreshing;
|
|
15
|
+
const className = ['stack-card', place.open && 'is-open', place.before && 'is-before', place.after && 'is-after', loading && 'is-loading'].filter(Boolean).join(' ');
|
|
16
|
+
return {
|
|
17
|
+
className,
|
|
18
|
+
style: place.before ? { '--stagger': String(place.stagger) } : undefined,
|
|
19
|
+
busy: loading || undefined,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const pxFromRem = (rem) => parseFloat(getComputedStyle(document.documentElement).fontSize) * rem;
|
|
23
|
+
/* the pull's reach is the room the sheet still has to travel — measured, not a class */
|
|
24
|
+
const sheetsIn = (stack) => [...stack.children]
|
|
25
|
+
.filter((c) => !c.classList.contains('sheet-stack__base'))
|
|
26
|
+
.map((c) => (c.classList.contains('card') ? c : c.querySelector(':scope > .card')))
|
|
27
|
+
.filter((c) => c instanceof HTMLElement);
|
|
28
|
+
const restPullPx = (card, stack) => {
|
|
29
|
+
if (!stack.classList.contains('sheet-stack--closed'))
|
|
30
|
+
return card.offsetTop;
|
|
31
|
+
const sheets = sheetsIn(stack);
|
|
32
|
+
const step = sheets[1]?.offsetTop ?? 0;
|
|
33
|
+
return Math.max(0, stack.clientHeight - sheets.length * step) + card.offsetTop;
|
|
34
|
+
};
|
|
35
|
+
const readFrame = (el) => { el.getBoundingClientRect(); };
|
|
36
|
+
const along = (dir, startY, y) => (dir === 'down' ? Math.max(0, y - startY) : Math.max(0, startY - y));
|
|
37
|
+
const pullsHead = (target, head) => {
|
|
38
|
+
if (!(target instanceof Element))
|
|
39
|
+
return false;
|
|
40
|
+
return target === head || !!target.closest('.sheet-stack__back') || !!target.closest('.card__head > :is(h1, h2, h3)');
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The card's own pull, refresh and focus. Listeners sit on this card's head and body.
|
|
44
|
+
* `--_pull` / `--_lift` are written while a finger is down, the same way a single Sheet writes `--_drag`.
|
|
45
|
+
*/
|
|
46
|
+
export function useStackCard(ref, place, swallow) {
|
|
47
|
+
const placeRef = useRef(place);
|
|
48
|
+
placeRef.current = place;
|
|
49
|
+
const live = useRef(null);
|
|
50
|
+
const headPull = useRef(null);
|
|
51
|
+
const bodyPull = useRef(null);
|
|
52
|
+
useLayoutEffect(() => {
|
|
53
|
+
const el = ref.current;
|
|
54
|
+
if (!el || !place)
|
|
55
|
+
return;
|
|
56
|
+
const g = live.current;
|
|
57
|
+
if (g) {
|
|
58
|
+
el.classList.add('is-dragging');
|
|
59
|
+
el.style.setProperty(g.prop, g.px);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
/* a finger let go: the prop leaves in this commit, so the sheet eases from the frame just read */
|
|
63
|
+
el.style.removeProperty('--_pull');
|
|
64
|
+
el.style.removeProperty('--_lift');
|
|
65
|
+
});
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (!place?.open || !ref.current)
|
|
68
|
+
return;
|
|
69
|
+
const cancel = focusWhenActive(ref.current, { scope: ':scope > .sheet__body' });
|
|
70
|
+
return () => { cancel(); };
|
|
71
|
+
}, [place?.open, ref]);
|
|
72
|
+
/* open: a pull down on the head folds it; a pull down in the content, already at the top, asks for a reload */
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const card = ref.current;
|
|
75
|
+
const here = placeRef.current;
|
|
76
|
+
if (!card || !here?.open)
|
|
77
|
+
return;
|
|
78
|
+
const stack = card.closest('.sheet-stack');
|
|
79
|
+
const head = card.querySelector(':scope > .card__head');
|
|
80
|
+
const scroller = card.querySelector(':scope > .sheet__body') ?? card;
|
|
81
|
+
let stopDoc = () => { };
|
|
82
|
+
const listen = (move, up, cancel) => {
|
|
83
|
+
stopDoc();
|
|
84
|
+
document.addEventListener('pointermove', move, { passive: false });
|
|
85
|
+
document.addEventListener('pointerup', up);
|
|
86
|
+
document.addEventListener('pointercancel', cancel);
|
|
87
|
+
stopDoc = () => {
|
|
88
|
+
document.removeEventListener('pointermove', move);
|
|
89
|
+
document.removeEventListener('pointerup', up);
|
|
90
|
+
document.removeEventListener('pointercancel', cancel);
|
|
91
|
+
stopDoc = () => { };
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
const primary = (e) => e.isPrimary && e.button === 0;
|
|
95
|
+
const armSwallow = () => {
|
|
96
|
+
swallow.current = true;
|
|
97
|
+
requestAnimationFrame(() => { swallow.current = false; });
|
|
98
|
+
};
|
|
99
|
+
const release = (prop) => {
|
|
100
|
+
live.current = null;
|
|
101
|
+
card.classList.remove('is-dragging');
|
|
102
|
+
readFrame(card);
|
|
103
|
+
card.style.removeProperty(prop);
|
|
104
|
+
};
|
|
105
|
+
const onHeadDown = (e) => {
|
|
106
|
+
e.stopPropagation();
|
|
107
|
+
if (!head || !stack || !primary(e) || headPull.current || bodyPull.current)
|
|
108
|
+
return;
|
|
109
|
+
if (!pullsHead(e.target, head))
|
|
110
|
+
return;
|
|
111
|
+
const slop = pxFromRem(SLOP_REM);
|
|
112
|
+
const commit = pxFromRem(COMMIT_REM);
|
|
113
|
+
const pull = { pointerId: e.pointerId, startY: e.clientY, last: 0, cap: Math.max(restPullPx(card, stack), commit), commit, slop, moved: false };
|
|
114
|
+
headPull.current = pull;
|
|
115
|
+
const onMove = (ev) => {
|
|
116
|
+
if (ev.pointerId !== pull.pointerId || headPull.current !== pull)
|
|
117
|
+
return;
|
|
118
|
+
const dy = along('down', pull.startY, ev.clientY);
|
|
119
|
+
pull.last = dy;
|
|
120
|
+
if (dy < pull.slop)
|
|
121
|
+
return;
|
|
122
|
+
pull.moved = true;
|
|
123
|
+
ev.preventDefault();
|
|
124
|
+
try {
|
|
125
|
+
if (head && !head.hasPointerCapture(ev.pointerId))
|
|
126
|
+
head.setPointerCapture(ev.pointerId);
|
|
127
|
+
}
|
|
128
|
+
catch { /* the pointer is already gone */ }
|
|
129
|
+
live.current = { prop: '--_pull', px: `${Math.min(dy, pull.cap)}px` };
|
|
130
|
+
card.classList.add('is-dragging');
|
|
131
|
+
card.style.setProperty('--_pull', live.current.px);
|
|
132
|
+
};
|
|
133
|
+
const onUp = (ev) => {
|
|
134
|
+
if (ev.pointerId !== pull.pointerId || headPull.current !== pull)
|
|
135
|
+
return;
|
|
136
|
+
const dy = Math.max(pull.last, along('down', pull.startY, ev.clientY));
|
|
137
|
+
const moved = pull.moved || dy >= pull.slop;
|
|
138
|
+
headPull.current = null;
|
|
139
|
+
stopDoc();
|
|
140
|
+
if (moved && dy >= pull.commit) {
|
|
141
|
+
card.classList.remove('is-dragging');
|
|
142
|
+
readFrame(card);
|
|
143
|
+
live.current = null;
|
|
144
|
+
flushSync(() => placeRef.current?.onClose());
|
|
145
|
+
if (card.isConnected && card.classList.contains('is-open') && card.style.getPropertyValue('--_pull'))
|
|
146
|
+
release('--_pull');
|
|
147
|
+
}
|
|
148
|
+
else
|
|
149
|
+
release('--_pull');
|
|
150
|
+
if (moved)
|
|
151
|
+
armSwallow();
|
|
152
|
+
};
|
|
153
|
+
const onCancel = (ev) => {
|
|
154
|
+
if (ev.pointerId !== pull.pointerId || headPull.current !== pull)
|
|
155
|
+
return;
|
|
156
|
+
headPull.current = null;
|
|
157
|
+
stopDoc();
|
|
158
|
+
release('--_pull');
|
|
159
|
+
};
|
|
160
|
+
listen(onMove, onUp, onCancel);
|
|
161
|
+
};
|
|
162
|
+
const onBodyDown = (e) => {
|
|
163
|
+
const ask = placeRef.current;
|
|
164
|
+
if (!primary(e) || headPull.current || bodyPull.current || !ask?.onRefresh || (ask.open && ask.refreshing))
|
|
165
|
+
return;
|
|
166
|
+
if (scroller.scrollTop > 0)
|
|
167
|
+
return;
|
|
168
|
+
if (e.target instanceof Element && e.target.closest('.card__head'))
|
|
169
|
+
return;
|
|
170
|
+
const slop = pxFromRem(SLOP_REM);
|
|
171
|
+
const commit = pxFromRem(COMMIT_REM);
|
|
172
|
+
const pull = { pointerId: e.pointerId, startX: e.clientX, startY: e.clientY, lastDy: 0, commit, slop, moved: false, drop: false };
|
|
173
|
+
bodyPull.current = pull;
|
|
174
|
+
const onMove = (ev) => {
|
|
175
|
+
if (ev.pointerId !== pull.pointerId || bodyPull.current !== pull)
|
|
176
|
+
return;
|
|
177
|
+
if (scroller.scrollTop > 0) {
|
|
178
|
+
pull.drop = true;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (pull.drop)
|
|
182
|
+
return;
|
|
183
|
+
const dx = ev.clientX - pull.startX;
|
|
184
|
+
const dy = ev.clientY - pull.startY;
|
|
185
|
+
pull.lastDy = Math.max(0, dy);
|
|
186
|
+
if (!pull.moved && !pull.drop) {
|
|
187
|
+
if (Math.max(Math.abs(dx), Math.abs(dy)) < pull.slop)
|
|
188
|
+
return;
|
|
189
|
+
if (Math.abs(dx) > dy || dy <= 0) {
|
|
190
|
+
pull.drop = true;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
pull.moved = true;
|
|
195
|
+
ev.preventDefault();
|
|
196
|
+
try {
|
|
197
|
+
if (!scroller.hasPointerCapture(ev.pointerId))
|
|
198
|
+
scroller.setPointerCapture(ev.pointerId);
|
|
199
|
+
}
|
|
200
|
+
catch { /* the pointer is already gone */ }
|
|
201
|
+
scroller.classList.add('is-refreshing');
|
|
202
|
+
scroller.style.setProperty('--_refresh', `${Math.min(dy, pull.commit)}px`);
|
|
203
|
+
};
|
|
204
|
+
const finish = () => {
|
|
205
|
+
scroller.classList.remove('is-refreshing');
|
|
206
|
+
scroller.style.removeProperty('--_refresh');
|
|
207
|
+
};
|
|
208
|
+
const onUp = (ev) => {
|
|
209
|
+
if (ev.pointerId !== pull.pointerId || bodyPull.current !== pull)
|
|
210
|
+
return;
|
|
211
|
+
const dy = Math.max(pull.lastDy, Math.max(0, ev.clientY - pull.startY));
|
|
212
|
+
const go = pull.moved && !pull.drop && scroller.scrollTop <= 0 && dy >= pull.commit;
|
|
213
|
+
bodyPull.current = null;
|
|
214
|
+
stopDoc();
|
|
215
|
+
readFrame(scroller);
|
|
216
|
+
finish();
|
|
217
|
+
if (go)
|
|
218
|
+
placeRef.current?.onRefresh?.();
|
|
219
|
+
if (pull.moved)
|
|
220
|
+
armSwallow();
|
|
221
|
+
};
|
|
222
|
+
const onCancel = (ev) => {
|
|
223
|
+
if (ev.pointerId !== pull.pointerId || bodyPull.current !== pull)
|
|
224
|
+
return;
|
|
225
|
+
bodyPull.current = null;
|
|
226
|
+
stopDoc();
|
|
227
|
+
finish();
|
|
228
|
+
};
|
|
229
|
+
listen(onMove, onUp, onCancel);
|
|
230
|
+
};
|
|
231
|
+
/* a finger: the browser takes a touch for its own scroll at the first move unless that move is refused here */
|
|
232
|
+
let touch = null;
|
|
233
|
+
const onTouchStart = (e) => {
|
|
234
|
+
const t = e.touches[0];
|
|
235
|
+
const ask = placeRef.current;
|
|
236
|
+
const inHead = e.target instanceof Element && !!e.target.closest('.card__head');
|
|
237
|
+
touch = e.touches.length === 1 && t && ask?.onRefresh && !(ask.open && ask.refreshing) && scroller.scrollTop <= 0 && !inHead
|
|
238
|
+
? { x: t.clientX, y: t.clientY, ours: null } : null;
|
|
239
|
+
};
|
|
240
|
+
const onTouchMove = (e) => {
|
|
241
|
+
const t = e.touches[0];
|
|
242
|
+
if (!touch || !t)
|
|
243
|
+
return;
|
|
244
|
+
const dx = t.clientX - touch.x;
|
|
245
|
+
const dy = t.clientY - touch.y;
|
|
246
|
+
if (touch.ours == null)
|
|
247
|
+
touch.ours = dy > 0 && dy >= Math.abs(dx) && scroller.scrollTop <= 0;
|
|
248
|
+
if (touch.ours && e.cancelable)
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
};
|
|
251
|
+
head?.addEventListener('pointerdown', onHeadDown);
|
|
252
|
+
scroller.addEventListener('pointerdown', onBodyDown);
|
|
253
|
+
scroller.addEventListener('touchstart', onTouchStart, { passive: true });
|
|
254
|
+
scroller.addEventListener('touchmove', onTouchMove, { passive: false });
|
|
255
|
+
return () => {
|
|
256
|
+
scroller.removeEventListener('touchstart', onTouchStart);
|
|
257
|
+
scroller.removeEventListener('touchmove', onTouchMove);
|
|
258
|
+
stopDoc();
|
|
259
|
+
headPull.current = null;
|
|
260
|
+
bodyPull.current = null;
|
|
261
|
+
if (live.current?.prop === '--_pull')
|
|
262
|
+
release('--_pull');
|
|
263
|
+
head?.removeEventListener('pointerdown', onHeadDown);
|
|
264
|
+
scroller.removeEventListener('pointerdown', onBodyDown);
|
|
265
|
+
};
|
|
266
|
+
}, [place?.open, place?.refreshing, !!place?.onRefresh, ref, swallow]);
|
|
267
|
+
/* none open: a pull up on a peeked head follows the finger and, past the commit, opens this sheet */
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
const card = ref.current;
|
|
270
|
+
const here = placeRef.current;
|
|
271
|
+
if (!card || !here || here.open || here.before || here.after)
|
|
272
|
+
return;
|
|
273
|
+
const stack = card.closest('.sheet-stack');
|
|
274
|
+
const head = card.querySelector(':scope > .card__head');
|
|
275
|
+
if (!head || !stack)
|
|
276
|
+
return;
|
|
277
|
+
let stopDoc = () => { };
|
|
278
|
+
const listen = (move, up, cancel) => {
|
|
279
|
+
stopDoc();
|
|
280
|
+
document.addEventListener('pointermove', move, { passive: false });
|
|
281
|
+
document.addEventListener('pointerup', up);
|
|
282
|
+
document.addEventListener('pointercancel', cancel);
|
|
283
|
+
stopDoc = () => {
|
|
284
|
+
document.removeEventListener('pointermove', move);
|
|
285
|
+
document.removeEventListener('pointerup', up);
|
|
286
|
+
document.removeEventListener('pointercancel', cancel);
|
|
287
|
+
stopDoc = () => { };
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
const armSwallow = () => {
|
|
291
|
+
swallow.current = true;
|
|
292
|
+
requestAnimationFrame(() => { swallow.current = false; });
|
|
293
|
+
};
|
|
294
|
+
const release = () => {
|
|
295
|
+
live.current = null;
|
|
296
|
+
card.classList.remove('is-dragging');
|
|
297
|
+
readFrame(card);
|
|
298
|
+
card.style.removeProperty('--_lift');
|
|
299
|
+
};
|
|
300
|
+
const onDown = (e) => {
|
|
301
|
+
if (!e.isPrimary || e.button !== 0 || headPull.current)
|
|
302
|
+
return;
|
|
303
|
+
if (!pullsHead(e.target, head)) {
|
|
304
|
+
if (!(e.target instanceof Element) || !e.target.closest('button, a'))
|
|
305
|
+
return;
|
|
306
|
+
const startY = e.clientY;
|
|
307
|
+
const id = e.pointerId;
|
|
308
|
+
const slop = pxFromRem(SLOP_REM);
|
|
309
|
+
const move = (ev) => {
|
|
310
|
+
if (ev.pointerId !== id)
|
|
311
|
+
return;
|
|
312
|
+
if (Math.abs(ev.clientY - startY) >= slop)
|
|
313
|
+
swallow.current = true;
|
|
314
|
+
};
|
|
315
|
+
const up = (ev) => {
|
|
316
|
+
if (ev.pointerId !== id)
|
|
317
|
+
return;
|
|
318
|
+
document.removeEventListener('pointermove', move);
|
|
319
|
+
document.removeEventListener('pointerup', up);
|
|
320
|
+
if (swallow.current)
|
|
321
|
+
requestAnimationFrame(() => { swallow.current = false; });
|
|
322
|
+
};
|
|
323
|
+
document.addEventListener('pointermove', move);
|
|
324
|
+
document.addEventListener('pointerup', up);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
e.stopPropagation();
|
|
328
|
+
const slop = pxFromRem(SLOP_REM);
|
|
329
|
+
const commit = pxFromRem(COMMIT_REM);
|
|
330
|
+
const travel = card.getBoundingClientRect().top - stack.getBoundingClientRect().top;
|
|
331
|
+
const pull = { pointerId: e.pointerId, startY: e.clientY, last: 0, cap: Math.max(travel, commit), commit, slop, moved: false };
|
|
332
|
+
headPull.current = pull;
|
|
333
|
+
const onMove = (ev) => {
|
|
334
|
+
if (ev.pointerId !== pull.pointerId || headPull.current !== pull)
|
|
335
|
+
return;
|
|
336
|
+
const upPx = along('up', pull.startY, ev.clientY);
|
|
337
|
+
pull.last = upPx;
|
|
338
|
+
if (upPx < pull.slop)
|
|
339
|
+
return;
|
|
340
|
+
pull.moved = true;
|
|
341
|
+
ev.preventDefault();
|
|
342
|
+
try {
|
|
343
|
+
if (!head.hasPointerCapture(ev.pointerId))
|
|
344
|
+
head.setPointerCapture(ev.pointerId);
|
|
345
|
+
}
|
|
346
|
+
catch { /* the pointer is already gone */ }
|
|
347
|
+
live.current = { prop: '--_lift', px: `${Math.min(upPx, pull.cap)}px` };
|
|
348
|
+
card.classList.add('is-dragging');
|
|
349
|
+
card.style.setProperty('--_lift', live.current.px);
|
|
350
|
+
};
|
|
351
|
+
const onUp = (ev) => {
|
|
352
|
+
if (ev.pointerId !== pull.pointerId || headPull.current !== pull)
|
|
353
|
+
return;
|
|
354
|
+
const upPx = Math.max(pull.last, along('up', pull.startY, ev.clientY));
|
|
355
|
+
const moved = pull.moved || upPx >= pull.slop;
|
|
356
|
+
headPull.current = null;
|
|
357
|
+
stopDoc();
|
|
358
|
+
if (moved && upPx >= pull.commit) {
|
|
359
|
+
card.classList.remove('is-dragging');
|
|
360
|
+
readFrame(card);
|
|
361
|
+
live.current = null;
|
|
362
|
+
flushSync(() => placeRef.current?.onOpen());
|
|
363
|
+
if (card.isConnected && !card.classList.contains('is-open') && card.style.getPropertyValue('--_lift'))
|
|
364
|
+
release();
|
|
365
|
+
}
|
|
366
|
+
else
|
|
367
|
+
release();
|
|
368
|
+
if (moved)
|
|
369
|
+
armSwallow();
|
|
370
|
+
};
|
|
371
|
+
const onCancel = (ev) => {
|
|
372
|
+
if (ev.pointerId !== pull.pointerId || headPull.current !== pull)
|
|
373
|
+
return;
|
|
374
|
+
headPull.current = null;
|
|
375
|
+
stopDoc();
|
|
376
|
+
release();
|
|
377
|
+
};
|
|
378
|
+
listen(onMove, onUp, onCancel);
|
|
379
|
+
};
|
|
380
|
+
head.addEventListener('pointerdown', onDown);
|
|
381
|
+
return () => {
|
|
382
|
+
stopDoc();
|
|
383
|
+
headPull.current = null;
|
|
384
|
+
if (live.current?.prop === '--_lift')
|
|
385
|
+
release();
|
|
386
|
+
head.removeEventListener('pointerdown', onDown);
|
|
387
|
+
};
|
|
388
|
+
}, [place?.open, place?.before, place?.after, ref, swallow]);
|
|
389
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface StepCursorValue {
|
|
2
|
+
parked: boolean;
|
|
3
|
+
/** cleared by the open step at the start of its render; the first field this render writes its id, and keeps the mark if it renders again on its own */
|
|
4
|
+
slot: {
|
|
5
|
+
id: string | null;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export declare const StepCursor: import("react").Context<StepCursorValue | null>;
|
|
9
|
+
export declare function useStepCursor(): {
|
|
10
|
+
'data-autofocus'?: '';
|
|
11
|
+
'data-cursor'?: '';
|
|
12
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* the open step's first field renders its own cursor marks. Sheet reads `data-autofocus` when it rises;
|
|
2
|
+
`data-cursor` is the caret a phone shows while the keyboard waits on the steps' holder. */
|
|
3
|
+
import { createContext, useContext, useId } from 'react';
|
|
4
|
+
export const StepCursor = createContext(null);
|
|
5
|
+
export function useStepCursor() {
|
|
6
|
+
const ctx = useContext(StepCursor);
|
|
7
|
+
const id = useId();
|
|
8
|
+
if (!ctx)
|
|
9
|
+
return {};
|
|
10
|
+
if (ctx.slot.id == null)
|
|
11
|
+
ctx.slot.id = id;
|
|
12
|
+
if (ctx.slot.id !== id)
|
|
13
|
+
return {};
|
|
14
|
+
return ctx.parked ? { 'data-autofocus': '', 'data-cursor': '' } : { 'data-autofocus': '' };
|
|
15
|
+
}
|
package/dist/templates/Home.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export interface HomeProps extends Omit<ComponentProps<typeof Screen>, 'children
|
|
|
31
31
|
onOpenChange?: (open: number | null) => void;
|
|
32
32
|
/** the open sheet's scroller was already at the top and the finger pulled down past the commit. The stack asks with that index. It does not fetch. Omit it and a pull in the body only scrolls */
|
|
33
33
|
onRefresh?: (index: number) => void;
|
|
34
|
+
/** the sheet the app is reloading (its index), or `null` — as `SheetStack refreshing` */
|
|
35
|
+
refreshing?: number | null;
|
|
34
36
|
/** CONTENT above the sheet heads — one `Page`: a welcome, the news, a picture. It reads
|
|
35
37
|
* from the top; a sheet coming up covers it */
|
|
36
38
|
base?: ReactNode;
|
|
@@ -50,4 +52,4 @@ export interface HomeProps extends Omit<ComponentProps<typeof Screen>, 'children
|
|
|
50
52
|
* at the bottom). A tap on a head brings that sheet up to fill everything under the top. Only the open sheet's body is rendered — three
|
|
51
53
|
* sections on one home never cost three pages (`keep` holds a body once opened).
|
|
52
54
|
*/
|
|
53
|
-
export declare function Home({ top, title, sheets, open: openProp, defaultOpen, onOpenChange, onRefresh, base, keep, backLabel, backAt, children, ...rest }: HomeProps): import("react").JSX.Element;
|
|
55
|
+
export declare function Home({ top, title, sheets, open: openProp, defaultOpen, onOpenChange, onRefresh, refreshing, base, keep, backLabel, backAt, children, ...rest }: HomeProps): import("react").JSX.Element;
|
package/dist/templates/Home.js
CHANGED
|
@@ -15,7 +15,7 @@ const LEAVE = 800; // ms: a closing sheet keeps its body until it has slid home
|
|
|
15
15
|
* at the bottom). A tap on a head brings that sheet up to fill everything under the top. Only the open sheet's body is rendered — three
|
|
16
16
|
* sections on one home never cost three pages (`keep` holds a body once opened).
|
|
17
17
|
*/
|
|
18
|
-
export function Home({ top, title, sheets, open: openProp, defaultOpen = null, onOpenChange, onRefresh, base, keep, backLabel = word('back'), backAt, children, ...rest }) {
|
|
18
|
+
export function Home({ top, title, sheets, open: openProp, defaultOpen = null, onOpenChange, onRefresh, refreshing, base, keep, backLabel = word('back'), backAt, children, ...rest }) {
|
|
19
19
|
const [open, change] = useControlled(openProp, defaultOpen, onOpenChange);
|
|
20
20
|
const [leaving, setLeaving] = useState(null);
|
|
21
21
|
const [seen, setSeen] = useState(() => new Set(open != null ? [open] : []));
|
|
@@ -35,5 +35,5 @@ export function Home({ top, title, sheets, open: openProp, defaultOpen = null, o
|
|
|
35
35
|
}, [leaving]);
|
|
36
36
|
const [tabs, setTabs] = useState(() => Object.fromEntries(sheets.map((x, i) => [i, x.defaultTab ?? x.tabs?.[0]?.id])));
|
|
37
37
|
const shows = (i) => i === open || i === leaving || (keep && seen.has(i));
|
|
38
|
-
return (_jsxs(Screen, { fill: true, top: top, ...rest, children: [_jsx(Text, { level: "h1", as: "h1", sr: true, children: title }), _jsx(SheetStack, { closed: true, open: open, onOpenChange: change, onRefresh: onRefresh, base: base, children: sheets.map((s, i) => (_jsx(StackSheet, { tone: s.tone ?? TONES[i], title: s.title, icon: s.icon, tabs: s.tabs, tab: tabs[i], onTabChange: (t) => setTabs((v) => ({ ...v, [i]: t })), backLabel: backLabel, backAt: backAt, foot: typeof s.foot === 'function' ? s.foot(tabs[i]) : s.foot, children: shows(i) ? (typeof s.body === 'function' ? s.body(tabs[i]) : s.body) : null }, i))) }), children] }));
|
|
38
|
+
return (_jsxs(Screen, { fill: true, top: top, ...rest, children: [_jsx(Text, { level: "h1", as: "h1", sr: true, children: title }), _jsx(SheetStack, { closed: true, open: open, onOpenChange: change, onRefresh: onRefresh, refreshing: refreshing, base: base, children: sheets.map((s, i) => (_jsx(StackSheet, { tone: s.tone ?? TONES[i], title: s.title, icon: s.icon, tabs: s.tabs, tab: tabs[i], onTabChange: (t) => setTabs((v) => ({ ...v, [i]: t })), backLabel: backLabel, backAt: backAt, foot: typeof s.foot === 'function' ? s.foot(tabs[i]) : s.foot, children: shows(i) ? (typeof s.body === 'function' ? s.body(tabs[i]) : s.body) : null }, i))) }), children] }));
|
|
39
39
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usableapp/cardds",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.54",
|
|
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.",
|