arcane-os 0.16.3 → 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,15 @@
|
|
|
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
|
+
|
|
3
13
|
## 0.16.3
|
|
4
14
|
|
|
5
15
|
- Remember browser-reported PWA installation in app-scoped DBOPFS and suppress
|
|
@@ -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
|
|
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
|
|