@stacksjs/defaults 0.75.182 → 0.75.183
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.
|
@@ -452,7 +452,14 @@ option the original metres heuristic remains for unlabelled inputs.
|
|
|
452
452
|
- `<NativeSheet v-model:open="editing" title="…">` raises a bottom sheet for a
|
|
453
453
|
task that belongs to the screen: dragged down from its grabber or header to
|
|
454
454
|
dismiss, the page behind inert. `detents="medium large"` makes it full
|
|
455
|
-
height with detents, the page pressed into a card at large.
|
|
455
|
+
height with detents, the page pressed into a card at large. An open sheet is
|
|
456
|
+
a layer on stx's overlay stack (`window.stx.overlays`) with Dialog, Drawer,
|
|
457
|
+
stxConfirm and the HTML alerts: the stack locks the page, makes it inert,
|
|
458
|
+
gives Escape and Tab to the top layer and returns focus, so a confirm opened
|
|
459
|
+
from a sheet closes alone. `lockBackground` and `trapFocus` no longer exist;
|
|
460
|
+
an app's own overlay uses `openOverlay` / `closeOverlay` from
|
|
461
|
+
`@stacksjs/components/overlays` (in `<script client>`), passing `root` for
|
|
462
|
+
an element lifted into `<body>` and `zIndex` to stack by opening order.
|
|
456
463
|
- `<NativeContextMenu :items="[...]" @select="…">` opens a menu on a
|
|
457
464
|
half-second press: UIMenu in the shell, an HTML menu elsewhere.
|
|
458
465
|
- `<NativeActionSheet v-model:open="choosing" :actions="[...]" @select="…">`
|
package/ide/vscode/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"publisher": "Stacks",
|
|
3
3
|
"name": "vscode-stacks",
|
|
4
4
|
"displayName": "Stacks",
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.183",
|
|
6
6
|
"description": "Stacks, built into VS Code: stx templates, pickier linting and formatting, encrypted env files, the dev server preview and every buddy command. No other extensions required.",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"funding": "https://github.com/sponsors/chrisbbreuer",
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.183",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@iconify-json/f7": "^1.2.2",
|
|
57
57
|
"@iconify-json/hugeicons": "^1.2.35",
|
|
58
|
-
"@stacksjs/mobile": "0.75.
|
|
58
|
+
"@stacksjs/mobile": "0.75.183",
|
|
59
59
|
"@stacksjs/sanitizer": "^0.2.483",
|
|
60
60
|
"@xterm/addon-fit": "^0.11.0",
|
|
61
61
|
"@xterm/xterm": "^6.0.0",
|
|
@@ -118,10 +118,15 @@ describe('Native components', () => {
|
|
|
118
118
|
expect(bar).toContain("useReactiveProp<boolean>('titleOnScroll', false)")
|
|
119
119
|
})
|
|
120
120
|
|
|
121
|
-
it('lifts
|
|
121
|
+
it('lifts a sheet into the body when it mounts, out of the router\'s stacking context', () => {
|
|
122
122
|
const sheet = read('NativeSheet')
|
|
123
|
-
expect(sheet).toContain('main:has(.native-sheet.is-open) { view-transition-name: none; }')
|
|
124
123
|
expect(sheet).toContain("useModel('open'")
|
|
124
|
+
// Lifted before it can open, so it never draws inside <main>, which the
|
|
125
|
+
// router makes a stacking context; <main> no longer has to give up its
|
|
126
|
+
// view-transition name while a sheet is up.
|
|
127
|
+
const mounted = sheet.slice(sheet.indexOf('onMount(() => {'))
|
|
128
|
+
expect(mounted.indexOf('putBack ??= liftToBody(element)')).toBeLessThan(mounted.indexOf('if (open()) present()'))
|
|
129
|
+
expect(sheet).not.toContain('main:has(')
|
|
125
130
|
})
|
|
126
131
|
|
|
127
132
|
it('hides the native tab bar under a shell that has none, like a sign-in screen', () => {
|
|
@@ -272,12 +277,32 @@ describe('Native components feel native', () => {
|
|
|
272
277
|
expect(html).toContain('data-stx-ref="nativeSheetHeader"')
|
|
273
278
|
})
|
|
274
279
|
|
|
275
|
-
it('opens a sheet without a haptic,
|
|
280
|
+
it('opens a sheet without a haptic, as a layer on the stx overlay stack', () => {
|
|
276
281
|
const sheet = read('NativeSheet')
|
|
277
282
|
expect(sheet).not.toContain('haptics')
|
|
278
|
-
expect(sheet).toContain('releaseBackground = lockBackground(element)')
|
|
279
|
-
expect(sheet).toContain('trapFocus(panel.current as HTMLElement, event)')
|
|
280
283
|
expect(sheet).toContain('putBack ??= liftToBody(element)')
|
|
284
|
+
// The stack holds the page, traps Tab, answers Escape for the top layer
|
|
285
|
+
// and hands focus back, shared with Dialog, Drawer and stxConfirm. The
|
|
286
|
+
// sheet's own Escape listener, Tab trap, background lock and focus
|
|
287
|
+
// restore each fought the stack when one overlay opened over another.
|
|
288
|
+
expect(sheet).toContain("import { closeOverlay, openOverlay } from '@stacksjs/components/overlays'")
|
|
289
|
+
expect(sheet).toContain('openOverlay(overlayId, {')
|
|
290
|
+
expect(sheet).toContain('root: element,')
|
|
291
|
+
expect(sheet).toContain('zIndex: SHEET_LAYER,')
|
|
292
|
+
expect(sheet).toContain('onEscape: close,')
|
|
293
|
+
expect(sheet).toContain('initialFocus: sheetPanel,')
|
|
294
|
+
for (const gone of ['lockBackground', 'trapFocus', 'previousFocus', "useEventListener('keydown'", 'releaseBackground'])
|
|
295
|
+
expect(sheet).not.toContain(gone)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('leaves the overlay stack when it closes, and when it is removed while up', () => {
|
|
299
|
+
const sheet = read('NativeSheet')
|
|
300
|
+
const dismissed = sheet.slice(sheet.indexOf('function dismissed(): void {'), sheet.indexOf('let wasOpen = false'))
|
|
301
|
+
expect(dismissed).toContain('closeOverlay(overlayId)')
|
|
302
|
+
const destroyed = sheet.slice(sheet.indexOf('onDestroy(() => {'), sheet.indexOf("useEventListener('stx:navigate'"))
|
|
303
|
+
expect(destroyed).toContain('closeOverlay(overlayId)')
|
|
304
|
+
// Each sheet gives back only its own hold on the card.
|
|
305
|
+
expect(destroyed).toContain('presentPageAsCard(element.ownerDocument, false)')
|
|
281
306
|
})
|
|
282
307
|
|
|
283
308
|
it('slides a sheet up from where it was lifted to, rather than appearing up', () => {
|
|
@@ -285,8 +310,8 @@ describe('Native components feel native', () => {
|
|
|
285
310
|
// is-open follows `shown`, which present() sets only after the lifted
|
|
286
311
|
// sheet's closed style is worked out in the body.
|
|
287
312
|
expect(sheet).toContain("const sheetClass = derived(() => `${shown() ? 'is-open' : ''}")
|
|
288
|
-
expect(sheet).toContain('sheetPanel
|
|
289
|
-
expect(sheet.indexOf('sheetPanel
|
|
313
|
+
expect(sheet).toContain('sheetPanel()?.getBoundingClientRect()')
|
|
314
|
+
expect(sheet.indexOf('sheetPanel()?.getBoundingClientRect()')).toBeLessThan(sheet.indexOf('shown.set(true)'))
|
|
290
315
|
expect(sheet.indexOf('putBack ??= liftToBody(element)')).toBeLessThan(sheet.indexOf('shown.set(true)'))
|
|
291
316
|
})
|
|
292
317
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script client>
|
|
2
2
|
import type { SheetDetent } from '@stacksjs/mobile'
|
|
3
|
-
import {
|
|
3
|
+
import { closeOverlay, openOverlay } from '@stacksjs/components/overlays'
|
|
4
|
+
import { coverTabBar, liftToBody, observeSheetDrag, presentPageAsCard } from '@stacksjs/mobile'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* A bottom sheet: a task that belongs to this screen, raised over it.
|
|
@@ -19,6 +20,15 @@ import { coverTabBar, liftToBody, lockBackground, observeSheetDrag, presentPageA
|
|
|
19
20
|
* it is up. It belongs to the screen it was opened on, so leaving that screen
|
|
20
21
|
* (a link from outside, a tab switch) closes it rather than carrying it over
|
|
21
22
|
* the next one.
|
|
23
|
+
*
|
|
24
|
+
* An open sheet is a layer on the page's overlay stack (stx's
|
|
25
|
+
* `window.stx.overlays`), shared with Dialog, Drawer, stxConfirm, the web
|
|
26
|
+
* stand-ins for native alerts and the app's own overlays. The stack holds the
|
|
27
|
+
* page still from the first layer to the last, makes the rest of the page
|
|
28
|
+
* inert, gives Escape and Tab to whichever layer is on top, and hands focus
|
|
29
|
+
* back to whatever opened each one. A confirmation opened from a sheet, or a
|
|
30
|
+
* sheet opened over a drawer, closes with one Escape and leaves the layer
|
|
31
|
+
* under it up, locked and in charge of the keyboard.
|
|
22
32
|
*/
|
|
23
33
|
const emit = defineEmits()
|
|
24
34
|
const open = useModel('open', { default: false })
|
|
@@ -44,9 +54,15 @@ const panel = useRef('nativeSheetPanel')
|
|
|
44
54
|
const grabber = useRef('nativeSheetGrabber')
|
|
45
55
|
const header = useRef('nativeSheetHeader')
|
|
46
56
|
|
|
47
|
-
|
|
57
|
+
// This sheet's id on the overlay stack.
|
|
58
|
+
const overlayId = useId('native-sheet')
|
|
59
|
+
// Sheets stack above the page at 70 (the stylesheet's z-index); the stack
|
|
60
|
+
// adds each open sheet's place to it, so a sheet opened over another draws
|
|
61
|
+
// above it wherever the two were lifted into the body.
|
|
62
|
+
const SHEET_LAYER = 70
|
|
63
|
+
|
|
64
|
+
let presented = false
|
|
48
65
|
let putBack: (() => void) | null = null
|
|
49
|
-
let previousFocus: HTMLElement | null = null
|
|
50
66
|
let carded = false
|
|
51
67
|
let stopDrag: (() => void) | null = null
|
|
52
68
|
let openedOn = ''
|
|
@@ -59,35 +75,47 @@ function root(): HTMLElement | null {
|
|
|
59
75
|
return sheetRoot.current as HTMLElement | null
|
|
60
76
|
}
|
|
61
77
|
|
|
78
|
+
function sheetPanel(): HTMLElement | null {
|
|
79
|
+
return panel.current as HTMLElement | null
|
|
80
|
+
}
|
|
81
|
+
|
|
62
82
|
function present(): void {
|
|
63
83
|
const element = root()
|
|
64
|
-
if (!element ||
|
|
84
|
+
if (!element || presented) return
|
|
85
|
+
presented = true
|
|
65
86
|
detent.set(detents()[0] ?? 'large')
|
|
66
|
-
previousFocus = element.ownerDocument.activeElement as HTMLElement | null
|
|
67
87
|
// Out of the page's stacking contexts and transforms (the card below
|
|
68
|
-
// scales the page),
|
|
88
|
+
// scales the page). A child of the body, it is what the overlay stack keeps
|
|
89
|
+
// usable while everything else goes inert.
|
|
69
90
|
putBack ??= liftToBody(element)
|
|
70
|
-
releaseBackground = lockBackground(element)
|
|
71
91
|
openedOn = currentPath()
|
|
72
92
|
coverTabBar(element, true)
|
|
73
93
|
// A node moved in the document starts over with no style to transition
|
|
74
94
|
// from, so a sheet lifted and marked open in one go appears already up.
|
|
75
95
|
// Work its closed style out where it now is, then open it: the panel
|
|
76
96
|
// slides from there.
|
|
77
|
-
|
|
78
|
-
if (sheetPanel) void sheetPanel.getBoundingClientRect()
|
|
97
|
+
void sheetPanel()?.getBoundingClientRect()
|
|
79
98
|
shown.set(true)
|
|
80
|
-
|
|
99
|
+
// Focus goes to the panel itself, not its first control: on a phone a
|
|
100
|
+
// focused field raises the keyboard over the sheet before it is up.
|
|
101
|
+
openOverlay(overlayId, {
|
|
102
|
+
panel: sheetPanel,
|
|
103
|
+
root: element,
|
|
104
|
+
zIndex: SHEET_LAYER,
|
|
105
|
+
initialFocus: sheetPanel,
|
|
106
|
+
onEscape: close,
|
|
107
|
+
})
|
|
81
108
|
}
|
|
82
109
|
|
|
83
110
|
function dismissed(): void {
|
|
111
|
+
if (!presented) return
|
|
112
|
+
presented = false
|
|
84
113
|
shown.set(false)
|
|
85
114
|
const element = root()
|
|
86
115
|
if (element) coverTabBar(element, false)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
previousFocus = null
|
|
116
|
+
// Off the stack: the layer underneath (or the page) gets the keyboard back,
|
|
117
|
+
// and focus returns to whatever opened the sheet.
|
|
118
|
+
closeOverlay(overlayId)
|
|
91
119
|
}
|
|
92
120
|
|
|
93
121
|
let wasOpen = false
|
|
@@ -98,9 +126,9 @@ effect(() => {
|
|
|
98
126
|
wasOpen = isOpen
|
|
99
127
|
})
|
|
100
128
|
|
|
101
|
-
// The page behind becomes a card while a full-height sheet is at large.
|
|
102
|
-
//
|
|
103
|
-
//
|
|
129
|
+
// The page behind becomes a card while a full-height sheet is at large. Each
|
|
130
|
+
// sheet hands back only its own hold, so a sheet closing over another never
|
|
131
|
+
// undoes the card the other still wants.
|
|
104
132
|
effect(() => {
|
|
105
133
|
const element = root()
|
|
106
134
|
const want = open() && hasDetents() && detent() === 'large'
|
|
@@ -117,13 +145,15 @@ function close(): void {
|
|
|
117
145
|
onMount(() => {
|
|
118
146
|
// Give the sheet its permanent presentation parent before its slot loads
|
|
119
147
|
// media. Reparenting an already loading iframe or video interrupts playback.
|
|
148
|
+
// A closed sheet waits in the body like this, so one nested in another's
|
|
149
|
+
// content can open over it without leaving the document mid-transition.
|
|
120
150
|
const element = root()
|
|
121
151
|
if (element) putBack ??= liftToBody(element)
|
|
122
|
-
const
|
|
123
|
-
if (!
|
|
152
|
+
const dragPanel = sheetPanel()
|
|
153
|
+
if (!dragPanel) return
|
|
124
154
|
const handles = [grabber.current, header.current].filter(Boolean) as HTMLElement[]
|
|
125
155
|
stopDrag = observeSheetDrag({
|
|
126
|
-
panel:
|
|
156
|
+
panel: dragPanel,
|
|
127
157
|
handles,
|
|
128
158
|
detents: () => detents(),
|
|
129
159
|
detent: () => detent(),
|
|
@@ -137,10 +167,16 @@ onMount(() => {
|
|
|
137
167
|
|
|
138
168
|
onDestroy(() => {
|
|
139
169
|
stopDrag?.()
|
|
140
|
-
releaseBackground?.()
|
|
141
170
|
const element = root()
|
|
142
|
-
if (element &&
|
|
143
|
-
|
|
171
|
+
if (element && presented) coverTabBar(element, false)
|
|
172
|
+
// Removed while up: off the stack, so the page is not left locked under a
|
|
173
|
+
// sheet that is gone.
|
|
174
|
+
presented = false
|
|
175
|
+
closeOverlay(overlayId)
|
|
176
|
+
if (carded && element) {
|
|
177
|
+
carded = false
|
|
178
|
+
presentPageAsCard(element.ownerDocument, false)
|
|
179
|
+
}
|
|
144
180
|
putBack?.()
|
|
145
181
|
})
|
|
146
182
|
|
|
@@ -153,12 +189,6 @@ useEventListener('stx:navigate', (event: Event) => {
|
|
|
153
189
|
if (to !== openedOn) close()
|
|
154
190
|
})
|
|
155
191
|
|
|
156
|
-
useEventListener('keydown', (event: KeyboardEvent) => {
|
|
157
|
-
if (!open()) return
|
|
158
|
-
if (event.key === 'Escape') close()
|
|
159
|
-
else if (panel.current) trapFocus(panel.current as HTMLElement, event)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
192
|
const sheetClass = derived(() => `${shown() ? 'is-open' : ''}${hasDetents() ? ' has-detents' : ''}${detent() === 'medium' ? ' is-medium' : ''}`.trim())
|
|
163
193
|
</script>
|
|
164
194
|
|
|
@@ -308,9 +338,4 @@ const sheetClass = derived(() => `${shown() ? 'is-open' : ''}${hasDetents() ? '
|
|
|
308
338
|
:root[data-native-reduce-motion] .native-sheet-panel { transition: none; }
|
|
309
339
|
:root[data-native-reduce-motion][data-native-sheet-card] .native-app-content { transform: none; border-radius: 0; }
|
|
310
340
|
|
|
311
|
-
/* The router names the page's <main> for view transitions, which makes it a
|
|
312
|
-
stacking context: a sheet inside it stacked under the tab bar whatever its
|
|
313
|
-
z-index. While a sheet is open <main> gives the name up. Nothing navigates
|
|
314
|
-
with a sheet open, so no transition is lost. */
|
|
315
|
-
main:has(.native-sheet.is-open) { view-transition-name: none; }
|
|
316
341
|
</style>
|