@justai/cuts 0.72.0 → 0.73.0
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/package.json +1 -1
- package/src/FilePicker.astro +9 -3
- package/src/Sheet.astro +45 -1
package/package.json
CHANGED
package/src/FilePicker.astro
CHANGED
|
@@ -85,9 +85,15 @@ const { id, title = "Choose a file", src = "/files" } = Astro.props;
|
|
|
85
85
|
|
|
86
86
|
{/* NO `Done` here, deliberately. This sheet is a QUESTION, and its answer is a row being
|
|
87
87
|
tapped inside the frame — so a Done that merely closed would be a Cancel wearing the word for
|
|
88
|
-
finishing, on the one surface where the difference decides what the caller receives.
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
finishing, on the one surface where the difference decides what the caller receives.
|
|
89
|
+
|
|
90
|
+
CANCEL IS IN THE BAR (2026-08-01). It used to be the kernel's own, in a footer beside Upload,
|
|
91
|
+
Camera and Record — which put it at the rank of the three SOURCES when it is not one of them:
|
|
92
|
+
those answer "where shall I get it", and this answers "nowhere". Apple's guidance is the same
|
|
93
|
+
shape — relying on Done alone "implies that completing the task is the only way to exit the
|
|
94
|
+
sheet" — and the leading edge of the bar is where iOS puts the way out. The kernel inside stops
|
|
95
|
+
drawing its own while it is somebody's guest; one exit, in the place a person already looks. */}
|
|
96
|
+
<Sheet id={`${id}-sheet`} title={title} detent="large" done={false} cancel="Cancel">
|
|
91
97
|
<div class="jfp" id={id} data-picker={id} data-src={src}>
|
|
92
98
|
{/* Empty until the first open. `title` is the accessible name of the document inside — a frame
|
|
93
99
|
announced as "frame" is the one control on the page that says nothing about itself. */}
|
package/src/Sheet.astro
CHANGED
|
@@ -53,6 +53,23 @@ import "./ground.css";
|
|
|
53
53
|
interface Props {
|
|
54
54
|
/** DOM id — the handle `data-sheet-open` points at. */
|
|
55
55
|
id: string;
|
|
56
|
+
// queries: cancel button on a sheet · how do I get out of a sheet without answering · sheet
|
|
57
|
+
// cancel top left · Cancel Done and Back together · dismiss a question
|
|
58
|
+
//
|
|
59
|
+
// ── THE WAY OUT, IN THE BAR ──────────────────────────────────────────────────────────────
|
|
60
|
+
//
|
|
61
|
+
// A sheet that ASKS something needs a visible way to answer "neither" — and Apple's own guidance
|
|
62
|
+
// is that relying on Done alone "implies that completing the task is the only way to exit the
|
|
63
|
+
// sheet, which can feel restrictive or misleading". So a question carries a leading dismiss.
|
|
64
|
+
//
|
|
65
|
+
// It is the LEADING edge because that is where iOS puts it and because Done owns the trailing
|
|
66
|
+
// one; a cancel on the right would be a control that looks like a commit. And it gives way to the
|
|
67
|
+
// BACK button when a page is pushed — the same guidance says not to show Cancel, Done and Back
|
|
68
|
+
// together, and going back is the way out of a page that has one.
|
|
69
|
+
//
|
|
70
|
+
// Default off: a sheet that only SHOWS something already has a grabber and a backdrop, and a
|
|
71
|
+
// labelled exit on a surface with nothing to abandon is a control asking a question nobody has.
|
|
72
|
+
cancel?: string | false;
|
|
56
73
|
/** Shown at the top of the panel, beside the grabber. Omit for a bare sheet. */
|
|
57
74
|
title?: string;
|
|
58
75
|
/** How tall it opens. `medium` is iOS's half-height default; drag up promotes it to large. */
|
|
@@ -97,9 +114,10 @@ interface Props {
|
|
|
97
114
|
// hardcoded the English would be a component making its consumer's copy decisions.
|
|
98
115
|
done?: string | false;
|
|
99
116
|
}
|
|
100
|
-
const { id, title, detent = "medium", done, floorToParent = true } = Astro.props;
|
|
117
|
+
const { id, title, detent = "medium", done, cancel, floorToParent = true } = Astro.props;
|
|
101
118
|
// Default: shown at large, absent at medium. `false` removes it, a string replaces the word.
|
|
102
119
|
const doneLabel = done === false ? null : (done ?? (detent === "large" ? "Done" : null));
|
|
120
|
+
const cancelLabel = cancel === false || cancel === undefined ? null : cancel;
|
|
103
121
|
---
|
|
104
122
|
|
|
105
123
|
{/* aria-labelledby over aria-label when there IS a title, so the accessible name and the visible
|
|
@@ -136,6 +154,9 @@ const doneLabel = done === false ? null : (done ?? (detent === "large" ? "Done"
|
|
|
136
154
|
|
|
137
155
|
NO HOMEWORK: a consumer writes markup and two attributes. No script, and the sheet still
|
|
138
156
|
works exactly as before for the consumers that never declare a page. */}
|
|
157
|
+
{cancelLabel && (
|
|
158
|
+
<button class="sheet-cancel" id={`${id}-cancel`} type="button" data-sheet-cancel>{cancelLabel}</button>
|
|
159
|
+
)}
|
|
139
160
|
<button class="sheet-back" id={`${id}-back`} type="button" data-sheet-back hidden>
|
|
140
161
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg>
|
|
141
162
|
<span data-sheet-back-label></span>
|
|
@@ -206,6 +227,19 @@ const doneLabel = done === false ? null : (done ?? (detent === "large" ? "Done"
|
|
|
206
227
|
:global([data-sheet-page].is-current), :global([data-sheet-page].is-popping) { animation: none; }
|
|
207
228
|
}
|
|
208
229
|
|
|
230
|
+
/* MIRRORS DONE ACROSS THE BAR — same band, same weight, opposite edge — and it is not tinted
|
|
231
|
+
like a commit: leaving without answering is an ordinary thing to do, not the reason you came. */
|
|
232
|
+
.sheet-cancel {
|
|
233
|
+
position: absolute; inset-block-start: 0; inset-inline-start: 0;
|
|
234
|
+
background: none; border: 0; cursor: pointer; padding: 12px 16px;
|
|
235
|
+
font: inherit; font-size: var(--fs-sm); font-weight: 500; color: var(--text-dim);
|
|
236
|
+
}
|
|
237
|
+
.sheet-cancel:active { opacity: 0.6; }
|
|
238
|
+
.sheet-cancel:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 8px; }
|
|
239
|
+
/* Cancel, Done and Back must never stand together — going back IS the way out of a pushed page,
|
|
240
|
+
so the leading slot belongs to whichever of the two is meaningful right now. */
|
|
241
|
+
.sheet:has(.sheet-back:not([hidden])) .sheet-cancel { display: none; }
|
|
242
|
+
|
|
209
243
|
.sheet-done {
|
|
210
244
|
position: absolute; inset-block-start: 0; inset-inline-end: 0;
|
|
211
245
|
background: none; border: 0; cursor: pointer; padding: 12px 16px;
|
|
@@ -586,6 +620,16 @@ const doneLabel = done === false ? null : (done ?? (detent === "large" ? "Done"
|
|
|
586
620
|
(d.closest("dialog.sheet") as HTMLDialogElement | null)?.close();
|
|
587
621
|
});
|
|
588
622
|
|
|
623
|
+
// Cancel closes too, and the difference is not in what it DOES — it is in what the caller may
|
|
624
|
+
// conclude. A sheet that resolves a promise reads its own `close` event and settles null there,
|
|
625
|
+
// so a cancel, a backdrop tap and a drag down are one path with one answer. Two words, one exit,
|
|
626
|
+
// and no second place for a caller to be told something different.
|
|
627
|
+
document.addEventListener("click", (e) => {
|
|
628
|
+
const c = (e.target as HTMLElement)?.closest?.("[data-sheet-cancel]");
|
|
629
|
+
if (!c) return;
|
|
630
|
+
(c.closest("dialog.sheet") as HTMLDialogElement | null)?.close();
|
|
631
|
+
});
|
|
632
|
+
|
|
589
633
|
document.addEventListener("click", (e) => {
|
|
590
634
|
const t = (e.target as HTMLElement)?.closest?.("[data-sheet-open]");
|
|
591
635
|
if (!t) return;
|