@stacksjs/defaults 0.70.358 → 0.70.362
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-browse/SKILL.md +5 -4
- package/ai/skills/stacks-browse/scripts/browse.ts +48 -7
- package/app/Actions/AI/AskAction.ts +12 -17
- package/app/Actions/AI/SummaryAction.ts +12 -17
- package/app/Actions/LogAction.ts +11 -11
- package/ide/vscode/package.json +1 -1
- package/package.json +1 -1
- package/resources/components/Dashboard/Marketing/CampaignDeleteDialog.stx +11 -12
- package/resources/components/Dashboard/Marketing/MarketingListDeleteDialog.stx +15 -15
- package/resources/components/Dashboard/Marketing/SocialPostDeleteDialog.stx +11 -12
- package/resources/components/Dashboard/UI/ConfirmDialog.stx +51 -53
- package/resources/components/Dashboard/UI/Modal.stx +24 -20
|
@@ -114,10 +114,10 @@ Use `{"action":"assert","selector":"button","text":"Save","focused":true}` to ve
|
|
|
114
114
|
|
|
115
115
|
### Crawl (whole-site browser audit)
|
|
116
116
|
```bash
|
|
117
|
-
bun storage/framework/defaults/ai/skills/stacks-browse/scripts/browse.ts crawl <url> [--viewport 1280x900] [--max 500] [--path /extra-route] [--settle 350] [--progress]
|
|
117
|
+
bun storage/framework/defaults/ai/skills/stacks-browse/scripts/browse.ts crawl <url> [--viewport 1280x900] [--max 500] [--path /extra-route] [--settle 350] [--progress] [--summary]
|
|
118
118
|
```
|
|
119
|
-
Uses a fresh isolated page target per route,
|
|
120
|
-
session
|
|
119
|
+
Uses a fresh isolated page target per route, waits for each target to fully close,
|
|
120
|
+
and relaunches Chromium if its DevTools session or target lifecycle fails during a long audit, while
|
|
121
121
|
following every same-origin link it discovers.
|
|
122
122
|
The viewport is deterministic and defaults to `1280x900`; pass `--viewport 768x1024`
|
|
123
123
|
to repeat the same route audit at a tablet breakpoint.
|
|
@@ -125,7 +125,8 @@ Each page is checked for a non-200 document, console exceptions, failed
|
|
|
125
125
|
requests, and horizontal overflow. Repeat `--path` to seed routes that are not
|
|
126
126
|
linked from the starting page. The command prints every crawled path and exits
|
|
127
127
|
nonzero when any page fails. Add `--progress` to stream each completed page
|
|
128
|
-
during a long audit.
|
|
128
|
+
during a long audit. Add `--summary` to report totals and compact failure
|
|
129
|
+
diagnostics without printing every visited path or overflowing element.
|
|
129
130
|
|
|
130
131
|
## Stacks-Specific QA
|
|
131
132
|
|
|
@@ -264,12 +264,38 @@ async function createPage(port: number): Promise<BrowserPage> {
|
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
async function closePage(port: number, page: BrowserPage): Promise<
|
|
268
|
-
page.cdp.close()
|
|
267
|
+
async function closePage(port: number, page: BrowserPage): Promise<boolean> {
|
|
269
268
|
try {
|
|
270
|
-
await fetch(`http://127.0.0.1:${port}/json/close/${encodeURIComponent(page.targetId)}`)
|
|
269
|
+
const response = await fetch(`http://127.0.0.1:${port}/json/close/${encodeURIComponent(page.targetId)}`)
|
|
270
|
+
if (!response.ok) {
|
|
271
|
+
page.cdp.close()
|
|
272
|
+
return false
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
page.cdp.close()
|
|
277
|
+
return false
|
|
278
|
+
}
|
|
279
|
+
page.cdp.close()
|
|
280
|
+
|
|
281
|
+
// Chromium acknowledges target closure before every renderer/helper has
|
|
282
|
+
// actually left the target list. Creating the next target immediately can
|
|
283
|
+
// accumulate closing renderers across a large crawl, eventually dropping
|
|
284
|
+
// Network and Runtime events. Wait for the target to disappear so each route
|
|
285
|
+
// starts with the isolation the command promises.
|
|
286
|
+
for (let attempt = 0; attempt < 40; attempt++) {
|
|
287
|
+
try {
|
|
288
|
+
const targets = await (await fetch(`http://127.0.0.1:${port}/json/list`)).json() as any[]
|
|
289
|
+
if (!targets.some(target => target.id === page.targetId))
|
|
290
|
+
return true
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return false
|
|
294
|
+
}
|
|
295
|
+
await Bun.sleep(25)
|
|
271
296
|
}
|
|
272
|
-
|
|
297
|
+
|
|
298
|
+
return false
|
|
273
299
|
}
|
|
274
300
|
|
|
275
301
|
function kill(s: Session): void {
|
|
@@ -864,7 +890,11 @@ async function main() {
|
|
|
864
890
|
}
|
|
865
891
|
}
|
|
866
892
|
const replacePage = async (): Promise<void> => {
|
|
867
|
-
await closePage(session.port, browserPage)
|
|
893
|
+
const closed = await closePage(session.port, browserPage)
|
|
894
|
+
if (!closed) {
|
|
895
|
+
kill(session)
|
|
896
|
+
session = await launch()
|
|
897
|
+
}
|
|
868
898
|
browserPage = await createReplacementPage()
|
|
869
899
|
cdp = browserPage.cdp
|
|
870
900
|
}
|
|
@@ -1019,6 +1049,16 @@ async function main() {
|
|
|
1019
1049
|
|| page.consoleErrors.length > 0
|
|
1020
1050
|
|| page.failedRequests.length > 0
|
|
1021
1051
|
|| page.horizontalOverflowPx > 0)
|
|
1052
|
+
const summaryOnly = Boolean(flags.summary)
|
|
1053
|
+
const reportedFailures = summaryOnly
|
|
1054
|
+
? failures.map(page => ({
|
|
1055
|
+
path: page.path,
|
|
1056
|
+
status: page.status,
|
|
1057
|
+
consoleErrorCount: page.consoleErrors.length,
|
|
1058
|
+
failedRequestCount: page.failedRequests.length,
|
|
1059
|
+
horizontalOverflowPx: page.horizontalOverflowPx,
|
|
1060
|
+
}))
|
|
1061
|
+
: failures
|
|
1022
1062
|
console.log(JSON.stringify({
|
|
1023
1063
|
start: start.href,
|
|
1024
1064
|
browser: session.browser,
|
|
@@ -1026,8 +1066,9 @@ async function main() {
|
|
|
1026
1066
|
crawled: pages.length,
|
|
1027
1067
|
remaining: queue.length,
|
|
1028
1068
|
max,
|
|
1029
|
-
|
|
1030
|
-
|
|
1069
|
+
failureCount: failures.length,
|
|
1070
|
+
...(!summaryOnly ? { paths: pages.map(page => page.path) } : {}),
|
|
1071
|
+
failures: reportedFailures,
|
|
1031
1072
|
}, null, 2))
|
|
1032
1073
|
if (failures.length > 0)
|
|
1033
1074
|
process.exitCode = 1
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { ask } from '@stacksjs/ai'
|
|
4
|
+
import { log } from '@stacksjs/logging'
|
|
5
|
+
import { response } from '@stacksjs/router'
|
|
3
6
|
import { schema } from '@stacksjs/validation'
|
|
4
7
|
|
|
5
|
-
// TODO: this should have been auto-generated
|
|
6
|
-
interface Request {
|
|
7
|
-
question: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
8
|
export default new Action({
|
|
11
9
|
name: 'AiAskAction',
|
|
12
10
|
description: 'Ask AI',
|
|
13
11
|
method: 'POST',
|
|
12
|
+
apiResponse: true,
|
|
14
13
|
|
|
15
14
|
validations: {
|
|
16
15
|
question: {
|
|
@@ -19,22 +18,18 @@ export default new Action({
|
|
|
19
18
|
},
|
|
20
19
|
},
|
|
21
20
|
|
|
22
|
-
async handle(request:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
console.log(`Question received: ${question}`)
|
|
21
|
+
async handle(request: RequestInstance) {
|
|
22
|
+
await request.validate()
|
|
23
|
+
const question = String(request.get('question') || '').trim()
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
try {
|
|
26
|
+
return response.json({
|
|
29
27
|
data: await ask(question),
|
|
30
|
-
}
|
|
28
|
+
})
|
|
31
29
|
}
|
|
32
30
|
catch (error) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
error: 'An error occurred while processing your request.',
|
|
37
|
-
}
|
|
31
|
+
log.error('[ai] Ask request failed', { error })
|
|
32
|
+
return response.json({ message: 'The AI provider could not answer the question.' }, 502)
|
|
38
33
|
}
|
|
39
34
|
},
|
|
40
35
|
})
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { summarize } from '@stacksjs/ai'
|
|
4
|
+
import { log } from '@stacksjs/logging'
|
|
5
|
+
import { response } from '@stacksjs/router'
|
|
3
6
|
import { schema } from '@stacksjs/validation'
|
|
4
7
|
|
|
5
|
-
// TODO: this should have been auto-generated
|
|
6
|
-
interface Request {
|
|
7
|
-
text: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
8
|
export default new Action({
|
|
11
9
|
name: 'AiSummaryAction',
|
|
12
10
|
description: 'Summary AI',
|
|
13
11
|
method: 'POST',
|
|
12
|
+
apiResponse: true,
|
|
14
13
|
|
|
15
14
|
validations: {
|
|
16
15
|
text: {
|
|
@@ -19,22 +18,18 @@ export default new Action({
|
|
|
19
18
|
},
|
|
20
19
|
},
|
|
21
20
|
|
|
22
|
-
async handle(request:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
console.log(`Text received: ${text}`)
|
|
21
|
+
async handle(request: RequestInstance) {
|
|
22
|
+
await request.validate()
|
|
23
|
+
const text = String(request.get('text') || '').trim()
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
try {
|
|
26
|
+
return response.json({
|
|
29
27
|
data: await summarize(text),
|
|
30
|
-
}
|
|
28
|
+
})
|
|
31
29
|
}
|
|
32
30
|
catch (error) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
error: 'An error occurred while processing your request.',
|
|
37
|
-
}
|
|
31
|
+
log.error('[ai] Summary request failed', { error })
|
|
32
|
+
return response.json({ message: 'The AI provider could not summarize the text.' }, 502)
|
|
38
33
|
}
|
|
39
34
|
},
|
|
40
35
|
})
|
package/app/Actions/LogAction.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { log } from '@stacksjs/logging'
|
|
4
|
+
import { response } from '@stacksjs/router'
|
|
3
5
|
import { schema } from '@stacksjs/validation'
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
message: string
|
|
7
|
-
level: 'info' | 'warn' | 'error'
|
|
8
|
-
}
|
|
7
|
+
type LogLevel = 'info' | 'warn' | 'error'
|
|
9
8
|
|
|
10
9
|
export default new Action({
|
|
11
10
|
name: 'Dummy Logger',
|
|
12
11
|
description: 'This action is used to demo how to POST to a server and upon success, log a message.',
|
|
12
|
+
method: 'POST',
|
|
13
|
+
apiResponse: true,
|
|
13
14
|
|
|
14
15
|
// the request object is optional, but if it is provided, it will be used for validation
|
|
15
16
|
validations: {
|
|
@@ -29,14 +30,13 @@ export default new Action({
|
|
|
29
30
|
},
|
|
30
31
|
},
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!request)
|
|
35
|
-
return 'No request was provided.'
|
|
33
|
+
async handle(request: RequestInstance) {
|
|
34
|
+
await request.validate()
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
const message = String(request.get('message') || '').trim()
|
|
37
|
+
const level = String(request.get('level')) as LogLevel
|
|
38
|
+
log[level](message)
|
|
39
39
|
|
|
40
|
-
return `Logged
|
|
40
|
+
return response.json({ level, message: `Logged at ${level} level.` })
|
|
41
41
|
},
|
|
42
42
|
})
|
package/ide/vscode/package.json
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.362",
|
|
6
6
|
"description": "The complete managed Stacks application scaffold, including runtime defaults, AI guidance, editor metadata, and npm-backed project support files.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"license": "MIT",
|
|
@@ -6,17 +6,16 @@ const record = useReactiveProp('record', null as CampaignRecord | null)
|
|
|
6
6
|
const busy = useReactiveProp('busy', false)
|
|
7
7
|
const error = useReactiveProp('error', '')
|
|
8
8
|
const emit = defineEmits()
|
|
9
|
+
const description = derived(() => `The persisted campaign ${record()?.name || ''} will be permanently removed. Campaigns with delivery history are retained.`)
|
|
9
10
|
</script>
|
|
10
11
|
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
</section>
|
|
22
|
-
</div>
|
|
12
|
+
<ConfirmDialog
|
|
13
|
+
:isOpen="open()"
|
|
14
|
+
title="Delete campaign?"
|
|
15
|
+
:description="description()"
|
|
16
|
+
confirmLabel="Delete campaign"
|
|
17
|
+
:busy="busy()"
|
|
18
|
+
:error="error()"
|
|
19
|
+
@close="emit('close')"
|
|
20
|
+
@confirm="emit('confirm')"
|
|
21
|
+
/>
|
|
@@ -6,20 +6,20 @@ const record = useReactiveProp('record', null as MarketingListRecord | null)
|
|
|
6
6
|
const busy = useReactiveProp('busy', false)
|
|
7
7
|
const error = useReactiveProp('error', '')
|
|
8
8
|
const emit = defineEmits()
|
|
9
|
+
const protectedRecord = derived(() => Boolean(record()?.subscriberCount) || Boolean(record()?.campaignCount))
|
|
10
|
+
const description = derived(() => protectedRecord()
|
|
11
|
+
? `${record()?.name || 'This email list'} has membership or campaign history. Archive it instead to preserve relationships.`
|
|
12
|
+
: `${record()?.name || 'This email list'} will be permanently removed.`)
|
|
9
13
|
</script>
|
|
10
14
|
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
</Button>
|
|
23
|
-
</footer>
|
|
24
|
-
</section>
|
|
25
|
-
</div>
|
|
15
|
+
<ConfirmDialog
|
|
16
|
+
:isOpen="open()"
|
|
17
|
+
title="Delete email list?"
|
|
18
|
+
:description="description()"
|
|
19
|
+
confirmLabel="Delete email list"
|
|
20
|
+
:busy="busy()"
|
|
21
|
+
:disabled="protectedRecord()"
|
|
22
|
+
:error="error()"
|
|
23
|
+
@close="emit('close')"
|
|
24
|
+
@confirm="emit('confirm')"
|
|
25
|
+
/>
|
|
@@ -6,17 +6,16 @@ const record = useReactiveProp('record', null as SocialPostRecord | null)
|
|
|
6
6
|
const busy = useReactiveProp('busy', false)
|
|
7
7
|
const error = useReactiveProp('error', '')
|
|
8
8
|
const emit = defineEmits()
|
|
9
|
+
const description = derived(() => `The persisted ${record()?.platform || 'social'} post will be permanently removed. This does not remove a post already published on the external platform.`)
|
|
9
10
|
</script>
|
|
10
11
|
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
</section>
|
|
22
|
-
</div>
|
|
12
|
+
<ConfirmDialog
|
|
13
|
+
:isOpen="open()"
|
|
14
|
+
title="Delete social post?"
|
|
15
|
+
:description="description()"
|
|
16
|
+
confirmLabel="Delete social post"
|
|
17
|
+
:busy="busy()"
|
|
18
|
+
:error="error()"
|
|
19
|
+
@close="emit('close')"
|
|
20
|
+
@confirm="emit('confirm')"
|
|
21
|
+
/>
|
|
@@ -8,6 +8,8 @@ const confirmLabel = useReactiveProp('confirmLabel', 'Delete')
|
|
|
8
8
|
const cancelLabel = useReactiveProp('cancelLabel', 'Cancel')
|
|
9
9
|
const variant = useReactiveProp('variant', 'danger' as ConfirmVariant)
|
|
10
10
|
const busy = useReactiveProp('busy', false)
|
|
11
|
+
const disabled = useReactiveProp('disabled', false)
|
|
12
|
+
const error = useReactiveProp('error', '')
|
|
11
13
|
const emit = defineEmits()
|
|
12
14
|
|
|
13
15
|
function close(): void {
|
|
@@ -16,7 +18,7 @@ function close(): void {
|
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
function confirm(): void {
|
|
19
|
-
if (!busy())
|
|
21
|
+
if (!busy() && !disabled())
|
|
20
22
|
emit('confirm')
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -32,57 +34,53 @@ function iconName(): string {
|
|
|
32
34
|
|
|
33
35
|
</script>
|
|
34
36
|
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
</div>
|
|
50
|
-
<div class="flex-1 min-w-0">
|
|
51
|
-
<h3 class="font-semibold text-lg text-neutral-900 dark:text-white">
|
|
52
|
-
{{ title() }}
|
|
53
|
-
</h3>
|
|
54
|
-
<p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">
|
|
55
|
-
{{ description() }}
|
|
56
|
-
</p>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
37
|
+
<Modal
|
|
38
|
+
:isOpen="open()"
|
|
39
|
+
:title="title()"
|
|
40
|
+
:description="description()"
|
|
41
|
+
:closable="!busy()"
|
|
42
|
+
role="alertdialog"
|
|
43
|
+
size="sm"
|
|
44
|
+
@close="close"
|
|
45
|
+
>
|
|
46
|
+
<template #header-leading>
|
|
47
|
+
<div class="shrink-0 p-2.5 rounded-lg" :class="iconClass()">
|
|
48
|
+
<span class="block h-5 w-5" :class="iconName()"></span>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
59
51
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
52
|
+
<template #footer>
|
|
53
|
+
<div class="w-full">
|
|
54
|
+
<div :if="error()" role="alert" class="mb-4 p-3 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900">
|
|
55
|
+
{{ error() }}
|
|
56
|
+
</div>
|
|
57
|
+
<div class="flex gap-2 items-center justify-end">
|
|
58
|
+
<Button
|
|
59
|
+
variant="secondary"
|
|
60
|
+
:disabled="busy()"
|
|
61
|
+
@click="close"
|
|
62
|
+
>
|
|
63
|
+
{{ cancelLabel() }}
|
|
64
|
+
</Button>
|
|
65
|
+
<Button
|
|
66
|
+
:if="variant() === 'warning'"
|
|
67
|
+
variant="warning"
|
|
68
|
+
:loading="busy()"
|
|
69
|
+
:disabled="disabled()"
|
|
70
|
+
@click="confirm"
|
|
71
|
+
>
|
|
72
|
+
{{ confirmLabel() }}
|
|
73
|
+
</Button>
|
|
74
|
+
<Button
|
|
75
|
+
:if="variant() !== 'warning'"
|
|
76
|
+
variant="danger"
|
|
77
|
+
:loading="busy()"
|
|
78
|
+
:disabled="disabled()"
|
|
79
|
+
@click="confirm"
|
|
80
|
+
>
|
|
81
|
+
{{ confirmLabel() }}
|
|
82
|
+
</Button>
|
|
83
|
+
</div>
|
|
86
84
|
</div>
|
|
87
|
-
</
|
|
88
|
-
</
|
|
85
|
+
</template>
|
|
86
|
+
</Modal>
|
|
@@ -5,9 +5,10 @@ interface ModalProps {
|
|
|
5
5
|
description?: string
|
|
6
6
|
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'
|
|
7
7
|
closable?: boolean
|
|
8
|
+
role?: 'dialog' | 'alertdialog'
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
const { isOpen = false, title = '', description = '', size = 'md', closable = true } = defineProps<ModalProps>()
|
|
11
|
+
const { isOpen = false, title = '', description = '', size = 'md', closable = true, role = 'dialog' } = defineProps<ModalProps>()
|
|
11
12
|
|
|
12
13
|
const sizeClasses = {
|
|
13
14
|
sm: 'max-w-sm',
|
|
@@ -22,6 +23,7 @@ const open = useReactiveProp('isOpen', false)
|
|
|
22
23
|
const liveClosable = useReactiveProp('closable', true)
|
|
23
24
|
const liveTitle = useReactiveProp('title', '')
|
|
24
25
|
const liveDescription = useReactiveProp('description', '')
|
|
26
|
+
const liveRole = useReactiveProp('role', 'dialog')
|
|
25
27
|
const dialog = useRef<HTMLDialogElement>('dashboardModalDialog')
|
|
26
28
|
const activeElement = useActiveElement()
|
|
27
29
|
const scrollLocked = useScrollLock()
|
|
@@ -72,7 +74,7 @@ effect(() => {
|
|
|
72
74
|
<dialog
|
|
73
75
|
:if="open()"
|
|
74
76
|
ref="dashboardModalDialog"
|
|
75
|
-
role="
|
|
77
|
+
:role="liveRole()"
|
|
76
78
|
aria-modal="true"
|
|
77
79
|
:aria-label="liveTitle() || 'Dialog'"
|
|
78
80
|
class="fixed inset-0 m-0 p-0 h-dvh max-h-none w-screen max-w-none overflow-y-auto z-[55] bg-transparent dashboard-modal-layer dashboard-modal-dialog"
|
|
@@ -84,8 +86,9 @@ effect(() => {
|
|
|
84
86
|
class="relative w-full rounded-xl bg-white dark:bg-neutral-900 shadow-2xl {{ sizeClasses[size] }}"
|
|
85
87
|
>
|
|
86
88
|
<!-- Header -->
|
|
87
|
-
|
|
88
|
-
<div class="flex gap-
|
|
89
|
+
<div :if="liveTitle() || liveClosable()" class="flex gap-4 items-start justify-between p-6 border-b border-neutral-200 dark:border-neutral-700">
|
|
90
|
+
<div class="flex gap-3 items-start min-w-0">
|
|
91
|
+
<slot name="header-leading" />
|
|
89
92
|
<div class="min-w-0">
|
|
90
93
|
<h3 :if="liveTitle()" class="font-semibold text-lg text-neutral-900 dark:text-white">
|
|
91
94
|
{{ liveTitle() }}
|
|
@@ -94,26 +97,27 @@ effect(() => {
|
|
|
94
97
|
{{ liveDescription() }}
|
|
95
98
|
</p>
|
|
96
99
|
</div>
|
|
97
|
-
|
|
98
|
-
@if(closable)
|
|
99
|
-
<Button
|
|
100
|
-
variant="ghost"
|
|
101
|
-
size="sm"
|
|
102
|
-
iconOnly
|
|
103
|
-
ariaLabel="Close modal"
|
|
104
|
-
@click="closeModal"
|
|
105
|
-
>
|
|
106
|
-
<div class="h-5 w-5 i-hugeicons-cancel-01"></div>
|
|
107
|
-
</Button>
|
|
108
|
-
@endif
|
|
109
100
|
</div>
|
|
110
|
-
@endif
|
|
111
101
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
102
|
+
<Button
|
|
103
|
+
:if="liveClosable()"
|
|
104
|
+
variant="ghost"
|
|
105
|
+
size="sm"
|
|
106
|
+
iconOnly
|
|
107
|
+
ariaLabel="Close modal"
|
|
108
|
+
@click="closeModal"
|
|
109
|
+
>
|
|
110
|
+
<div class="h-5 w-5 i-hugeicons-cancel-01"></div>
|
|
111
|
+
</Button>
|
|
115
112
|
</div>
|
|
116
113
|
|
|
114
|
+
<!-- Body -->
|
|
115
|
+
@if($slots.default)
|
|
116
|
+
<div class="p-6">
|
|
117
|
+
<slot />
|
|
118
|
+
</div>
|
|
119
|
+
@endif
|
|
120
|
+
|
|
117
121
|
@if($slots.footer)
|
|
118
122
|
<div class="flex items-center justify-end px-6 py-4 border-neutral-200 border-t dark:border-neutral-700">
|
|
119
123
|
<slot name="footer" />
|