@stacksjs/defaults 0.75.182 → 0.75.184
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/ai/skills/stacks-dashboard/SKILL.md +2 -2
- package/ai/skills/stacks-mobile/SKILL.md +8 -1
- package/ai/skills/stacks-native/CATALOG.md +4 -4
- package/ide/vscode/package.json +1 -1
- package/package.json +3 -3
- package/resources/components/NativeComponents.test.ts +32 -7
- package/resources/components/NativeSheet.stx +59 -34
- package/resources/components/index.ts +0 -2
- package/resources/components/Audio.stx +0 -92
- package/resources/components/Image.stx +0 -57
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: stacks-dashboard
|
|
3
|
-
description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the
|
|
3
|
+
description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the 411 built-in dashboard components. Covers the dashboard system at storage/framework/defaults/.
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Bun >= 1.3.0, TypeScript
|
|
6
6
|
allowed-tools: Read Edit Write Bash Grep Glob
|
|
@@ -8,7 +8,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
|
|
|
8
8
|
|
|
9
9
|
# Stacks Dashboard
|
|
10
10
|
|
|
11
|
-
The Stacks admin dashboard provides a full-featured admin panel with 100+ route views,
|
|
11
|
+
The Stacks admin dashboard provides a full-featured admin panel with 100+ route views, 411 components, and a multi-section layout.
|
|
12
12
|
|
|
13
13
|
## Key Paths
|
|
14
14
|
- Dashboard components: `storage/framework/defaults/resources/components/Dashboard/`
|
|
@@ -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="…">`
|
|
@@ -18,7 +18,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
|
|
|
18
18
|
| `@stacksjs/api` | [`stacks-api`](../stacks-api/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/api/src/index.ts) | 7 |
|
|
19
19
|
| `@stacksjs/arrays` | [`stacks-arrays`](../stacks-arrays/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/arrays/src/index.ts) | 6 |
|
|
20
20
|
| `@stacksjs/audio` | [`stacks-audio`](../stacks-audio/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/audio/src/index.ts) | 2 |
|
|
21
|
-
| `@stacksjs/auth` | [`stacks-auth`](../stacks-auth/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/index.ts) |
|
|
21
|
+
| `@stacksjs/auth` | [`stacks-auth`](../stacks-auth/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/index.ts) | 114 |
|
|
22
22
|
| `@stacksjs/browser` | [`stacks-browser`](../stacks-browser/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser/src/index.ts) | 8 |
|
|
23
23
|
| `@stacksjs/browser-extension` | [`stacks-browser-extension`](../stacks-browser-extension/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser-extension/src/index.ts) | 7 |
|
|
24
24
|
| `@stacksjs/buddy` | [`stacks-buddy`](../stacks-buddy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/index.ts) | 145 |
|
|
@@ -65,7 +65,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
|
|
|
65
65
|
| `@stacksjs/newsletter` | [`stacks-newsletter`](../stacks-newsletter/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/newsletter/src/index.ts) | 7 |
|
|
66
66
|
| `@stacksjs/notifications` | [`stacks-notifications`](../stacks-notifications/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/index.ts) | 15 |
|
|
67
67
|
| `@stacksjs/objects` | [`stacks-objects`](../stacks-objects/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/objects/src/index.ts) | 4 |
|
|
68
|
-
| `@stacksjs/orm` | [`stacks-orm`](../stacks-orm/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/index.ts) |
|
|
68
|
+
| `@stacksjs/orm` | [`stacks-orm`](../stacks-orm/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/index.ts) | 61 |
|
|
69
69
|
| `@stacksjs/pagination` | [`stacks-pagination`](../stacks-pagination/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/pagination/src/index.ts) | 0 |
|
|
70
70
|
| `@stacksjs/path` | [`stacks-path`](../stacks-path/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/path/src/index.ts) | 10 |
|
|
71
71
|
| `@stacksjs/payments` | [`stacks-payments`](../stacks-payments/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/payments/src/index.ts) | 23 |
|
|
@@ -185,7 +185,7 @@ Declared runtime exports: `DEFAULT_ROLE_PACKS`, `createBqbRbacStore`, `generateT
|
|
|
185
185
|
|
|
186
186
|
Local source modules: [src/authentication.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authentication.ts), [src/authenticator.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authenticator.ts), [src/authorizable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/authorizable.ts), [src/browser-session.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/browser-session.ts), [src/client.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/client.ts), [src/cookie-auth.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/cookie-auth.ts), [src/credential-user.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/credential-user.ts), [src/email-verification.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/email-verification.ts), [src/gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/gate.ts), [src/magic-link.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/magic-link.ts), [src/middleware.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/middleware.ts), [src/oauth-authorization-codes.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-codes.ts), [src/oauth-authorization-endpoint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-endpoint.ts), [src/oauth-authorization-requests.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-requests.ts), [src/oauth-authorization-response.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization-response.ts), [src/oauth-authorization.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-authorization.ts), [src/oauth-browser-session.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-browser-session.ts), [src/oauth-client-registration.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-client-registration.ts), [src/oauth-consent.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-consent.ts), [src/oauth-delegated-access.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-delegated-access.ts), [src/oauth-grants.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-grants.ts), [src/oauth-introspection.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-introspection.ts), [src/oauth-metadata.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-metadata.ts), [src/oauth-pkce.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-pkce.ts), [src/oauth-provider.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-provider.ts), [src/oauth-pruning.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-pruning.ts), [src/oauth-revocation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-revocation.ts), [src/oauth-token-endpoint.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-endpoint.ts), [src/oauth-token-exchange.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-exchange.ts), [src/oauth-token-request.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-request.ts), [src/oauth-token-response.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/oauth-token-response.ts), [src/page-gate.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/page-gate.ts), [src/passkey.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/passkey.ts), [src/passkeys.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/passkeys.ts), [src/password/reset.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/password/reset.ts), [src/permissions.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/permissions.ts), [src/policy.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/policy.ts), [src/rate-limiter.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rate-limiter.ts), [src/rbac-seed.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac-seed.ts), [src/rbac-store-bqb.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac-store-bqb.ts), [src/rbac.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/rbac.ts), [src/referrals.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/referrals.ts), [src/register.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/register.ts), [src/request-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/request-token.ts), [src/session-auth.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/session-auth.ts), [src/socials.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/socials.ts), [src/team.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/team.ts), [src/tokens.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/tokens.ts), [src/two-factor.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/two-factor.ts), [src/user.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/user.ts).
|
|
187
187
|
|
|
188
|
-
Evidence directory: [
|
|
188
|
+
Evidence directory: [114 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
|
|
189
189
|
|
|
190
190
|
### @stacksjs/browser
|
|
191
191
|
|
|
@@ -631,7 +631,7 @@ Declared runtime exports: `Activity`, `AnalyticsEvent`, `Auction`, `AuctionItem`
|
|
|
631
631
|
|
|
632
632
|
Local source modules: [src/auto-crud.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/auto-crud.ts), [src/batch-loader.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/batch-loader.ts), [src/db.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/db.ts), [src/define-model.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/define-model.ts), [src/extend-model.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/extend-model.ts), [src/gdpr/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/gdpr/index.ts), [src/generate-database-schema.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/generate-database-schema.ts), [src/locked-row.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/locked-row.ts), [src/messaging.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/messaging.ts), [src/model-types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/model-types.ts), [src/ownership.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/ownership.ts), [src/paginator-request.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/paginator-request.ts), [src/row-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/row-token.ts), [src/subquery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/subquery.ts), [src/traits/audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/audit.ts), [src/traits/billable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/billable.ts), [src/traits/categorizable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/categorizable.ts), [src/traits/commentable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/commentable.ts), [src/traits/likeable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/likeable.ts), [src/traits/taggable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/taggable.ts), [src/transaction.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/transaction.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/types.ts), [src/utils/encrypted.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/utils/encrypted.ts), [src/utils/prunable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/utils/prunable.ts).
|
|
633
633
|
|
|
634
|
-
Evidence directory: [
|
|
634
|
+
Evidence directory: [61 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
|
|
635
635
|
|
|
636
636
|
### @stacksjs/pagination
|
|
637
637
|
|
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.184",
|
|
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.184",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@iconify-json/f7": "^1.2.2",
|
|
57
57
|
"@iconify-json/hugeicons": "^1.2.35",
|
|
58
|
-
"@stacksjs/mobile": "0.75.
|
|
59
|
-
"@stacksjs/sanitizer": "^0.2.
|
|
58
|
+
"@stacksjs/mobile": "0.75.184",
|
|
59
|
+
"@stacksjs/sanitizer": "^0.2.534",
|
|
60
60
|
"@xterm/addon-fit": "^0.11.0",
|
|
61
61
|
"@xterm/xterm": "^6.0.0",
|
|
62
62
|
"ts-qr-codes": "^0.1.8"
|
|
@@ -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>
|
|
@@ -10,8 +10,6 @@ export { default as DashboardNavbar } from './Dashboard/Navbar.stx'
|
|
|
10
10
|
|
|
11
11
|
// Dashboard and UI components
|
|
12
12
|
export * from './Dashboard'
|
|
13
|
-
export { default as Audio } from './Audio.stx'
|
|
14
|
-
export { default as Image } from './Image.stx'
|
|
15
13
|
export { default as NativeActionSheet } from './NativeActionSheet.stx'
|
|
16
14
|
export { default as NativeAppShell } from './NativeAppShell.stx'
|
|
17
15
|
export { default as NativeContextMenu } from './NativeContextMenu.stx'
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
<script server>
|
|
2
|
-
interface AudioSource { src: string, type?: string }
|
|
3
|
-
interface AudioDrm {
|
|
4
|
-
widevine?: { url: string, headers?: Record<string, string> }
|
|
5
|
-
playready?: { url: string, headers?: Record<string, string> }
|
|
6
|
-
fairplay?: { url: string, certificateUrl: string, headers?: Record<string, string> }
|
|
7
|
-
}
|
|
8
|
-
interface AudioTrack { src: string, kind: 'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata', srclang?: string, label?: string, default?: boolean }
|
|
9
|
-
interface AudioProps {
|
|
10
|
-
src: string
|
|
11
|
-
sources?: AudioSource[]
|
|
12
|
-
drm?: AudioDrm
|
|
13
|
-
tracks?: AudioTrack[]
|
|
14
|
-
title?: string
|
|
15
|
-
artist?: string
|
|
16
|
-
artwork?: string
|
|
17
|
-
waveform?: string
|
|
18
|
-
controls?: boolean
|
|
19
|
-
autoplay?: boolean
|
|
20
|
-
loop?: boolean
|
|
21
|
-
muted?: boolean
|
|
22
|
-
preload?: 'none' | 'metadata' | 'auto'
|
|
23
|
-
crossorigin?: 'anonymous' | 'use-credentials'
|
|
24
|
-
class?: string
|
|
25
|
-
}
|
|
26
|
-
const props = defineProps<AudioProps>()
|
|
27
|
-
const src = props.src || ''
|
|
28
|
-
const sources = props.sources || []
|
|
29
|
-
const tracks = props.tracks || []
|
|
30
|
-
const title = props.title || 'Audio player'
|
|
31
|
-
const artist = props.artist || ''
|
|
32
|
-
const artwork = props.artwork || ''
|
|
33
|
-
const waveform = props.waveform || ''
|
|
34
|
-
const controls = props.controls !== false
|
|
35
|
-
const preload = props.preload || 'metadata'
|
|
36
|
-
const crossorigin = props.crossorigin || ''
|
|
37
|
-
const className = props.class || ''
|
|
38
|
-
const playerSources = (sources.length ? sources : src ? [{ src }] : []).map(source => props.drm && (source.type === 'application/dash+xml' || source.src.split(/[?#]/, 1)[0].toLowerCase().endsWith('.mpd')) ? { ...source, type: 'application/dash+xml', drm: props.drm } : source)
|
|
39
|
-
const playerTracks = tracks.map(track => ({ ...track, language: track.srclang }))
|
|
40
|
-
const playerConfig = JSON.stringify({ sources: playerSources, tracks: playerTracks, ...(props.drm ? { drm: props.drm } : {}) }).replaceAll('<', '\\u003c').replaceAll('>', '\\u003e').replaceAll('&', '\\u0026')
|
|
41
|
-
</script>
|
|
42
|
-
|
|
43
|
-
<script client>
|
|
44
|
-
import { registerElements } from 'ts-video-player/elements'
|
|
45
|
-
registerElements()
|
|
46
|
-
</script>
|
|
47
|
-
|
|
48
|
-
<figure class="m-0 overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-stone-200 dark:bg-stone-950 dark:ring-stone-800 {{ className }}">
|
|
49
|
-
<div class="flex gap-4 items-center p-4 sm:p-5">
|
|
50
|
-
@if(artwork)
|
|
51
|
-
<img src="{{ artwork }}" alt="" width="72" height="72" loading="lazy" decoding="async" class="object-cover shrink-0 h-18 w-18 ring-1 ring-black/5 rounded-xl dark:ring-white/10">
|
|
52
|
-
@else
|
|
53
|
-
<div class="flex items-center justify-center shrink-0 h-18 w-18 text-stone-500 dark:text-stone-400 bg-stone-100 dark:bg-stone-900 rounded-xl" aria-hidden="true"><span class="h-7 w-7 i-hugeicons-music-note-01"></span></div>
|
|
54
|
-
@endif
|
|
55
|
-
<div class="flex-1 min-w-0">
|
|
56
|
-
<figcaption class="font-semibold text-sm text-stone-950 truncate dark:text-white">{{ title }}</figcaption>
|
|
57
|
-
@if(artist)<p class="mt-0.5 text-sm text-stone-500 truncate dark:text-stone-400">{{ artist }}</p>@endif
|
|
58
|
-
@if(waveform)<img src="{{ waveform }}" alt="" loading="lazy" decoding="async" class="object-cover mt-3 h-10 w-full opacity-70 dark:opacity-80">@endif
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
<div class="p-3 bg-stone-950 border-stone-100 border-t dark:border-stone-800">
|
|
62
|
-
<video-player src="{{ src }}" title="{{ title }}" data-media-config="{{ playerConfig }}" @if(controls) controls @endif @if(props.autoplay) autoplay @endif @if(props.loop) loop @endif @if(props.muted) muted @endif preload="{{ preload }}" @if(crossorigin) crossorigin="{{ crossorigin }}" keep-alive aria-label="{{ title }}" class="block overflow-hidden relative h-16 w-full text-white rounded-xl focus-within:ring-2 focus-within:ring-sky-500">
|
|
63
|
-
<media-error-display></media-error-display>
|
|
64
|
-
<audio data-native-fallback src="{{ src }}" @if(controls) controls @endif @if(props.autoplay) autoplay @endif @if(props.loop) loop @endif @if(props.muted) muted @endif preload="{{ preload }}" @if(crossorigin) crossorigin="{{ crossorigin }}" @endif aria-label="{{ title }}" class="block h-16 w-full accent-sky-500">
|
|
65
|
-
@foreach (sources as source)<source src="{{ source.src }}" @if(source.type) type="{{ source.type }}" @endif>@endforeach
|
|
66
|
-
@foreach (tracks as track)<track src="{{ track.src }}" kind="{{ track.kind }}" @if(track.srclang) srclang="{{ track.srclang }}" @endif @if(track.label) label="{{ track.label }}" @endif @if(track.default) default @endif>@endforeach
|
|
67
|
-
Your browser does not support audio playback.
|
|
68
|
-
</audio>
|
|
69
|
-
@if(controls)
|
|
70
|
-
<video-skin>
|
|
71
|
-
<media-seek-button slot="left" seconds="-10" aria-label="Back 10 seconds"></media-seek-button>
|
|
72
|
-
<media-play-button slot="left" aria-label="Play or pause"></media-play-button>
|
|
73
|
-
<media-seek-button slot="left" seconds="10" aria-label="Forward 10 seconds"></media-seek-button>
|
|
74
|
-
<media-time-group slot="center" class="font-mono tabular-nums text-xs"></media-time-group>
|
|
75
|
-
<media-progress-bar slot="center" @if(waveform) waveform="{{ waveform }}" @endif aria-label="Audio progress"></media-progress-bar>
|
|
76
|
-
<media-mute-button slot="right" aria-label="Mute or unmute"></media-mute-button>
|
|
77
|
-
<media-volume-slider slot="right" aria-label="Volume"></media-volume-slider>
|
|
78
|
-
<media-playback-rate-button slot="right" rates="0.5,0.75,1,1.25,1.5,2" aria-label="Playback speed"></media-playback-rate-button>
|
|
79
|
-
<media-settings-menu slot="right" aria-label="Audio settings"></media-settings-menu>
|
|
80
|
-
<media-airplay-button slot="right" aria-label="Play on AirPlay"></media-airplay-button>
|
|
81
|
-
<media-remote-playback-button slot="right" aria-label="Play on another device"></media-remote-playback-button>
|
|
82
|
-
</video-skin>
|
|
83
|
-
@endif
|
|
84
|
-
</video-player>
|
|
85
|
-
</div>
|
|
86
|
-
</figure>
|
|
87
|
-
|
|
88
|
-
<style>
|
|
89
|
-
video-player:not(:defined) video-skin { display: none; }
|
|
90
|
-
video-player:defined > [data-native-fallback] { display: none; }
|
|
91
|
-
@media (prefers-reduced-motion: reduce) { video-player, video-player * { transition-duration: 0.01ms !important; } }
|
|
92
|
-
</style>
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
<script server>
|
|
2
|
-
interface ImageProps {
|
|
3
|
-
src: string
|
|
4
|
-
alt?: string
|
|
5
|
-
width?: number
|
|
6
|
-
height?: number
|
|
7
|
-
aspectRatio?: string
|
|
8
|
-
srcset?: string
|
|
9
|
-
sizes?: string
|
|
10
|
-
loading?: 'lazy' | 'eager'
|
|
11
|
-
decoding?: 'async' | 'sync' | 'auto'
|
|
12
|
-
fetchpriority?: 'high' | 'low' | 'auto'
|
|
13
|
-
priority?: boolean
|
|
14
|
-
decorative?: boolean
|
|
15
|
-
fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
|
|
16
|
-
position?: string
|
|
17
|
-
placeholder?: 'blur' | 'thumbhash' | 'color' | 'empty' | 'none' | string
|
|
18
|
-
placeholderColor?: string
|
|
19
|
-
class?: string
|
|
20
|
-
}
|
|
21
|
-
const props = defineProps<ImageProps>()
|
|
22
|
-
if (!props.decorative && !props.alt?.trim()) throw new TypeError('Image alt text is required unless decorative is true')
|
|
23
|
-
const src = props.src || ''
|
|
24
|
-
const alt = props.decorative ? '' : props.alt || ''
|
|
25
|
-
const width = props.width || ''
|
|
26
|
-
const height = props.height || ''
|
|
27
|
-
const ratio = props.aspectRatio || (width && height ? `${width} / ${height}` : '')
|
|
28
|
-
const srcset = props.srcset || ''
|
|
29
|
-
const sizes = props.sizes || ''
|
|
30
|
-
const loading = props.loading || ''
|
|
31
|
-
const decoding = props.decoding || ''
|
|
32
|
-
const fetchpriority = props.fetchpriority || ''
|
|
33
|
-
const fit = props.fit || 'cover'
|
|
34
|
-
const position = props.position || 'center'
|
|
35
|
-
const placeholder = props.placeholder || ''
|
|
36
|
-
const placeholderColor = props.placeholderColor || ''
|
|
37
|
-
const className = props.class || ''
|
|
38
|
-
const imageStyle = `${ratio ? `aspect-ratio:${ratio};` : ''}object-fit:${fit};object-position:${position};`
|
|
39
|
-
</script>
|
|
40
|
-
|
|
41
|
-
<StxImage
|
|
42
|
-
src="{{ src }}"
|
|
43
|
-
alt="{{ alt }}"
|
|
44
|
-
class="{{ className }}"
|
|
45
|
-
style="{{ imageStyle }}"
|
|
46
|
-
@if(width) width="{{ width }}" @endif
|
|
47
|
-
@if(height) height="{{ height }}" @endif
|
|
48
|
-
@if(srcset) srcset="{{ srcset }}" @endif
|
|
49
|
-
@if(sizes) sizes="{{ sizes }}" @endif
|
|
50
|
-
@if(loading) loading="{{ loading }}" @endif
|
|
51
|
-
@if(decoding) decoding="{{ decoding }}" @endif
|
|
52
|
-
@if(fetchpriority) fetchpriority="{{ fetchpriority }}" @endif
|
|
53
|
-
@if(props.priority) priority @endif
|
|
54
|
-
@if(props.decorative) aria-hidden="true" @endif
|
|
55
|
-
@if(placeholder) placeholder="{{ placeholder }}" @endif
|
|
56
|
-
@if(placeholderColor) placeholder-color="{{ placeholderColor }}" @endif
|
|
57
|
-
/>
|