arcane-os 0.16.2 → 0.17.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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.0
|
|
4
|
+
|
|
5
|
+
- Add neutral `modal.configure({dismissible})` configuration, defaulting to
|
|
6
|
+
`true`. Setting it to `false` hides the close button and prevents Escape,
|
|
7
|
+
backdrop and close-button dismissal while preserving the owner's existing
|
|
8
|
+
programmatic close/destroy lifecycle and running-task behavior.
|
|
9
|
+
- Retain the configuration through population, open/close cycles and task
|
|
10
|
+
completion. Keep focus within useful content when hiding a focused close
|
|
11
|
+
control, and preserve default modal behavior for existing consumers.
|
|
12
|
+
|
|
13
|
+
## 0.16.3
|
|
14
|
+
|
|
15
|
+
- Remember browser-reported PWA installation in app-scoped DBOPFS and suppress
|
|
16
|
+
the SDK's floating and inline installation controls on later visits. Capture
|
|
17
|
+
native events immediately while restoring the saved flag; keep rendering,
|
|
18
|
+
component loading and worker registration independent.
|
|
19
|
+
- Preserve confirmed installation across late reads, prompt results and display
|
|
20
|
+
changes. Save installed-app launches as well as `appinstalled`, without
|
|
21
|
+
mistaking prompt acceptance or ordinary fullscreen for installation. Expose
|
|
22
|
+
initial storage readiness and complete persistence errors through the shared
|
|
23
|
+
install owner, and retain confirmed-install writes through owner disposal.
|
|
24
|
+
|
|
3
25
|
## 0.16.2
|
|
4
26
|
|
|
5
27
|
- Scope explicit application selection and workspace resolution to the named
|
|
@@ -11,7 +11,11 @@ export function getPwaInstall() {
|
|
|
11
11
|
if (sharedOwner && sharedOwner.state.status !== 'disposed') {
|
|
12
12
|
return sharedOwner;
|
|
13
13
|
}
|
|
14
|
-
const owner = {
|
|
14
|
+
const owner = {
|
|
15
|
+
get state() { return snapshot(); },
|
|
16
|
+
get ready() { return ready; },
|
|
17
|
+
subscribe, prompt, dismiss, dispose
|
|
18
|
+
};
|
|
15
19
|
const source = createArcaneEventSource(owner, {
|
|
16
20
|
source: 'arcane.pwa.install', eventTypes: [PWA_INSTALL_STATE_EVENT]
|
|
17
21
|
});
|
|
@@ -20,6 +24,9 @@ export function getPwaInstall() {
|
|
|
20
24
|
'(display-mode: standalone), (display-mode: minimal-ui), '
|
|
21
25
|
+ '(display-mode: fullscreen), (display-mode: window-controls-overlay)'
|
|
22
26
|
);
|
|
27
|
+
const installedDisplayMode = globalThis.matchMedia?.(
|
|
28
|
+
'(display-mode: standalone), (display-mode: minimal-ui), (display-mode: window-controls-overlay)'
|
|
29
|
+
);
|
|
23
30
|
const manifestUrl = globalThis.document?.querySelector('link[rel~="manifest"]')?.href;
|
|
24
31
|
const dismissalKey = `arcane.pwa.install.dismissed:${manifestUrl ?? globalThis.location?.href ?? ''}`;
|
|
25
32
|
let deferredPrompt = null;
|
|
@@ -28,6 +35,11 @@ export function getPwaInstall() {
|
|
|
28
35
|
let status = isRunningAsApp() ? 'running' : 'waiting';
|
|
29
36
|
let outcome = null;
|
|
30
37
|
let error = null;
|
|
38
|
+
let installed = isInstalledApp();
|
|
39
|
+
let storedInstalled = false;
|
|
40
|
+
let storageReady = false;
|
|
41
|
+
let storageError = null;
|
|
42
|
+
let saveTask = null;
|
|
31
43
|
try {
|
|
32
44
|
dismissed = globalThis.sessionStorage?.getItem(dismissalKey) === 'true';
|
|
33
45
|
} catch (storageError) {
|
|
@@ -38,9 +50,17 @@ export function getPwaInstall() {
|
|
|
38
50
|
return displayMode?.matches === true || globalThis.navigator?.standalone === true;
|
|
39
51
|
}
|
|
40
52
|
|
|
53
|
+
function isInstalledApp() {
|
|
54
|
+
// Ordinary browser fullscreen is not evidence of installation.
|
|
55
|
+
return installedDisplayMode?.matches === true || globalThis.navigator?.standalone === true;
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
function snapshot() {
|
|
42
|
-
return {
|
|
43
|
-
|
|
59
|
+
return {
|
|
60
|
+
status,
|
|
61
|
+
available: storageReady && deferredPrompt !== null && !installed && !isRunningAsApp() && !disposed,
|
|
62
|
+
installed, dismissed, outcome, error, storageError
|
|
63
|
+
};
|
|
44
64
|
}
|
|
45
65
|
|
|
46
66
|
function publish(nextStatus, nextError = null) {
|
|
@@ -67,24 +87,92 @@ export function getPwaInstall() {
|
|
|
67
87
|
}
|
|
68
88
|
}
|
|
69
89
|
|
|
90
|
+
async function loadInstallStorage() {
|
|
91
|
+
if (!globalThis.dbopfs) {
|
|
92
|
+
await import('arcane/DBOPFS');
|
|
93
|
+
}
|
|
94
|
+
const storage = globalThis.dbopfs;
|
|
95
|
+
if (!storage) {
|
|
96
|
+
throw new Error('PWA installation state could not open DBOPFS.');
|
|
97
|
+
}
|
|
98
|
+
await storage.readyPromise;
|
|
99
|
+
return storage;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function reportStorageError(failure) {
|
|
103
|
+
storageError = failure;
|
|
104
|
+
console.warn('Arcane PWA installation state could not be persisted or restored:', failure);
|
|
105
|
+
publish(status, error);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function restoreInstallation() {
|
|
109
|
+
try {
|
|
110
|
+
const storage = await storageTask;
|
|
111
|
+
const record = await storage.get('pwa', 'installed.json', true);
|
|
112
|
+
storedInstalled = record?.installed === true;
|
|
113
|
+
if (!disposed && storedInstalled) {
|
|
114
|
+
installed = true;
|
|
115
|
+
deferredPrompt = null;
|
|
116
|
+
}
|
|
117
|
+
} catch (failure) {
|
|
118
|
+
reportStorageError(failure);
|
|
119
|
+
}
|
|
120
|
+
storageReady = true;
|
|
121
|
+
if (!disposed) {
|
|
122
|
+
if (installed) {
|
|
123
|
+
publish(isRunningAsApp() ? 'running' : 'installed', error);
|
|
124
|
+
} else if (deferredPrompt && !isRunningAsApp()) {
|
|
125
|
+
publish('available', error);
|
|
126
|
+
} else {
|
|
127
|
+
publish(status, error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return snapshot();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function saveInstallation() {
|
|
134
|
+
// The initial read avoids rewriting an already remembered installation.
|
|
135
|
+
await ready;
|
|
136
|
+
if (storedInstalled) return;
|
|
137
|
+
const storage = await storageTask;
|
|
138
|
+
await storage.set(
|
|
139
|
+
'pwa',
|
|
140
|
+
'installed.json',
|
|
141
|
+
{installed: true}
|
|
142
|
+
);
|
|
143
|
+
storedInstalled = true;
|
|
144
|
+
storageError = null;
|
|
145
|
+
publish(status, error);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function rememberInstallation() {
|
|
149
|
+
installed = true;
|
|
150
|
+
deferredPrompt = null;
|
|
151
|
+
// Retain and observe this durable write even if the page owner detaches.
|
|
152
|
+
saveTask ??= saveInstallation().catch(reportStorageError);
|
|
153
|
+
}
|
|
154
|
+
|
|
70
155
|
function onBeforeInstallPrompt(event) {
|
|
71
|
-
if (disposed || isRunningAsApp() || status === '
|
|
156
|
+
if (disposed || installed || isRunningAsApp() || status === 'accepted') return;
|
|
72
157
|
event.preventDefault();
|
|
73
158
|
deferredPrompt = event;
|
|
74
159
|
outcome = null;
|
|
75
|
-
publish('available');
|
|
160
|
+
publish(storageReady ? 'available' : 'waiting');
|
|
76
161
|
}
|
|
77
162
|
|
|
78
163
|
function onInstalled() {
|
|
79
|
-
|
|
164
|
+
rememberInstallation();
|
|
80
165
|
// This event may precede Android's completion of WebAPK creation.
|
|
81
166
|
publish('installed');
|
|
82
167
|
}
|
|
83
168
|
|
|
84
169
|
function onDisplayModeChange() {
|
|
170
|
+
if (isInstalledApp()) rememberInstallation();
|
|
85
171
|
if (isRunningAsApp()) {
|
|
86
172
|
deferredPrompt = null;
|
|
87
173
|
publish('running');
|
|
174
|
+
} else if (installed) {
|
|
175
|
+
publish('installed');
|
|
88
176
|
} else if (status === 'running') {
|
|
89
177
|
publish('waiting');
|
|
90
178
|
}
|
|
@@ -108,7 +196,7 @@ export function getPwaInstall() {
|
|
|
108
196
|
}
|
|
109
197
|
|
|
110
198
|
function prompt() {
|
|
111
|
-
if (
|
|
199
|
+
if (!snapshot().available) return Promise.resolve(null);
|
|
112
200
|
const event = deferredPrompt;
|
|
113
201
|
deferredPrompt = null;
|
|
114
202
|
publish('prompting');
|
|
@@ -162,8 +250,12 @@ export function getPwaInstall() {
|
|
|
162
250
|
observe(globalThis, 'beforeinstallprompt', onBeforeInstallPrompt);
|
|
163
251
|
observe(globalThis, 'appinstalled', onInstalled);
|
|
164
252
|
observe(displayMode, 'change', onDisplayModeChange);
|
|
253
|
+
observe(installedDisplayMode, 'change', onDisplayModeChange);
|
|
165
254
|
observe(globalThis, 'pagehide', onPageHide);
|
|
166
255
|
sharedOwner = owner;
|
|
256
|
+
const storageTask = loadInstallStorage();
|
|
257
|
+
const ready = restoreInstallation();
|
|
258
|
+
if (installed) rememberInstallation();
|
|
167
259
|
return owner;
|
|
168
260
|
}
|
|
169
261
|
|
package/docs/reference/pwa.md
CHANGED
|
@@ -293,18 +293,29 @@ does not infer installation support from the user-agent string. See
|
|
|
293
293
|
## getPwaInstall()
|
|
294
294
|
|
|
295
295
|
Import `getPwaInstall` and `PWA_INSTALL_STATE_EVENT` from `arcane-os/pwa`.
|
|
296
|
-
`getPwaInstall()` synchronously returns the shared page owner with `state`,
|
|
296
|
+
`getPwaInstall()` synchronously returns the shared page owner with `state`, `ready`,
|
|
297
297
|
`subscribe`, `prompt`, `dismiss` and `dispose`. Call it early when owning a
|
|
298
298
|
separate install entry point so it can capture `beforeinstallprompt` before
|
|
299
299
|
loading the UI. The generated bootstrap already does this through
|
|
300
300
|
`mountPwaInstallPrompt()`.
|
|
301
301
|
|
|
302
|
-
`state` contains `status`, `available`, `dismissed`, `outcome
|
|
302
|
+
`state` contains `status`, `available`, `installed`, `dismissed`, `outcome`,
|
|
303
|
+
`error` and `storageError`.
|
|
303
304
|
Status is `waiting`, `available`, `prompting`, `accepted`, `dismissed`,
|
|
304
305
|
`installed`, `running`, `error` or `disposed`. `available` means a native event
|
|
305
|
-
is retained
|
|
306
|
+
is retained, the initial installation-record read has settled, and installation
|
|
307
|
+
has not been recorded or detected. A dismissed floating suggestion can still
|
|
308
|
+
have `available: true`. `installed` is true after browser-reported installation
|
|
309
|
+
or restoration of that app's saved installation record.
|
|
306
310
|
`outcome` is the browser's `accepted` or `dismissed` choice, or `null` before a
|
|
307
|
-
choice. `error` carries the complete prompt error, or `null
|
|
311
|
+
choice. `error` carries the complete prompt error, or `null`; `storageError`
|
|
312
|
+
carries the complete DBOPFS read or write error, or `null`.
|
|
313
|
+
|
|
314
|
+
`ready` resolves to the state after the initial DBOPFS read settles. Native
|
|
315
|
+
events are captured synchronously while this read runs. Only installation
|
|
316
|
+
availability waits for it; page rendering, component loading and worker
|
|
317
|
+
registration continue independently. A storage failure is logged and published
|
|
318
|
+
as `storageError`; `ready` still resolves and native installation remains usable.
|
|
308
319
|
|
|
309
320
|
`subscribe(listener, {emitCurrent: true, signal} = {})` immediately replays
|
|
310
321
|
state by default and returns an unsubscribe function. Later state travels
|
|
@@ -315,7 +326,8 @@ registration, storage initialization or model readiness.
|
|
|
315
326
|
Call `prompt()` directly from the user's install click, before any asynchronous
|
|
316
327
|
wait. It invokes the browser prompt in the same call stack, consumes the event
|
|
317
328
|
once, and returns a promise for the browser's choice. It resolves to `null`
|
|
318
|
-
when
|
|
329
|
+
when installation is unavailable, including while the saved state is loading
|
|
330
|
+
or after installation is remembered. Failure publishes
|
|
319
331
|
`error` state and rejects. A new native event is required for another prompt.
|
|
320
332
|
|
|
321
333
|
```javascript
|
|
@@ -336,8 +348,24 @@ installButton.addEventListener('click', function requestInstallation() {
|
|
|
336
348
|
|
|
337
349
|
`dismiss()` remembers the session choice without consuming the retained event
|
|
338
350
|
and returns the current state. A browser-native dismissed choice is remembered
|
|
339
|
-
too. `appinstalled` clears the event
|
|
340
|
-
|
|
351
|
+
too. `appinstalled` clears the event, publishes `installed`, and saves
|
|
352
|
+
`{installed: true}` as `pwa/installed.json` through the current app-scoped
|
|
353
|
+
DBOPFS singleton. An installed-app launch in standalone, minimal-ui or
|
|
354
|
+
window-controls-overlay mode, or with `navigator.standalone === true`, also
|
|
355
|
+
records installation. Ordinary fullscreen suppresses the current prompt but
|
|
356
|
+
does not record installation because browsers can enter fullscreen without
|
|
357
|
+
installing an app; see the [display-mode specification](https://drafts.csswg.org/mediaqueries-5/#display-modes).
|
|
358
|
+
|
|
359
|
+
Every new owner reads the saved flag, so both floating and explicit inline
|
|
360
|
+
controls stay hidden on subsequent visits in that browser origin's app scope.
|
|
361
|
+
The SDK never clears this flag or resets it when display mode changes. Prompt
|
|
362
|
+
acceptance and dismissal alone do not write it. The record is independent of
|
|
363
|
+
resource-check history and other application data. A pending read cannot undo
|
|
364
|
+
newly observed installation, and a confirmed installation's pending write is
|
|
365
|
+
retained through owner disposal. A write failure remains observable without
|
|
366
|
+
making the current installed session eligible again.
|
|
367
|
+
|
|
368
|
+
Running in an app display mode publishes `running`. These states
|
|
341
369
|
do not establish offline readiness. On Android, `appinstalled` can arrive
|
|
342
370
|
before WebAPK creation finishes. See the
|
|
343
371
|
[browser lifecycle distinction](https://web.dev/learn/pwa/detection/).
|
|
@@ -79,7 +79,7 @@ appropriate.
|
|
|
79
79
|
| [`markdown-document.html`](#markdown-documenthtml) | Renders and navigates a complete Markdown document with focusable fragments. | `configure()`<br>`load()`<br>`render()`<br>`clear()`<br>`fail()`<br>`focus()`<br>`focusFragment()`<br>`destroy()` | `markdown-document-ready`<br>`markdown-document-state`<br>`markdown-document-loading`<br>`markdown-document-rendered`<br>`markdown-document-empty`<br>`markdown-document-error`<br>`markdown-document-navigate` | Complete Markdown/state normalized; malformed input and Marked/DOM failures remain visible |
|
|
80
80
|
| [`markdown-editor.html`](#markdown-editorhtml) | Configurable Markdown authoring, toolbar, preview, title, and save surface. | `configure()`<br>`focus()`<br>`clear()`<br>`saveEntry()`<br>`destroy()` | `markdown-editor-ready`<br>`markdown-editor-change`<br>`markdown-editor-saved` | Editor values normalized; injected save result mixed |
|
|
81
81
|
| [`media-embed.html`](#media-embedhtml) | Loads a parsed YouTube video or playlist embed with ordinary hosting by default, optional privacy enhancement, and an external-platform action. | `configure()`<br>`load()`<br>`destroy()` | `media-embed-ready`<br>`media-load`<br>`media-error`<br>`media-open-platform` | URL/error normalized; iframe/platform behavior native |
|
|
82
|
-
| [`modal.html`](#modalhtml) | Generic modal with population,
|
|
82
|
+
| [`modal.html`](#modalhtml) | Generic modal with population, configurable user dismissal, actions, and concurrent task execution. | `configure()`<br>`populate()`<br>`open()`<br>`close()`<br>`runTasks()`<br>`destroy()`<br>`running`<br>`opened` | `modal-ready`<br>`modal-opened`<br>`modal-closed`<br>`modal-action` | Modal state normalized; injected task results mixed |
|
|
83
83
|
| [`output-panel.html`](#output-panelhtml) | Presents status, output, body, coverage, actions, pending, error, and cleared states. | `configure()`<br>`setOutput()`<br>`setBody()`<br>`setCoverage()`<br>`setActions()`<br>`setPending()`<br>`setStatus()`<br>`setError()`<br>`clear()`<br>`destroy()` | `output-panel-ready`<br>`output-panel-state`<br>`output-panel-change`<br>`output-panel-action`<br>`output-panel-error`<br>`output-panel-cleared` | DOM-normalized |
|
|
84
84
|
| [`preferences-form.html`](#preferences-formhtml) | Builds a schema-driven preferences form with submit, reset, busy, and status behavior. | `configure()`<br>`getValues()`<br>`setValues()`<br>`setBusy()`<br>`setStatus()`<br>`destroy()` | `preferences-form-ready`<br>`preferences-change`<br>`preferences-submit`<br>`preferences-reset` | Normalized form values |
|
|
85
85
|
| [`pwa-install.html`](#pwa-installhtml) | Presents a dismissible browser installation action with floating or inline placement. | `configure()`<br>`install()`<br>`dismiss()`<br>`destroy()`<br>`state`<br>`ready` | `pwa-install-ready`<br>`pwa-install-change`<br>`pwa-install-dismissed` | Browser install availability and outcome supplied by the shared PWA owner |
|
|
@@ -944,11 +944,28 @@ Shared dependencies: [`YouTubeMedia.js`](runtime-modules.md#youtubemediajs).
|
|
|
944
944
|
|
|
945
945
|
### Overview
|
|
946
946
|
|
|
947
|
-
Generic modal with population,
|
|
947
|
+
Generic modal with population, configurable user dismissal, actions, and concurrent task execution.
|
|
948
948
|
|
|
949
949
|
### Public surface
|
|
950
950
|
|
|
951
|
-
Methods/properties: `populate()`, `open()`, `close()`, `runTasks()`,
|
|
951
|
+
Methods/properties: `configure()`, `populate()`, `open()`, `close()`, `runTasks()`,
|
|
952
|
+
`destroy()`, `running`, `opened`.
|
|
953
|
+
|
|
954
|
+
`configure({dismissible})` synchronously returns the current `{dismissible}`
|
|
955
|
+
configuration, or `false` after destruction. `dismissible` starts as `true`;
|
|
956
|
+
omitting it preserves the current choice. A supplied value must be a boolean.
|
|
957
|
+
Setting it to `false` hides the close button and prevents user dismissal through
|
|
958
|
+
Escape, backdrop clicks and the close button. Setting it back to `true` restores
|
|
959
|
+
normal user dismissal. The choice persists across population, open/close cycles
|
|
960
|
+
and task completion. If the close button held focus when disabled, focus moves
|
|
961
|
+
to a content control or the focusable modal body.
|
|
962
|
+
|
|
963
|
+
This option does not prevent the owning application from calling `close()` or
|
|
964
|
+
`destroy()`. The existing running-task behavior remains: `close()` leaves a
|
|
965
|
+
running modal open unless called with its existing second `force` argument set
|
|
966
|
+
to `true`; `destroy()` releases the component. Product-specific conditions for
|
|
967
|
+
closing a modal belong to the application. Content, actions and task results
|
|
968
|
+
are preserved, and `runTasks()` continues to execute independent jobs concurrently.
|
|
952
969
|
|
|
953
970
|
Events: `modal-ready`, `modal-opened`, `modal-closed`, `modal-action`.
|
|
954
971
|
|
|
@@ -1069,6 +1086,12 @@ availability comes from the browser's `beforeinstallprompt` event through
|
|
|
1069
1086
|
`getPwaInstall()`. Without an available event, the suggestion stays hidden;
|
|
1070
1087
|
absence does not identify why installation is unavailable. Installed-app events,
|
|
1071
1088
|
accepted prompts, and an already running installed display mode hide it.
|
|
1089
|
+
The shared SDK owner persists browser-reported installation in app-scoped
|
|
1090
|
+
DBOPFS. Both floating and inline controls stay hidden while that record is
|
|
1091
|
+
loading and remain hidden on later visits when installation is remembered.
|
|
1092
|
+
Acceptance alone does not save installation. The component's `ready` means
|
|
1093
|
+
its methods and subscription are attached; storage completion belongs to the
|
|
1094
|
+
shared owner's `ready` promise and does not delay page rendering.
|
|
1072
1095
|
|
|
1073
1096
|
### Example
|
|
1074
1097
|
|
|
@@ -6231,13 +6231,24 @@ payload and current-state subscription behavior.
|
|
|
6231
6231
|
|
|
6232
6232
|
### Overview
|
|
6233
6233
|
|
|
6234
|
-
`getPwaInstall()` returns one synchronous page owner exposing `state`,
|
|
6234
|
+
`getPwaInstall()` returns one synchronous page owner exposing `state`, `ready`,
|
|
6235
6235
|
`subscribe`, `prompt`, `dismiss` and `dispose`. It captures the browser's
|
|
6236
6236
|
`beforeinstallprompt` event and observes `appinstalled` and app display-mode
|
|
6237
6237
|
changes. A missing event leaves installation availability unknown and the owner
|
|
6238
6238
|
waiting; it does not prove browser incompatibility.
|
|
6239
6239
|
|
|
6240
|
-
State contains `status`, `available`, `dismissed`, `outcome
|
|
6240
|
+
State contains `status`, `available`, `installed`, `dismissed`, `outcome`,
|
|
6241
|
+
`error` and `storageError`. The owner restores `pwa/installed.json` through the
|
|
6242
|
+
app-scoped DBOPFS singleton and saves `{installed: true}` after `appinstalled`
|
|
6243
|
+
or an installed-app launch. Remembered installation suppresses both floating
|
|
6244
|
+
and inline controls on later visits. Acceptance alone and ordinary fullscreen
|
|
6245
|
+
do not record installation.
|
|
6246
|
+
|
|
6247
|
+
`ready` resolves to the state once the initial read settles. Native events are
|
|
6248
|
+
captured immediately, while install availability waits for that read. Rendering
|
|
6249
|
+
and worker registration remain independent. Storage failures are logged and
|
|
6250
|
+
published in `storageError`; they do not reject `ready` or reverse an observed
|
|
6251
|
+
installation. Confirmed-install writes remain owned after disposal.
|
|
6241
6252
|
Subscriptions replay current state by default. `prompt()` must be called
|
|
6242
6253
|
directly within the install click to preserve native user activation. It
|
|
6243
6254
|
consumes the event once and returns the browser choice, resolves to `null` when
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcane-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
|
|
69
69
|
"test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
|
|
70
70
|
"test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
|
|
71
|
-
"test:functional": "node ./bin/arcane-test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/update-check.test.mjs",
|
|
71
|
+
"test:functional": "node ./bin/arcane-test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/update-check.test.mjs",
|
|
72
72
|
"test:integration": "node ./bin/arcane-test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-plan.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
|
|
73
73
|
"test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
|
|
74
74
|
"check": "node tools/check-source.mjs && npm test",
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
<button id="close" class="modal-button" part="close" type="button" aria-label="Close" title="Close">✕</button>
|
|
151
151
|
<header id="modal-header" class="modal-header" part="header" hidden><slot id="header-slot" name="header"></slot></header>
|
|
152
152
|
<input type="search" id="modal-search" class="modal-search hidden" part="search" placeholder="Search..." aria-label="Search modal content">
|
|
153
|
-
<div id="modal-content" class="modal-content" part="body"><slot name="body"></slot></div>
|
|
153
|
+
<div id="modal-content" class="modal-content" part="body" tabindex="-1"><slot name="body"></slot></div>
|
|
154
154
|
<footer id="modal-footer" class="modal-footer" part="footer" hidden><slot id="footer-slot" name="footer"></slot></footer>
|
|
155
155
|
</div>
|
|
156
156
|
</dialog>
|
|
@@ -174,11 +174,13 @@
|
|
|
174
174
|
const modalStack=window.modalStack=window.modalStack||[];
|
|
175
175
|
|
|
176
176
|
let running=false;
|
|
177
|
+
let dismissible = true;
|
|
177
178
|
let disposed=false;
|
|
178
179
|
let activeRun=Promise.resolve([]);
|
|
179
180
|
let returnFocus=null;
|
|
180
181
|
|
|
181
182
|
host.ready=false;
|
|
183
|
+
host.configure = configure;
|
|
182
184
|
host.populate=populate;
|
|
183
185
|
host.open=open;
|
|
184
186
|
host.close=close;
|
|
@@ -195,13 +197,18 @@
|
|
|
195
197
|
opened:{get:function getOpenedState(){return dialog.open;}}
|
|
196
198
|
});
|
|
197
199
|
|
|
198
|
-
dialog.addEventListener(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
dialog.addEventListener(
|
|
201
|
+
'cancel',
|
|
202
|
+
function handleDialogCancellation(event) {
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
if (dismissible) {
|
|
205
|
+
close(event);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
);
|
|
202
209
|
|
|
203
210
|
dialog.addEventListener('click',function closeFromDialogBackdrop(event){
|
|
204
|
-
if(event.target!==dialog||running){
|
|
211
|
+
if (event.target !== dialog || running || !dismissible) {
|
|
205
212
|
return;
|
|
206
213
|
}
|
|
207
214
|
const bounds=dialog.getBoundingClientRect();
|
|
@@ -214,9 +221,45 @@
|
|
|
214
221
|
}
|
|
215
222
|
});
|
|
216
223
|
|
|
217
|
-
closeButton.addEventListener(
|
|
218
|
-
|
|
219
|
-
|
|
224
|
+
closeButton.addEventListener(
|
|
225
|
+
'click',
|
|
226
|
+
function closeFromButton(event) {
|
|
227
|
+
if (dismissible) {
|
|
228
|
+
close(event);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
function configure(input = {}) {
|
|
234
|
+
if (disposed) return false;
|
|
235
|
+
if (input === null || !is.object(input) || is.array(input)) {
|
|
236
|
+
throw new TypeError('Modal configuration must be an object.');
|
|
237
|
+
}
|
|
238
|
+
if (input.dismissible !== undefined) {
|
|
239
|
+
if (!is.boolean(input.dismissible)) {
|
|
240
|
+
throw new TypeError('Modal dismissible must be a boolean.');
|
|
241
|
+
}
|
|
242
|
+
dismissible = input.dismissible;
|
|
243
|
+
}
|
|
244
|
+
const closeButtonFocused = shadowRoot.activeElement === closeButton;
|
|
245
|
+
updateCloseButton();
|
|
246
|
+
if (dialog.open && !dismissible && closeButtonFocused) {
|
|
247
|
+
focusOpenedModal();
|
|
248
|
+
}
|
|
249
|
+
return {dismissible};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function updateCloseButton() {
|
|
253
|
+
closeButton.classList.toggle('hidden', running || !dismissible);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function focusOpenedModal() {
|
|
257
|
+
const focusTarget = modalContent.querySelector('[autofocus],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),button:not([disabled]),a[href]')
|
|
258
|
+
|| (running || !dismissible ? modalContent : closeButton);
|
|
259
|
+
focusTarget.focus(
|
|
260
|
+
{preventScroll: true}
|
|
261
|
+
);
|
|
262
|
+
}
|
|
220
263
|
|
|
221
264
|
for(const slot of [headerSlot,footerSlot]){
|
|
222
265
|
const region=slot.parentElement;
|
|
@@ -253,10 +296,7 @@
|
|
|
253
296
|
?document.activeElement
|
|
254
297
|
:null;
|
|
255
298
|
dialog.showModal();
|
|
256
|
-
queueMicrotask(
|
|
257
|
-
const focusTarget=modalContent.querySelector('[autofocus],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),button:not([disabled]),a[href]')||closeButton;
|
|
258
|
-
focusTarget?.focus({preventScroll:true});
|
|
259
|
-
});
|
|
299
|
+
queueMicrotask(focusOpenedModal);
|
|
260
300
|
emit('modal-opened',{}, {opened:true});
|
|
261
301
|
}
|
|
262
302
|
return true;
|
|
@@ -274,7 +314,7 @@
|
|
|
274
314
|
if(wasOpen){
|
|
275
315
|
dialog.close();
|
|
276
316
|
}
|
|
277
|
-
|
|
317
|
+
updateCloseButton();
|
|
278
318
|
|
|
279
319
|
const stackIndex=modalStack.indexOf(host);
|
|
280
320
|
if(stackIndex>-1){
|
|
@@ -317,7 +357,7 @@
|
|
|
317
357
|
}
|
|
318
358
|
|
|
319
359
|
async function populate(content='<p>Content goes here</p>',search=false){
|
|
320
|
-
|
|
360
|
+
updateCloseButton();
|
|
321
361
|
modalSearch.classList.toggle('hidden',!search);
|
|
322
362
|
if(content instanceof Node){
|
|
323
363
|
modalContent.replaceChildren(content);
|
|
@@ -349,7 +389,7 @@
|
|
|
349
389
|
}
|
|
350
390
|
|
|
351
391
|
running=true;
|
|
352
|
-
|
|
392
|
+
updateCloseButton();
|
|
353
393
|
modalSearch.classList.add('hidden');
|
|
354
394
|
modalContent.replaceChildren();
|
|
355
395
|
|
|
@@ -399,16 +439,20 @@
|
|
|
399
439
|
return result.status==='rejected';
|
|
400
440
|
});
|
|
401
441
|
running=false;
|
|
402
|
-
|
|
442
|
+
updateCloseButton();
|
|
403
443
|
|
|
404
444
|
if(failed){
|
|
405
445
|
heading.innerText='Some Tasks Could Not Be Completed';
|
|
406
|
-
message.innerText=
|
|
446
|
+
message.innerText = dismissible
|
|
447
|
+
? 'You may close this message and try again.'
|
|
448
|
+
: 'Review the failed tasks and try again.';
|
|
407
449
|
return results;
|
|
408
450
|
}
|
|
409
451
|
|
|
410
452
|
heading.innerText='All Tasks Complete';
|
|
411
|
-
message.innerText=
|
|
453
|
+
message.innerText = dismissible
|
|
454
|
+
? 'Every task completed. You may close this message.'
|
|
455
|
+
: 'Every task completed.';
|
|
412
456
|
return results;
|
|
413
457
|
}
|
|
414
458
|
|