@arraypress/extension-kit 1.6.0 → 1.8.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/README.md +117 -4
- package/package.json +2 -2
- package/src/ai/model.d.ts +317 -0
- package/src/ai/model.js +312 -0
- package/src/build/store-frame.d.ts +10 -91
- package/src/build/store-frame.js +11 -159
- package/src/build/vendor.js +1 -1
- package/src/content/slices.d.ts +63 -0
- package/src/content/slices.js +80 -0
- package/src/content/watch.d.ts +71 -0
- package/src/content/watch.js +95 -0
- package/src/frame/window.d.ts +120 -0
- package/src/frame/window.js +197 -0
- package/src/index.d.ts +36 -4
- package/src/index.js +25 -4
- package/src/settings/saver.d.ts +13 -0
- package/src/settings/saver.js +11 -0
- package/src/styles/chrome-ui.css +11 -0
- package/src/testing/e2e.d.ts +15 -0
- package/src/testing/e2e.js +26 -1
- package/src/testing/fake-model.d.ts +87 -0
- package/src/testing/fake-model.js +142 -0
- package/src/testing/fake-storage.d.ts +2 -0
- package/src/testing/fake-storage.js +15 -3
- package/src/testing/heartbeat.d.ts +23 -0
- package/src/testing/heartbeat.js +43 -0
- package/src/testing/index.d.ts +13 -3
- package/src/testing/index.js +10 -3
- package/src/testing/messages.d.ts +33 -0
- package/src/testing/messages.js +51 -0
- package/src/testing/prompt.d.ts +20 -0
- package/src/testing/prompt.js +29 -0
- package/src/ui/model-row.d.ts +111 -0
- package/src/ui/model-row.js +108 -0
- package/src/ui/text-dialog.d.ts +44 -7
- package/src/ui/text-dialog.js +54 -16
package/src/ui/text-dialog.js
CHANGED
|
@@ -19,8 +19,16 @@ import { focusedElement } from '../dom/focus.js';
|
|
|
19
19
|
* @typedef {object} FieldRequest
|
|
20
20
|
* @property {string} name Its key in the answer ("folder"), and part of its id.
|
|
21
21
|
* @property {string} label Its label ("Folder").
|
|
22
|
-
* @property {string} [value] The text to start with. Default ''.
|
|
22
|
+
* @property {string | Promise<string>} [value] The text to start with. Default ''. A promise is text
|
|
23
|
+
* still being worked out (an on-device model's suggestion): the field waits, disabled, with
|
|
24
|
+
* `waiting` under it, and takes the text when it comes ('' if it fails).
|
|
25
|
+
* @property {string} [waiting] What the field says while its `value` is worked out ("Finding words…").
|
|
23
26
|
* @property {string} [placeholder] Shown while it's empty. Default none.
|
|
27
|
+
* @property {boolean} [multiline] A `<textarea>` (cr-textarea) rather than one line: Enter starts a new
|
|
28
|
+
* line, and only the action button saves. Default false.
|
|
29
|
+
* @property {number} [rows] A multi-line field's height in lines. Default 6.
|
|
30
|
+
* @property {string | ((text: string) => string)} [hint] Help under the field (cr-input's
|
|
31
|
+
* description), or a function of its text, kept up as it's typed ("12 words"). Default none.
|
|
24
32
|
* @property {(text: string) => string | null} [validate] Why the (non-blank) text can't be used,
|
|
25
33
|
* shown under the field; null when it can. Default: anything but blank.
|
|
26
34
|
* @property {number} [maxLength] Its `maxlength`. Default none.
|
|
@@ -74,14 +82,14 @@ export function checkText(text, validate = () => null) {
|
|
|
74
82
|
*
|
|
75
83
|
* @param {string} name - The field's name.
|
|
76
84
|
* @param {boolean} only - Whether it's the dialog's only field.
|
|
77
|
-
* @returns {{input: string, error: string}} The ids.
|
|
85
|
+
* @returns {{input: string, error: string, hint: string}} The ids.
|
|
78
86
|
* @example
|
|
79
|
-
* fieldIds('folder', false); // → {input: 'text-dialog-folder', error: 'text-dialog-folder-error'}
|
|
87
|
+
* fieldIds('folder', false); // → {input: 'text-dialog-folder', error: 'text-dialog-folder-error', hint: 'text-dialog-folder-hint'}
|
|
80
88
|
*/
|
|
81
89
|
export function fieldIds(name, only) {
|
|
82
90
|
return only
|
|
83
|
-
? { input: 'text-dialog-input', error: 'text-dialog-error' }
|
|
84
|
-
: { input: `text-dialog-${name}`, error: `text-dialog-${name}-error` };
|
|
91
|
+
? { input: 'text-dialog-input', error: 'text-dialog-error', hint: 'text-dialog-hint' }
|
|
92
|
+
: { input: `text-dialog-${name}`, error: `text-dialog-${name}-error`, hint: `text-dialog-${name}-hint` };
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
/**
|
|
@@ -101,8 +109,10 @@ export function fieldIds(name, only) {
|
|
|
101
109
|
*
|
|
102
110
|
* @typedef {object} Field
|
|
103
111
|
* @property {FieldRequest} request What was asked.
|
|
104
|
-
* @property {HTMLInputElement} input Its input.
|
|
112
|
+
* @property {HTMLInputElement | HTMLTextAreaElement} input Its input.
|
|
105
113
|
* @property {HTMLElement} error The reason under it.
|
|
114
|
+
* @property {HTMLElement} hint The help under it.
|
|
115
|
+
* @property {boolean} waiting Whether its value is still being worked out.
|
|
106
116
|
*/
|
|
107
117
|
|
|
108
118
|
/** Each page's dialog, made on first use. @type {WeakMap<Document, Parts>} */
|
|
@@ -161,14 +171,19 @@ function fieldFor(doc, request, only) {
|
|
|
161
171
|
const row = element(doc, 'div', { class: 'field' });
|
|
162
172
|
const label = element(doc, 'label', { for: ids.input });
|
|
163
173
|
label.textContent = request.label;
|
|
164
|
-
const
|
|
165
|
-
input
|
|
174
|
+
const described = `${ids.error} ${ids.hint}`;
|
|
175
|
+
const input = request.multiline
|
|
176
|
+
? element(doc, 'textarea', { id: ids.input, rows: String(request.rows ?? 6), 'aria-describedby': described })
|
|
177
|
+
: element(doc, 'input', { id: ids.input, type: 'text', autocomplete: 'off', 'aria-describedby': described });
|
|
178
|
+
input.value = typeof request.value === 'string' ? request.value : '';
|
|
166
179
|
if (request.placeholder) input.placeholder = request.placeholder;
|
|
167
180
|
if (request.maxLength !== undefined) input.maxLength = request.maxLength;
|
|
168
181
|
const error = element(doc, 'span', { id: ids.error, class: 'hint error' });
|
|
169
182
|
error.hidden = true;
|
|
170
|
-
|
|
171
|
-
|
|
183
|
+
const hint = element(doc, 'span', { id: ids.hint, class: 'hint secondary' });
|
|
184
|
+
hint.hidden = true;
|
|
185
|
+
row.append(label, input, error, hint);
|
|
186
|
+
return { row, field: { request, input, error, hint, waiting: false } };
|
|
172
187
|
}
|
|
173
188
|
|
|
174
189
|
/** The open dialog's settle function, per page. @type {WeakMap<Document, (answer: Record<string, string> | null) => void>} */
|
|
@@ -205,12 +220,14 @@ export function askFields({
|
|
|
205
220
|
parts.confirm.textContent = action;
|
|
206
221
|
parts.cancel.textContent = cancel;
|
|
207
222
|
|
|
208
|
-
const allOk = () => fields.every(({ input, request }) => checkText(input.value, request.validate).ok);
|
|
223
|
+
const allOk = () => fields.every(({ input, request, waiting }) => !waiting && checkText(input.value, request.validate).ok);
|
|
209
224
|
/** @param {Field} field */
|
|
210
|
-
const refresh = ({ input, error, request }) => {
|
|
211
|
-
const { reason } = checkText(input.value, request.validate);
|
|
225
|
+
const refresh = ({ input, error, hint, request, waiting }) => {
|
|
226
|
+
const { reason } = waiting ? { reason: '' } : checkText(input.value, request.validate);
|
|
212
227
|
error.textContent = reason;
|
|
213
228
|
error.hidden = !reason;
|
|
229
|
+
hint.textContent = waiting ? request.waiting ?? '' : typeof request.hint === 'function' ? request.hint(input.value) : request.hint ?? '';
|
|
230
|
+
hint.hidden = !hint.textContent;
|
|
214
231
|
};
|
|
215
232
|
for (const field of fields) refresh(field);
|
|
216
233
|
parts.confirm.disabled = !allOk();
|
|
@@ -232,8 +249,29 @@ export function askFields({
|
|
|
232
249
|
});
|
|
233
250
|
};
|
|
234
251
|
pending.set(doc, settle);
|
|
252
|
+
let typed = false;
|
|
253
|
+
// A value still being worked out: the field waits, then takes it, unless the dialog has moved on.
|
|
254
|
+
for (const field of fields) {
|
|
255
|
+
const { value } = field.request;
|
|
256
|
+
if (typeof value !== 'object' || !value) continue;
|
|
257
|
+
field.waiting = true;
|
|
258
|
+
field.input.disabled = true;
|
|
259
|
+
refresh(field);
|
|
260
|
+
value.catch(() => '').then((text) => {
|
|
261
|
+
if (signal.aborted) return;
|
|
262
|
+
field.waiting = false;
|
|
263
|
+
field.input.disabled = false;
|
|
264
|
+
field.input.value = text;
|
|
265
|
+
refresh(field);
|
|
266
|
+
parts.confirm.disabled = !allOk();
|
|
267
|
+
// The person was waiting for it: the focus moves in, unless they've been typing meanwhile.
|
|
268
|
+
if (!typed) field.input.focus();
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
parts.confirm.disabled = !allOk();
|
|
235
272
|
for (const field of fields) {
|
|
236
273
|
field.input.addEventListener('input', () => {
|
|
274
|
+
typed = true;
|
|
237
275
|
refresh(field);
|
|
238
276
|
parts.confirm.disabled = !allOk();
|
|
239
277
|
}, { signal });
|
|
@@ -256,7 +294,7 @@ export function askFields({
|
|
|
256
294
|
|
|
257
295
|
parts.dialog.showModal();
|
|
258
296
|
// The first field still to fill in, or the first, its text selected to type over.
|
|
259
|
-
(fields.find(({ input }) => !input.value) ?? fields[0])?.input.select();
|
|
297
|
+
(fields.find(({ input }) => !input.value && !input.disabled) ?? fields[0])?.input.select();
|
|
260
298
|
});
|
|
261
299
|
}
|
|
262
300
|
|
|
@@ -275,8 +313,8 @@ export function askFields({
|
|
|
275
313
|
* });
|
|
276
314
|
*/
|
|
277
315
|
export async function askText({
|
|
278
|
-
label, value, placeholder, validate, maxLength, ...dialog
|
|
316
|
+
label, value, placeholder, validate, maxLength, multiline, rows, hint, waiting, ...dialog
|
|
279
317
|
}) {
|
|
280
|
-
const answer = await askFields({ ...dialog, fields: [{ name: 'input', label, value, placeholder, validate, maxLength }] });
|
|
318
|
+
const answer = await askFields({ ...dialog, fields: [{ name: 'input', label, value, placeholder, validate, maxLength, multiline, rows, hint, waiting }] });
|
|
281
319
|
return answer?.input ?? null;
|
|
282
320
|
}
|