@optionfactory/fml 9.0.0-rc3 → 9.0.0-rc31

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.
Files changed (44) hide show
  1. package/README.md +16 -88
  2. package/dist/client-errors.iife.js +40 -49
  3. package/dist/client-errors.iife.js.map +1 -1
  4. package/dist/client-errors.iife.min.js +1 -1
  5. package/dist/client-errors.iife.min.js.map +1 -1
  6. package/dist/custom-elements.json +491 -51
  7. package/dist/fml.css +25 -20
  8. package/dist/fml.css.map +1 -1
  9. package/dist/fml.iife.js +5866 -2563
  10. package/dist/fml.iife.js.map +1 -1
  11. package/dist/fml.iife.min.js +1 -1
  12. package/dist/fml.iife.min.js.map +1 -1
  13. package/dist/ftl.d.mts +425 -231
  14. package/dist/ftl.iife.js +774 -1044
  15. package/dist/ftl.iife.js.map +1 -1
  16. package/dist/ftl.iife.min.js +1 -1
  17. package/dist/ftl.iife.min.js.map +1 -1
  18. package/dist/ftl.min.mjs +1 -1
  19. package/dist/ftl.min.mjs.map +1 -1
  20. package/dist/ftl.mjs +774 -1044
  21. package/dist/ftl.mjs.map +1 -1
  22. package/dist/ful.css +25 -20
  23. package/dist/ful.css.map +1 -1
  24. package/dist/ful.d.mts +3048 -556
  25. package/dist/ful.iife.js +4977 -1395
  26. package/dist/ful.iife.js.map +1 -1
  27. package/dist/ful.iife.min.js +1 -1
  28. package/dist/ful.iife.min.js.map +1 -1
  29. package/dist/ful.min.mjs +1 -1
  30. package/dist/ful.min.mjs.map +1 -1
  31. package/dist/ful.mjs +4975 -1396
  32. package/dist/ful.mjs.map +1 -1
  33. package/dist/httpc.d.mts +143 -123
  34. package/dist/httpc.iife.js +163 -175
  35. package/dist/httpc.iife.js.map +1 -1
  36. package/dist/httpc.iife.min.js +1 -1
  37. package/dist/httpc.iife.min.js.map +1 -1
  38. package/dist/httpc.min.mjs +1 -1
  39. package/dist/httpc.min.mjs.map +1 -1
  40. package/dist/httpc.mjs +163 -175
  41. package/dist/httpc.mjs.map +1 -1
  42. package/dist/vscode.html-custom-data.json +239 -39
  43. package/dist/web-types.json +498 -51
  44. package/package.json +2 -2
package/dist/ful.d.mts CHANGED
@@ -1,25 +1,55 @@
1
1
  import { ParsedElement } from './ftl.mjs';
2
- declare const LocalStorage: {
3
- save: (k: any, v: any) => void;
4
- load: (k: any) => any;
5
- remove: (k: any) => void;
6
- pop: (k: any) => any;
7
- };
8
- declare const SessionStorage: {
9
- save: (k: any, v: any) => void;
10
- load: (k: any) => any;
11
- remove: (k: any) => void;
12
- pop: (k: any) => any;
13
- };
14
- declare const VersionedLocalStorage: {
15
- save(key: any, revision: any, data: any): void;
16
- load(key: any, revision: any): any;
2
+ import { HttpClient } from './httpc.mjs';
3
+ export type JsonStorage = {
4
+ /**
5
+ * stores `JSON.stringify(v)`
6
+ * under `k`, throwing what the encoding or the storage throws: an unreachable
7
+ * storage, a full quota, a value json cannot encode
8
+ */
9
+ save: (k: string, v: any) => void;
10
+ /**
11
+ * the value stored under `k`, decoded;
12
+ * undefined when there is none, when the storage cannot be reached, or when the
13
+ * stored text is not json, in which case the entry is also removed
14
+ */
15
+ load: (k: string) => any;
16
+ /**
17
+ * removes the entry under `k`
18
+ */
19
+ remove: (k: string) => void;
20
+ /**
21
+ * what `load` answers for `k`, removing the
22
+ * entry
23
+ */
24
+ pop: (k: string) => any;
17
25
  };
18
- declare const VersionedSessionStorage: {
19
- save(key: any, revision: any, data: any): void;
20
- load(key: any, revision: any): any;
26
+ export type VersionedJsonStorage = {
27
+ /**
28
+ * stores
29
+ * `data` with `revision` under `key`, throwing what `JsonStorage.save` throws
30
+ */
31
+ save: (key: string, revision: string, data: any) => void;
32
+ /**
33
+ * the data stored under
34
+ * `key` when it was saved with this exact revision; otherwise undefined, and the
35
+ * entry is removed, whether it holds another revision, holds no revision at
36
+ * all, or is missing
37
+ */
38
+ load: (key: string, revision: string) => any;
21
39
  };
40
+ /** `localStorage`, holding json. */
41
+ declare const LocalStorage: JsonStorage;
42
+ /** `sessionStorage`, holding json. */
43
+ declare const SessionStorage: JsonStorage;
44
+ /** `localStorage`, holding json beside a revision. */
45
+ declare const VersionedLocalStorage: VersionedJsonStorage;
46
+ /** `sessionStorage`, holding json beside a revision. */
47
+ declare const VersionedSessionStorage: VersionedJsonStorage;
22
48
  export type AsyncExtension = {
49
+ /**
50
+ * one per `asyncOn` listener the event
51
+ * reached, in the order they ran
52
+ */
23
53
  promises: Promise<any>[];
24
54
  };
25
55
  export type AsyncEvent = Event & {
@@ -27,7 +57,8 @@ export type AsyncEvent = Event & {
27
57
  };
28
58
  /**
29
59
  * @typedef {Object} AsyncExtension
30
- * @property {Promise<any>[]} promises
60
+ * @property {Promise<any>[]} promises one per `asyncOn` listener the event
61
+ * reached, in the order they ran
31
62
  * @typedef {Event & { async?: AsyncExtension }} AsyncEvent
32
63
  */
33
64
  /**
@@ -38,42 +69,80 @@ export type AsyncEvent = Event & {
38
69
  */
39
70
  declare class AsyncEvents {
40
71
  /**
41
- * Dispatches an event and handles asynchronous resolution based on the execution mode.
42
- * @param {HTMLElement} el - The target element dispatching the event.
43
- * @param {AsyncEvent} evt - The event instance.
44
- * @param {{mode?: 'broadcast' | 'pipeline' | 'delegate'}} [options] - Configuration options (defaults to 'broadcast').
45
- * @returns {Promise<any>} Resolves with an array of values for broadcasts, a single value for pipelines/delegates, or undefined.
72
+ * Dispatches `evt` on `el` synchronously, then waits for the answers of the
73
+ * `asyncOn` listeners it reached, on `el` or, for a bubbling event, on its
74
+ * ancestors. Listeners added with a plain `addEventListener` run but are not
75
+ * awaited. In `broadcast` mode, the default, it resolves with every answer
76
+ * in the order the listeners ran, an empty array when none did. In
77
+ * `pipeline` mode it accepts at most one listener and resolves with its
78
+ * answer, or undefined when none ran. In `delegate` mode it requires exactly
79
+ * one listener and resolves with its answer.
80
+ * @param {HTMLElement} el the element to dispatch on
81
+ * @param {AsyncEvent} evt the event to dispatch
82
+ * @param {{mode?: 'broadcast' | 'pipeline' | 'delegate'}} [options]
83
+ * @returns {Promise<any>} the answer or answers as the mode describes
84
+ * @throws what the first answer to fail threw or rejected with; or an
85
+ * Error naming the event and the mode when `pipeline` reached more than one
86
+ * listener or `delegate` did not reach exactly one, in which case the
87
+ * listeners have already run and their outcome is discarded. Both arrive as
88
+ * the rejection of the returned promise.
46
89
  */
47
90
  static fireAsync(el: HTMLElement, evt: AsyncEvent, options?: {
48
91
  mode?: 'broadcast' | 'pipeline' | 'delegate';
49
92
  }): Promise<any>;
50
93
  /**
51
- * Registers an asynchronous event listener wrapper.
52
- * @param {HTMLElement} el - The target element.
53
- * @param {string} type - The event name/type.
54
- * @param {Function} fn - The async listener middleware function returning the execution result.
55
- * @param {AddEventListenerOptions} [options] - Native addEventListener options.
56
- * @returns {EventListener} The underlying proxy listener function needed for cleanup via asyncOff.
94
+ * Adds a listener whose answer `fireAsync` can await. When the event
95
+ * reaches it, the listener attaches a promise to `evt.async.promises`,
96
+ * creating the list when it is the first, then calls `fn` with the event;
97
+ * the promise settles with what `fn` returns or throws, awaited when it is a
98
+ * promise. The event is dispatched the usual way, so an event fired with a
99
+ * plain `dispatchEvent` runs `fn` too, and nobody awaits its answer.
100
+ * @param {HTMLElement} el the element to listen on
101
+ * @param {string} type the event type
102
+ * @param {(evt: any) => any} fn called with the event; its result is the answer
103
+ * @param {AddEventListenerOptions} [options] passed to `addEventListener`
104
+ * @returns {EventListener} the listener actually added, which `asyncOff` takes
57
105
  */
58
- static asyncOn(el: HTMLElement, type: string, fn: Function, options?: AddEventListenerOptions): EventListener;
106
+ static asyncOn(el: HTMLElement, type: string, fn: (evt: any) => any, options?: AddEventListenerOptions): EventListener;
59
107
  /**
60
- * Unregisters an asynchronous event listener proxy.
61
- * @param {HTMLElement} el - The target element.
62
- * @param {string} type - The event name/type.
63
- * @param {EventListener} listener - The proxy listener instance previously returned by asyncOn.
64
- * @param {EventListenerOptions} [options] - Native removeEventListener options.
108
+ * Removes a listener added by `asyncOn`.
109
+ * @param {HTMLElement} el the element it listens on
110
+ * @param {string} type the event type it was added for
111
+ * @param {EventListener} listener what `asyncOn` returned, not the `fn` given to it
112
+ * @param {EventListenerOptions} [options] passed to `removeEventListener`
113
+ * @returns {void}
65
114
  */
66
115
  static asyncOff(el: HTMLElement, type: string, listener: EventListener, options?: EventListenerOptions): void;
67
116
  /**
68
- * Mixes the asynchronous execution engine extensions into target class prototypes.
69
- * @param {...Function} classes - The target class constructors to decorate.
117
+ * Assigns `fireAsync(evt, options)`, `asyncOn(type, fn, options)` and
118
+ * `asyncOff(type, listener, options)` onto each class's prototype, each
119
+ * calling the static of the same name with the instance as the element.
120
+ * Members of those names already on the prototype are overwritten.
121
+ * @param {...Function} classes the classes to extend
122
+ * @returns {void}
70
123
  */
71
124
  static mixInto(...classes: Function[]): void;
72
125
  }
126
+ /**
127
+ * Asks before the page is left, through the browser's own `beforeunload`
128
+ * prompt.
129
+ */
130
+ declare class PageLeave {
131
+ /**
132
+ * Asks before the page is left while `isDirty()` answers true, until
133
+ * `signal` aborts. The browser words the prompt, and asks only once the
134
+ * user has interacted with the page.
135
+ * @param {() => boolean} isDirty
136
+ * @param {AbortSignal} [signal]
137
+ */
138
+ static guard(isDirty: () => boolean, signal?: AbortSignal): void;
139
+ }
73
140
  export type Claim = {
74
141
  readonly stale: boolean;
75
142
  };
76
143
  /**
144
+ * A hold on one generation of a `Claims`: `stale` turns true once a later
145
+ * `take()` or `invalidate()` on it starts a newer generation.
77
146
  * @typedef {{ readonly stale: boolean }} Claim
78
147
  */
79
148
  /**
@@ -99,50 +168,95 @@ declare class Claims {
99
168
  * @returns {Claim}
100
169
  */
101
170
  hold(): Claim;
102
- /** Supersedes every claim without holding a new one. */
171
+ /**
172
+ * Supersedes every claim without holding a new one.
173
+ * @returns {void}
174
+ */
103
175
  invalidate(): void;
104
176
  }
177
+ export type Describable = {
178
+ describedBy(el: HTMLElement): boolean;
179
+ };
105
180
  /**
106
- * Sleeping, debouncing and throttling. Debounce and throttle both return the
107
- * wrapped function together with a cancel function.
181
+ * Something that adds elements to the accessible description of the control it
182
+ * owns, such as a field: `describedBy(el)` adds `el` to the control's
183
+ * `aria-describedby` and answers whether it did.
184
+ * @typedef {{ describedBy(el: HTMLElement): boolean }} Describable
185
+ */
186
+ /**
187
+ * The nearest ancestor of `el` that has a `describedBy` method. Content
188
+ * slotted inside a field does not write the control's `aria-describedby`
189
+ * itself: once it has something to offer, it hands the element carrying the
190
+ * words to the `describedBy` of the ancestor this answers.
191
+ * @param {Element} el
192
+ * @returns {(Element & Describable) | null} null when no ancestor has one
193
+ */
194
+ declare const describable: (el: Element) => (Element & Describable) | null;
195
+ /**
196
+ * Sleeping, debouncing and throttling. `debounce` and `throttle` both return
197
+ * the wrapped function together with an abort function, which cancels the call
198
+ * pending and leaves the wrapped function working.
108
199
  */
109
200
  declare class Timing {
110
- /** Resolves after the given milliseconds. @param {number} ms */
111
- static sleep(ms: number): Promise<any>;
112
201
  /**
113
- * Executes only after a period of inactivity (pause in events).
114
- * Respond to the "end" of a series of events.
202
+ * @param {number} ms
203
+ * @returns {Promise<void>} resolving after `ms` milliseconds
204
+ */
205
+ static sleep(ms: number): Promise<void>;
206
+ /**
207
+ * Collapses a burst of calls into one call of `func`, a burst ending once
208
+ * `timeoutMs` pass without a call. By default `func` is called at the end
209
+ * of the burst with the arguments of its last call. With `immediate` it is
210
+ * called at the first call of a burst, with that call's arguments, and not
211
+ * at the end. Without `immediate`, a call the wrapped function receives
212
+ * from inside `func` starts a new burst.
115
213
  * @param {number} timeoutMs
116
- * @param {function} func
117
- * @param {{ immediate?: boolean }} [options] - immediate fires on the leading edge instead of the trailing one
118
- * @returns {[function, function]}
214
+ * @param {(...args: any[]) => any} func
215
+ * @param {{ immediate?: boolean }} [options]
216
+ * @returns {[(...args: any[]) => void, () => void]} the wrapped function
217
+ * and the abort function
119
218
  */
120
- static debounce(timeoutMs: number, func: Function, options?: {
219
+ static debounce(timeoutMs: number, func: (...args: any[]) => any, options?: {
121
220
  immediate?: boolean;
122
- }): [Function, Function];
221
+ }): [(...args: any[]) => void, () => void];
123
222
  /**
124
- * Executes at most once per specified time interval, regardless of ongoing events.
223
+ * Calls `func` at most once per `timeoutMs`, always with the arguments of
224
+ * the latest call. With `leading`, on by default, a call arriving once the
225
+ * interval since the previous call of `func` has passed calls it at once;
226
+ * without it, the first call only starts the interval. With `trailing`, on
227
+ * by default, a call arriving inside the interval schedules `func` for the
228
+ * interval's end; without it, such a call is dropped.
125
229
  * @param {number} timeoutMs
126
- * @param {function} func
127
- * @param {{ leading?: boolean, trailing?: boolean }} [options] - which edges of the interval call, both by default
128
- * @returns {[function, function]}
230
+ * @param {(...args: any[]) => any} func
231
+ * @param {{ leading?: boolean, trailing?: boolean }} [options]
232
+ * @returns {[(...args: any[]) => void, () => void]} the wrapped function
233
+ * and the abort function
129
234
  */
130
- static throttle(timeoutMs: number, func: Function, options?: {
235
+ static throttle(timeoutMs: number, func: (...args: any[]) => any, options?: {
131
236
  leading?: boolean;
132
237
  trailing?: boolean;
133
- }): [Function, Function];
238
+ }): [(...args: any[]) => void, () => void];
134
239
  }
135
- /** Field wiring: extracting and filling values, pinning problems to the fields they name. */
240
+ /**
241
+ * Moves values between a form's named controls and a nested object, and pins
242
+ * problems onto the controls they name. Works on any native form, not only
243
+ * the one inside a `ful-form`. Every method reads the controls through
244
+ * `form.elements`, so a control in the subtree that belongs to another form
245
+ * (a nested form, a `form` attribute pointing elsewhere) is never read,
246
+ * written or marked.
247
+ */
136
248
  declare class Bindings {
137
249
  #private;
138
250
  /**
139
- * Flattens a nested object into dotted keys, stopping wherever `stops` names
140
- * a key: a field named `address` takes the whole object, while one named
141
- * `address.city` takes the leaf.
251
+ * Flattens a nested object into dotted keys, array indexes becoming numeric
252
+ * segments (`a.0.b`). It does not descend below a key that `stops` names, so
253
+ * with a field named `address` the whole object is kept under `address`,
254
+ * while with one named `address.city` the leaf is kept under `address.city`.
255
+ * A null value is kept as a leaf.
142
256
  * @param {{ [x: string]: any; }} obj
143
- * @param {string} prefix
144
- * @param {Set<String>} stops - the names the form actually has fields for
145
- * @return {{ [x: string]: any; }}
257
+ * @param {string} prefix prepended to every key, `''` at the root
258
+ * @param {Set<string>} stops the dotted keys to keep whole
259
+ * @returns {{ [x: string]: any; }} a flat object keyed by dotted path
146
260
  */
147
261
  static flatten(obj: {
148
262
  [x: string]: any;
@@ -151,19 +265,37 @@ declare class Bindings {
151
265
  };
152
266
  /**
153
267
  * Writes a value into an object at a dotted path, creating the intermediate
154
- * objects and arrays the path implies. A numeric segment makes an array.
155
- * @param {any} result
156
- * @param {string} path - a field name, `a.b` or `a[0].b`
268
+ * objects and arrays the path implies: a numeric segment makes its container
269
+ * an array. Brackets are not parsed, so `a[0]` is a plain key.
270
+ *
271
+ * An undefined value declares the path without filling it: an entry already
272
+ * there is kept, a missing one is set to null. A scalar or a null left on the
273
+ * path by a shorter overlapping name (`a` written before `a.b`) is replaced
274
+ * by a container, so the longer name wins whichever order they arrive in.
275
+ * @param {any} result the object or array to write into
276
+ * @param {string} path a dotted name such as `a.b` or `a.0.b`
157
277
  * @param {any} value
278
+ * @returns {any} the root written into, to be used in place of `result`: a
279
+ * new array replaces it when the first segment is numeric and `result` is
280
+ * not an array
281
+ * @throws {Error} when a segment is `__proto__`, `prototype` or `constructor`,
282
+ * before anything is written
158
283
  */
159
284
  static providePath(result: any, path: string, value: any): any;
160
285
  /**
161
- * Reads one control's value the way its kind demands: an unchecked radio
162
- * answers undefined so it contributes nothing, a checkbox answers its
163
- * checked state, a multiple select answers its selected values, and a blank
164
- * native control answers null rather than an empty string.
286
+ * Reads one control's value by its kind. The kind is read from the `type`
287
+ * attribute and the tag, so a ful element answers its own `value`.
288
+ *
289
+ * - a radio answers its `value` when checked and undefined otherwise
290
+ * - a checkbox answers its `checked` state
291
+ * - `data-ful-bind-type="boolean"` decodes the value: a radio answers
292
+ * whether its value is `'true'`, any other control answers null when
293
+ * blank and whether its value is `'true'` otherwise
294
+ * - a multiple select answers the array of its selected values
295
+ * - a native input, select or textarea answers null when blank
296
+ * - anything else answers its `value` unchanged
165
297
  * @param {Element & {dataset?: any} & {checked?: boolean} & {value?: any}} el
166
- * @returns {any} the value, or undefined where the control contributes none
298
+ * @returns {any} the value; undefined for an unchecked radio
167
299
  */
168
300
  static extract(el: Element & {
169
301
  dataset?: any;
@@ -172,13 +304,32 @@ declare class Bindings {
172
304
  } & {
173
305
  value?: any;
174
306
  }): any;
175
- static extractFrom(form: any, submitter: any): {};
176
307
  /**
177
- * Writes a value into one control, the inverse of `extract`: a radio is
178
- * checked when its own value matches, a checkbox takes the value as its
179
- * checked state, and a multiple select selects the options the list names.
308
+ * Reads the form's named controls into a nested object, the dotted names
309
+ * deciding its shape (see `providePath`) and each value read by `extract`.
310
+ * Controls are read in document order, so of two overlapping names the
311
+ * later one decides. A control is skipped when it has no `name`, when it
312
+ * matches `:disabled` (its own `disabled` or a disabled fieldset's), or when
313
+ * it is a submit, reset or plain button. The submitter is the exception to
314
+ * both of the last two: it contributes its name and value even while
315
+ * disabled.
316
+ * @param {HTMLFormElement} form
317
+ * @param {HTMLElement} [submitter] the button that submitted, when there is one
318
+ * @returns {any} the nested values; an array when the first segment of a name is numeric
319
+ * @throws {Error} when a name has a segment `providePath` refuses
320
+ */
321
+ static extractFrom(form: HTMLFormElement, submitter?: HTMLElement): any;
322
+ /**
323
+ * Writes a value into one control, the inverse of `extract`.
324
+ *
325
+ * - a radio is checked when `raw` is not null and its `value` attribute
326
+ * equals `String(raw)`, so a number or a boolean matches its text
327
+ * - a checkbox takes `raw` as its `checked` state
328
+ * - a multiple select selects the options whose value is in `raw`, compared
329
+ * as text: an array selects each entry, a scalar selects one, null selects none
330
+ * - anything else takes `raw` as its `value`
180
331
  * @param {Element & {dataset?: any} & {checked?: boolean} & {value?: any}} el
181
- * @param {any} raw the value as it arrived, coerced per control kind
332
+ * @param {any} raw
182
333
  */
183
334
  static mutate(el: Element & {
184
335
  dataset?: any;
@@ -187,8 +338,60 @@ declare class Bindings {
187
338
  } & {
188
339
  value?: any;
189
340
  }, raw: any): void;
190
- static mutateIn(form: any, values: any): void;
191
- static errors(form: any, es: any, scrollOnError: any): void;
341
+ /**
342
+ * Writes a nested object onto the form's named controls: the object is
343
+ * flattened with the control names as stops (see `flatten`), and each key
344
+ * is written by `mutate` into every control carrying exactly that name. A
345
+ * key naming no control is ignored, and a control the object does not name
346
+ * keeps its value.
347
+ * @param {HTMLFormElement} form
348
+ * @param {{ [x: string]: any; }} values
349
+ */
350
+ static mutateIn(form: HTMLFormElement, values: {
351
+ [x: string]: any;
352
+ }): void;
353
+ /**
354
+ * Shows problems on the form, replacing whatever the previous call showed.
355
+ *
356
+ * Every named control's custom validity is first cleared. A problem whose
357
+ * `type` is `FIELD_ERROR` or `INVALID_FORMAT` and whose `context` is not
358
+ * empty is pinned to a control: brackets in the context are turned into
359
+ * dots (`users[0].name` becomes `users.0.name`), and the longest leading
360
+ * part of it that names a control wins, so an exact field takes the problem
361
+ * alone and a composite field named for a prefix catches the problems of its
362
+ * subtree. Each control of that name gets `setCustomValidity(reason, rest)`,
363
+ * `rest` being the remainder of the path (`''` on an exact match) that a
364
+ * composite can use to route the problem to an inner control. Several
365
+ * problems reaching one control with the same `rest` are joined one per
366
+ * line into a single call.
367
+ *
368
+ * A control counts only when it can show a problem, through a
369
+ * `setCustomValidity` of its own: a form-associated element that has none is
370
+ * skipped, so its problems fall to a shorter prefix or to the banner instead
371
+ * of being dropped.
372
+ *
373
+ * Every other problem, and a field problem naming no control, is shown in
374
+ * each `ful-errors` banner of the form: the banner gets `role="alert"`, is
375
+ * revealed and holds the reasons one per line. With no such problem the
376
+ * banner is emptied and hidden.
377
+ *
378
+ * Each `ful-field-error` of the form gets `aria-live="off"` when
379
+ * `scrollOnError` is set, since focusing the field announces its error, and
380
+ * `aria-live="polite"` otherwise. With `scrollOnError` and at least one
381
+ * problem, the topmost `:invalid` control is focused; a `<fieldset>`
382
+ * matching `:invalid` for the controls it holds is not a candidate.
383
+ *
384
+ * A banner or `ful-field-error` belongs to the nearest enclosing form, so
385
+ * those of a nested form are left alone.
386
+ * @param {HTMLFormElement} form
387
+ * @param {{ type: string; context?: string | null; reason: string; }[]} es the problems, as an httpc `Failure` carries them
388
+ * @param {boolean} scrollOnError
389
+ */
390
+ static errors(form: HTMLFormElement, es: {
391
+ type: string;
392
+ context?: string | null;
393
+ reason: string;
394
+ }[], scrollOnError: boolean): void;
192
395
  }
193
396
  /**
194
397
  * The base of every form-associated ful field: a form-associated custom element
@@ -197,181 +400,344 @@ declare class Bindings {
197
400
  *
198
401
  * A subclass owns its template, its value semantics and its change events. It
199
402
  * implements `_build(conf)`, which builds its dom and returns the pieces the
200
- * base drives: the control, the error region, the label, and the optional
201
- * `claims`, `announces`, `freeze` and `also`. The base does the wiring,
202
- * the mounting and the application of the declared state. Nothing in the base
203
- * is there to be called from a subclass's build.
204
- *
205
- * The pieces are the contract: the claim setters, the validity protocol and
206
- * the aria wiring all act on them, so a field with no native control returns a
207
- * focusable piece of its own chrome as the control. The getters and `focus()`
208
- * are the only members that tolerate a not-yet-rendered element, where page
209
- * code may read a claim or ask for the focus before the upgrade; the
210
- * properties go live only after the render, as ParsedElement documents. The
211
- * base references no ful vocabulary, only what its subclasses return to it.
403
+ * base drives (see `_build`), and the `value` pair. The base wires the pieces,
404
+ * mounts the fragment and applies the declared state. Nothing in the base is
405
+ * there to be called from a subclass's build.
406
+ *
407
+ * The claim setters, the validity protocol and the aria wiring all act on the
408
+ * pieces, so a field with no native control returns a focusable piece of its
409
+ * own chrome as the control. The claims, `value`, `focus()` and
410
+ * `setCustomValidity` are the members that tolerate a not yet rendered
411
+ * element: before the render the `value` getter answers what the rendered
412
+ * field would answer from its current declared state, a value written then is
413
+ * held and applied by the render (see `_holdValue`), and a problem reported
414
+ * then is shown by it. The other members need the pieces and so work only
415
+ * after the render.
416
+ *
417
+ * The inner controls carry `form=""`, so the host is the only element the form
418
+ * sees. Enter pressed in an inner `<input>` of any type but `file`, `button`,
419
+ * `submit`, `reset` and `image` submits the field's form through
420
+ * `_requestSubmit()`, as Enter in a native input would. It does not when a
421
+ * listener inside the field already called `preventDefault()` on the keydown,
422
+ * while an input method is composing, or when the input is still associated
423
+ * with the form itself and so submits on its own.
212
424
  */
213
425
  declare class Field extends ParsedElement {
214
426
  #private;
215
427
  static formAssociated: boolean;
216
428
  /**
217
- * The claim attributes and the value are observed here so every field,
218
- * including the custom ones, keeps them live after the upgrade: the
219
- * attribute is a third way to author a claim, beside the markup and the
220
- * property,
221
- * exactly as a native input's. The value defaults to the string mapper and
222
- * every field with its own vocabulary overrides it (`value:bool`,
223
- * `value:csv`, `value:json`).
429
+ * The claim attributes and the value, observed so that every field,
430
+ * including a custom one that never lists them, keeps them live after the
431
+ * upgrade. A subclass's own `static observed` adds to this list. The value
432
+ * is read with the string mapper; a field with another vocabulary redeclares
433
+ * it with its own (`value:bool`, `value:csv`, `value:json`).
434
+ * @type {string[]}
224
435
  */
225
436
  static observed: string[];
226
- /** the role the element internals carry, 'presentation' unless the control is its own */
437
+ /**
438
+ * The role set on the element internals at construction. A field whose host
439
+ * is the widget itself overrides it, as `RadioGroup` does with `'radiogroup'`.
440
+ * @type {string}
441
+ */
227
442
  static ROLE: string;
443
+ /** Sets the internals' role from the subclass's `static ROLE`. */
228
444
  constructor();
229
- focus(options: any): void;
230
- /**
231
- * Clears or reports one validation problem: the text lands on the field's
232
- * live region and the state on the element internals, driving `:invalid`
233
- * styling. Validation is the server's: the submit travels regardless, and
234
- * the problems come back pinned here. The error mapping pins on the most
235
- * specific field name a problem's context reaches, handing over the
236
- * remaining path ('' on an exact match): the base ignores it, a composite
237
- * field owning a whole subtree overrides to route the problem to the inner
238
- * control it names.
239
- * @param {string} [error]
240
- * @param {string} [context] the path below this field's name, '' when exact
445
+ /**
446
+ * Adds an element to the accessible description of the field and answers
447
+ * whether the field took it.
448
+ *
449
+ * The field takes a description before its own render as readily as after:
450
+ * one handed over early is written as soon as the field has rendered, so the
451
+ * caller does not need to know which of the two upgraded first. The element
452
+ * is given an id (`ful-described-*`) when it has none, is added once however
453
+ * often it is offered, and is referenced from the `aria-describedby`
454
+ * attribute of the `described` piece (the control by default). The
455
+ * descriptions come in the order they were offered, the error region always
456
+ * last.
457
+ *
458
+ * Pass the element that carries the words rather than a wrapper around it:
459
+ * a hidden element is part of a description only where it is referenced
460
+ * directly, so a popover that is closed until opened must be passed itself.
461
+ *
462
+ * This is the field's half of the description protocol; `describable` in
463
+ * `ful/descriptions.mjs` is the half the content uses to find the field.
464
+ * @param {HTMLElement | null | undefined} el
465
+ * @returns {boolean} false only when `el` is missing
466
+ */
467
+ describedBy(el: HTMLElement | null | undefined): boolean;
468
+ /**
469
+ * Moves the focus to the `control` piece. Asked before the render, the
470
+ * request is kept (the last one wins) and applied once the control is
471
+ * mounted.
472
+ * @param {FocusOptions} [options]
473
+ */
474
+ focus(options?: FocusOptions): void;
475
+ /**
476
+ * Clears or reports one validation problem. A message sets a custom error on
477
+ * the element internals, which makes the host match `:invalid`, renders the
478
+ * message into the `error` piece and sets `aria-invalid="true"` on the
479
+ * `announces` piece, or on the `control` when the field announces nowhere.
480
+ * An empty or missing message clears all three.
481
+ *
482
+ * Validation is the server's: the submit is sent regardless, and the form's
483
+ * error mapping calls this with the problems that come back. It calls the
484
+ * field with the most specific name a problem's context reaches and passes
485
+ * the rest of the path as `context`. The base ignores `context`; a composite
486
+ * field owning a whole subtree overrides this to route the message to the
487
+ * inner control the path names.
488
+ *
489
+ * Before the render there is no error region to write to: a message is
490
+ * kept (the last one wins) and shown once the field has rendered, and a
491
+ * clear drops the one kept. A field whose render failed shows nothing.
492
+ * @param {string} [error] the message, empty or missing to clear
493
+ * @param {string} [context] the path below this field's name, '' on an exact match
241
494
  */
242
495
  setCustomValidity(error?: string, context?: string): void;
243
- /** Submits the associated form through its first submitter, as Enter on a native control would. */
496
+ /**
497
+ * The form the platform associated the field with, as a native control's
498
+ * own `form`.
499
+ * @returns {HTMLFormElement | null} null outside a form
500
+ */
501
+ get form(): HTMLFormElement | null;
502
+ /**
503
+ * Submits the associated form with `requestSubmit`, so constraint validation
504
+ * and the `submit` event run as for a user's submit. The submitter is the
505
+ * first enabled `button` or `input` of type `submit` in the form whose own
506
+ * `form` is that form; with none the form is submitted without one. Does
507
+ * nothing outside a form.
508
+ */
244
509
  _requestSubmit(): void;
245
510
  /**
246
- * Dispatches the field's change event: bubbling, not cancelable, the value
247
- * in the detail. Every field announces through this one method, and the detail
248
- * always carries the field's own `value`, so a listener can rely on
249
- * `el.value === evt.detail.value` whatever the field is. A field with more to
250
- * say adds keys beside it; none can replace it.
251
- * @param {Record<string, any>} [extras]
511
+ * Dispatches the field's `change` event on the host: bubbling, not
512
+ * cancelable, with `{ value: this.value, ...extras }` as the detail. Call it
513
+ * for a change the user made. Every field in the library announces through this method and
514
+ * passes no `value` key in `extras`, so a listener can rely on
515
+ * `el.value === evt.detail.value`. The extras are spread after `value`, so a
516
+ * `value` key among them would replace it.
517
+ * @param {Record<string, any>} [extras] further detail keys beside `value`
252
518
  */
253
519
  _notifyChange(extras?: Record<string, any>): void;
254
520
  /**
255
- * Whether the field's chrome should answer a gesture. Badges, dropzones,
256
- * menus and labels are not form controls, so their handlers must ask the
257
- * effective state: matches(':disabled') covers the fieldset ancestry the
258
- * disabled property deliberately does not reflect, readonly the field's
259
- * own claim.
521
+ * Whether the field's own chrome should act on a gesture. Badges, dropzones,
522
+ * menus and labels are not form controls, so the platform does not disable
523
+ * them: every handler on them asks this first. False while the host matches
524
+ * `:disabled`, which covers a disabled `<fieldset>` ancestry as well as the
525
+ * claim, or while the readonly claim holds.
526
+ * @returns {boolean}
260
527
  */
261
528
  _interactive(): boolean;
262
529
  /**
263
- * The field's value: every concrete field owns its semantics and overrides
264
- * this pair. The base pair exists so the form integration (the reset
265
- * protocol among others) has a member to write through; a custom field
266
- * forgetting its own keeps the base's inert one.
530
+ * Upgrades as `ParsedElement.upgrade` does, then writes the value held by
531
+ * `_holdValue`, if any, through the `value` setter: the value written
532
+ * before the render wins over the declared one.
533
+ * @returns {Promise<void>} rejecting with what the render threw
534
+ */
535
+ upgrade(): Promise<void>;
536
+ /**
537
+ * Keeps a value written before the field has its control, for a subclass's
538
+ * `value` setter to call in place of writing it: the upgrade writes it
539
+ * through the setter once the field has rendered, over the declared one.
540
+ * The `value` attribute is left alone, so a reset still restores it.
541
+ * @param {any} value
542
+ */
543
+ _holdValue(value: any): void;
544
+ /**
545
+ * The value a subclass's `value` getter shapes before the field has its
546
+ * control: the one `_holdValue` kept, else the declared `value` attribute
547
+ * read through its mapper.
548
+ * @returns {any}
549
+ */
550
+ _heldValue(): any;
551
+ /**
552
+ * The field's value, `null` when empty. Every concrete field overrides the
553
+ * pair with its own semantics; the form extraction, `values =` assignment,
554
+ * the `change` detail and `formResetCallback` all go through it. The base
555
+ * pair reads `undefined` and ignores writes. A concrete field answers and
556
+ * takes its value before the render too, through `_heldValue` and
557
+ * `_holdValue`.
267
558
  * @type {any}
268
559
  */
269
560
  get value(): any;
270
561
  set value(v: any);
271
562
  /**
272
- * A reset restores the field's declared value, as a native control's reset
273
- * restores its markup default: the `value` attribute goes back through the
274
- * element's own mapper and value setter, so every field resets through its
275
- * own semantics. A field whose value is not attribute backed overrides this.
563
+ * Restores the declared value on a form reset, as a native control restores
564
+ * its markup default: the current `value` attribute is decoded with the
565
+ * element's own mapper and written through the `value` setter. A field whose
566
+ * value is not attribute backed overrides this.
276
567
  */
277
568
  formResetCallback(): void;
278
569
  /**
279
- * The disabled protocol follows the semantics of a native form control:
570
+ * The field's own disabled claim, which follows a native form control:
280
571
  *
281
- * - the `disabled` attribute on the host is the field's own claim, and nothing
282
- * but its author ever writes or removes it, in markup or through the
283
- * property. The framework never claims on the form's behalf, so there is
284
- * nothing to unclaim and nothing to lose: a field declared disabled inside
285
- * a disabled `<fieldset>` stays disabled when the fieldset comes back,
286
- * exactly like a native input keeps its attribute.
287
- * - the effective state is the claim OR a disabled fieldset ancestry, which
288
- * the platform maintains on its own: `:disabled` matches both, a disabled
289
- * field is left out of the submitted values, and the inner native controls
290
- * are reached by the ancestry as descendants of the fieldset.
291
- * - the property reflects the claim only, like a native input's: a field
292
- * disabled by its ancestry reads `false` while `matches(':disabled')`
293
- * tells the effective state. Un-claiming inside a disabled fieldset
294
- * cannot enable the field.
295
- * - the inner controls mirror the claim and nothing else: the ancestry state
296
- * is never written anywhere, so it can never go stale, and the browser
297
- * composes the two on its own when it disables and re-enables a fieldset's
298
- * descendants. Subclass setters call super for the claim, then reach their
299
- * own controls, which mirror the claim like a native input's would.
300
- *
301
- * Because of this, formDisabledCallback carries nothing the framework needs
302
- * to apply, and the protocol does not define it.
572
+ * - the `disabled` attribute on the host is the claim. Only the author writes
573
+ * or removes it, in markup or through this property; the library never
574
+ * claims on a form's or fieldset's behalf. A field declared disabled inside
575
+ * a disabled `<fieldset>` stays disabled when the fieldset is re-enabled.
576
+ * - the effective state is the claim or a disabled `<fieldset>` ancestry:
577
+ * `:disabled` matches both, and a disabled field is left out of the
578
+ * submitted values.
579
+ * - the property reads the claim only: a field disabled by its ancestry reads
580
+ * `false` while `matches(':disabled')` is true. Removing the claim inside a
581
+ * disabled fieldset does not enable the field.
582
+ * - writing it sets or removes the `disabled` attribute on the `claims`
583
+ * piece (the control by default) and on every `also` control. The
584
+ * ancestry is never written anywhere: the browser disables those controls
585
+ * as descendants of the fieldset. A subclass setter calls super, then
586
+ * reaches any further controls of its own.
587
+ * @type {boolean}
303
588
  */
304
589
  get disabled(): boolean;
305
590
  set disabled(d: boolean);
306
591
  /**
307
- * A field is readonly through its control's native readOnly when it has one:
308
- * the control stays focusable and its text selectable, only editing is off.
309
- * Fields whose chrome must freeze too (popovers, buttons, label clicks) name
310
- * a `freeze` piece instead, whose gestures the base refuses while the claim
311
- * holds; the claim reflects on the host either way.
592
+ * The readonly claim, read from the host attribute. Writing it sets
593
+ * `readOnly` on the `claims` piece (the control by default) and on every
594
+ * `also` control, sets `aria-readonly` on the `announces` piece, and
595
+ * reflects the attribute. A text control stays focusable and selectable
596
+ * with only editing off. A field whose control has no usable native
597
+ * `readOnly` names a `freeze` piece, whose clicks are cancelled while the
598
+ * claim holds.
599
+ * @type {boolean}
312
600
  */
313
601
  get readonly(): boolean;
314
602
  set readonly(v: boolean);
315
603
  /**
316
- * A field is required through aria: the claim reflects on the host, the
317
- * announcement lives on the adopted control.
604
+ * The required claim, read from the host attribute. Writing it sets
605
+ * `aria-required` on the `announces` piece and reflects the attribute; no
606
+ * native `required` is set, since validation is the server's.
607
+ * @type {boolean}
318
608
  */
319
609
  get required(): boolean;
320
610
  set required(d: boolean);
321
611
  /**
322
- * The field's render is the base's: the subclass builds its dom in `_build`
323
- * and hands back what it built, the base wiring the pieces, mounting the
324
- * fragment and applying the declared state. Nothing in the base is there to
325
- * be called from a subclass's build. `_build` may be async (a select
326
- * awaiting its prefetch); a field that builds synchronously stays so.
612
+ * Calls `_build(conf)` and wires the pieces it answers. When `_build`
613
+ * returns a promise the pieces are wired once it settles; a field that
614
+ * builds synchronously renders synchronously. A default slot holding
615
+ * nothing but whitespace is emptied first, so the label it becomes is
616
+ * `:empty` and takes no row.
617
+ * @param {{ slots: Record<string, DocumentFragment> | undefined }} conf
618
+ * @returns {void | Promise<void>}
327
619
  */
328
- render(conf: any): Promise<void> | undefined;
620
+ render(conf: {
621
+ slots: Record<string, DocumentFragment> | undefined;
622
+ }): void | Promise<void>;
329
623
  /**
330
- * Builds the field's dom and answers the pieces the base drives. The one
331
- * method a concrete field implements beside its value pair, and the only
332
- * place its dom is created; the base does the wiring and the mounting.
624
+ * Builds the field's dom and answers the pieces the base drives, or a
625
+ * promise of them. The one method a concrete field implements beside its
626
+ * value pair, and the only place its dom is created. A subclass extending
627
+ * another field's build spreads the pieces it answered and overrides the
628
+ * keys it owns.
333
629
  *
334
- * - `fragment` is mounted on the host
335
- * - `control` is the focusable target: focus, the aria and, by default, all
336
- * three claims reach it
337
- * - `error` is the field's live region
338
- * - `label`, when given, names the control and focuses it on click
339
- * - `described` moves the error's description off the control and onto
340
- * another element, the host where no single control can carry it
341
- * - `claims` moves the three claims onto a wrapper the field disables as a
342
- * whole, leaving focus and aria on the control
343
- * - `announces` is the element whose role carries `aria-readonly` and
344
- * `aria-required`, the host where the widget role lives there; `null` for a
345
- * field whose control has no role that accepts them
346
- * - `freeze` is for a field with no usable native readOnly: the readonly
347
- * claim refuses the gestures inside it, leaving it focusable and readable
348
- * - `also` are further controls mirroring disabled and readOnly beside the
349
- * first
630
+ * - `fragment` (required) replaces the host's children.
631
+ * - `control` (required) is the focus target of `focus()`. By default it also
632
+ * carries the description, `aria-invalid`, `aria-readonly`,
633
+ * `aria-required`, and the mirrored `disabled` and `readOnly`.
634
+ * - `error` (required) is the live region `setCustomValidity` writes into.
635
+ * It is given an id (`ful-field-error-*`) when it has none and is always
636
+ * the last entry of the description.
637
+ * - `label` names the control. For a labelable control (`button`, `input`
638
+ * other than hidden, `meter`, `output`, `progress`, `select`, `textarea`)
639
+ * the control is given an id when it has none and the label's `for`
640
+ * points at it, so a click on the label does what it does in a plain
641
+ * form. For any other control the label is given an id, the control's
642
+ * `aria-labelledby` points at it, and a click on the label calls `focus()`.
643
+ * - `described` carries `aria-describedby` in place of the control: the
644
+ * host, for a field with no single control to describe.
645
+ * - `claims` receives the mirrored `disabled` and `readOnly` in place of the
646
+ * control, for a wrapper such as a `<fieldset>` the field disables as a
647
+ * whole. Focus and the aria stay on the control.
648
+ * - `announces` carries `aria-readonly`, `aria-required` and `aria-invalid`
649
+ * in place of the control: the host, where the widget role lives there.
650
+ * `null` for a field whose control has no role that accepts
651
+ * `aria-readonly` and `aria-required`; `aria-invalid` then goes on the
652
+ * control.
653
+ * - `freeze` is for a field with no usable native `readOnly`: a capturing
654
+ * click listener on it cancels the default action of every click inside
655
+ * it while the readonly claim holds. It stays focusable and in the
656
+ * accessibility tree.
657
+ * - `also` are further controls that mirror `disabled` and `readOnly`
658
+ * beside the claim target.
350
659
  *
351
- * A subclass extending another field's build spreads the pieces it answered
352
- * and overrides the keys it owns.
353
- * @param {{slots: any}} conf
354
- * @returns {any}
660
+ * The pieces are elements the field rendered itself, native controls or
661
+ * wrappers of its own. A ful field is not one: it has no `readOnly`, its
662
+ * host's role takes no aria, and a label's `for` names its host rather than
663
+ * its control. A composite built from ful fields answers the inner field as
664
+ * `control`, which forwards `focus()`, lets it render its own label with
665
+ * `label` and `announces` null, and forwards the claims in its setters,
666
+ * calling super first.
667
+ * @param {{ slots: Record<string, DocumentFragment> | undefined }} conf
668
+ * @returns {any} the pieces, or a promise of them
669
+ * @throws {Error} when the subclass does not implement it
355
670
  */
356
671
  _build(conf: {
357
- slots: any;
672
+ slots: Record<string, DocumentFragment> | undefined;
358
673
  }): any;
359
674
  }
360
- /** Submits a form's values as json to a url, mapping the request and the response through the configured mappers. */
675
+ /** Sends a form's request as a json body to a url, mapping the request and the response through the configured mappers. */
361
676
  declare class RemoteJsonFormLoader {
362
677
  #private;
363
- constructor(http: any, url: any, method: any, requestMapper: any, responseMapper: any);
364
- prepare(values: any, form: any): any;
365
- submit(request: any, form: any): Promise<any>;
366
- transform(response: any, form: any): any;
678
+ /**
679
+ * @param {{ request(method: string, url: string): { json(body: any): { fetch(): Promise<any> } } }} http the `http-client` component
680
+ * @param {string} url
681
+ * @param {string} method
682
+ * @param {(value: any, form: Form) => any} requestMapper
683
+ * @param {(value: any, form: Form) => any} responseMapper
684
+ */
685
+ constructor(http: {
686
+ request(method: string, url: string): {
687
+ json(body: any): {
688
+ fetch(): Promise<any>;
689
+ };
690
+ };
691
+ }, url: string, method: string, requestMapper: (value: any, form: Form) => any, responseMapper: (value: any, form: Form) => any);
692
+ /**
693
+ * @param {any} values
694
+ * @param {Form} form
695
+ * @returns {any} what the request mapper returns
696
+ */
697
+ prepare(values: any, form: Form): any;
698
+ /**
699
+ * @param {any} request
700
+ * @param {Form} form
701
+ * @returns {Promise<any>} what the client's `fetch()` resolves to: with the
702
+ * library's client, the `Response` of a 2xx status, rejecting with an
703
+ * `HttpClientError` (a `Failure`) otherwise
704
+ */
705
+ submit(request: any, form: Form): Promise<any>;
706
+ /**
707
+ * @param {any} response
708
+ * @param {Form} form
709
+ * @returns {any} what the response mapper returns
710
+ */
711
+ transform(response: any, form: Form): any;
367
712
  }
368
713
  /** Submits a form without a request: the request mapper produces the result the response mapper then reads, for a form handled entirely on the page. */
369
714
  declare class LocalFormLoader {
370
715
  #private;
371
- constructor(requestMapper: any, responseMapper: any);
372
- prepare(values: any, form: any): Promise<any>;
373
- submit(request: any, form: any, response: any): Promise<any>;
374
- transform(response: any, form: any): Promise<any>;
716
+ /**
717
+ * @param {(value: any, form: Form) => any} requestMapper
718
+ * @param {(value: any, form: Form) => any} responseMapper
719
+ */
720
+ constructor(requestMapper: (value: any, form: Form) => any, responseMapper: (value: any, form: Form) => any);
721
+ /**
722
+ * @param {any} values
723
+ * @param {Form} form
724
+ * @returns {Promise<any>} what the request mapper returns or resolves to
725
+ */
726
+ prepare(values: any, form: Form): Promise<any>;
727
+ /**
728
+ * Sends nothing.
729
+ * @param {any} request
730
+ * @param {Form} form
731
+ * @param {any} response what a `submit:requested` listener answered
732
+ * @returns {Promise<any>} `response`, unchanged
733
+ */
734
+ submit(request: any, form: Form, response: any): Promise<any>;
735
+ /**
736
+ * @param {any} response
737
+ * @param {Form} form
738
+ * @returns {Promise<any>} what the response mapper returns or resolves to
739
+ */
740
+ transform(response: any, form: Form): Promise<any>;
375
741
  }
376
742
  /**
377
743
  * Builds the form's loader from its attributes: a local one when no action is
@@ -384,121 +750,465 @@ declare class LocalFormLoader {
384
750
  * - `submit(request, form, response)` performs it and returns the response. The
385
751
  * third argument is whatever a `submit:requested` listener already answered,
386
752
  * which is how a loader with nothing to send returns it unchanged
387
- * - `transform(response, form)` turns that response into the detail of the
388
- * `submit:success` event
753
+ * - `transform(response, form)` turns that response into the `response` of the
754
+ * `submit:success` detail
389
755
  *
390
756
  * A rejection from any of the three is reported as a `submit:failure`.
391
757
  */
392
758
  declare class FormLoader {
393
- static create(el: any, conf: any): LocalFormLoader | RemoteJsonFormLoader;
759
+ /**
760
+ * The `request-mapper` and `response-mapper` attributes name registry
761
+ * components called as `(value, form)`; each defaults to the identity. A
762
+ * remote loader sends through the `http-client` component, with the
763
+ * `method` attribute or `POST`.
764
+ * @param {Form} el the form submitting
765
+ * @param {unknown} [conf] not read
766
+ * @returns {LocalFormLoader | RemoteJsonFormLoader}
767
+ */
768
+ static create(el: Form, conf?: unknown): LocalFormLoader | RemoteJsonFormLoader;
394
769
  }
395
770
  /**
396
- * Wraps its fields in a native form, extracts their values on submit and hands
397
- * them to a loader (loaders:form, or the action url as a json post),
398
- * announcing failures through the errors setter.
771
+ * Moves its children into a native `<form novalidate>`, extracts their values
772
+ * on submit and hands them to a loader, reporting problems through `errors`.
773
+ * Being `novalidate`, the form submits whatever the validity of its fields:
774
+ * the browser does not block a submit on a field still marked invalid.
775
+ *
776
+ * Attributes on the host starting with `form-` are forwarded, without the
777
+ * prefix, to the native form. `autocomplete` is copied onto the native form as
778
+ * well and stays on the host, the fields reading it from either; a
779
+ * `form-autocomplete` has the last word on the native form.
780
+ * `clear-invalid-on-change` clears the custom validity of a field of this form
781
+ * when a `change` event bubbles from it; the field's form is read from its
782
+ * `form` property, or from `internals.form` for a form-associated element
783
+ * that has none. `scroll-on-error` is passed to `Bindings.errors` as
784
+ * `scrollOnError`.
785
+ *
786
+ * A native submit, by a button or by Enter in a field, is stopped and runs
787
+ * `submit` instead.
788
+ *
789
+ * `track-changes` keeps track of the edits not saved yet (see `dirty`), and
790
+ * `guard-leave`, which implies it, asks before the page is left while there
791
+ * are any.
399
792
  */
400
793
  declare class Form extends ParsedElement {
401
794
  #private;
795
+ /** Read once at the upgrade: changing one afterwards has no effect. */
402
796
  static attributes: string[];
797
+ /** The native form holding the fields, created by the render. */
403
798
  form: any;
799
+ /** Builds the native form and moves the element's children into it. */
404
800
  render(): void;
801
+ connectedCallback(): void;
802
+ disconnectedCallback(): void;
803
+ /**
804
+ * Runs the submit pipeline, with the spinner up until its outcome:
805
+ *
806
+ * 1. builds the loader: the component the `loader` attribute names
807
+ * (`loaders:form` by default), called as `create(form)`
808
+ * 2. extracts the values with `Bindings.extractFrom` and has the loader
809
+ * `prepare` them into the request
810
+ * 3. fires a cancelable `submit` with `{ submitter, values, request }`.
811
+ * Cancelling it ends the submit there, reporting nothing and leaving the
812
+ * problems shown; a listener may replace `values` or `request` on the detail
813
+ * 4. clears the problems shown
814
+ * 5. fires `submit:requested` through `AsyncEvents.fireAsync` in `pipeline`
815
+ * mode: at most one async listener, whose answer is handed to the
816
+ * loader's `submit` as its third argument. A listener may replace
817
+ * `request` on the detail
818
+ * 6. has the loader `submit` the request and `transform` the response
819
+ * 7. under `track-changes`, commits the values extracted at step 2 (see
820
+ * `commit`): an edit made while the submit was in flight stays unsaved
821
+ * 8. releases the spinner and fires `submit:success` with `{ submitter,
822
+ * values, request, response }`, `response` being the transformed one
823
+ *
824
+ * Anything thrown from step 1 on, a missing loader or a throwing mapper
825
+ * included, fires `submit:failure` with `{ submitter, values, request,
826
+ * exception }` instead; the returned promise does not reject. A `Failure`
827
+ * has its problems shown through `errors`, anything else is logged with
828
+ * `console.warn`. The `values` of both outcome events are the extracted
829
+ * ones, not a replacement a `submit` listener put on its detail.
830
+ *
831
+ * The form settles before either outcome event (0016): its listeners see
832
+ * the spinner released and the buttons no longer held off, the problems of
833
+ * a `Failure` already shown, and the submit no longer in flight, so one
834
+ * of them may submit again.
835
+ *
836
+ * A call while a submit is in flight returns at once, doing nothing.
837
+ * @param {HTMLElement} [submitter] the button that submitted, if any; it is
838
+ * the only button whose name and value are submitted
839
+ * @returns {Promise<boolean>} settling once the submit has finished: true
840
+ * after `submit:success`, false when it failed, was cancelled or was dropped
841
+ * for one in flight
842
+ */
843
+ submit(submitter?: HTMLElement): Promise<boolean>;
405
844
  /**
406
- * Submits once: a submit while one is in flight is dropped before the
407
- * values are even extracted, so nothing fires and nothing travels; the
408
- * settled exchange re-arms the form. A write must not double behind a
409
- * second Enter or a programmatic call racing the first.
410
- * @param {HTMLElement} [submitter]
411
- * @returns
845
+ * Saves the unsaved edits, for a page action that would lose them, such as
846
+ * one reloading the page: under `track-changes` it submits only while the
847
+ * form is dirty, and without it always submits.
848
+ * @param {HTMLElement} [submitter] as `submit` takes it
849
+ * @returns {Promise<boolean>} whether no unsaved edit is left: false when
850
+ * the submit did not succeed, or when the user edited the form while it
851
+ * was in flight
852
+ */
853
+ save(submitter?: HTMLElement): Promise<boolean>;
854
+ /**
855
+ * Resets the native form, each field returning to its default value. Under
856
+ * `track-changes` a reset, this one or a native reset button's, counts
857
+ * every control as edited: the form is dirty if the defaults differ from
858
+ * what was last committed.
412
859
  */
413
- submit(submitter?: HTMLElement): Promise<void>;
414
- /** The native reset, routing every field through its own value semantics. */
415
860
  reset(): void;
416
- /** Shows the spinners and holds the submit buttons off, overlapping spins sharing one claim. */
417
- spinner(spin: any): void;
418
- /** The values of the fields the form contains, extracted and filled back through Bindings. */
419
- set values(vs: {});
420
- get values(): {};
421
- /** Pins problems to the fields they name, the banner taking the nameless ones. */
422
- set errors(es: any);
861
+ /**
862
+ * Whether, under `track-changes`, a control the user edited since the last
863
+ * commit holds a value other than the one committed. An edit is what the
864
+ * controls report as one: an `input` or a `change` bubbling from a control
865
+ * of this form (0019), so assigning `value`, a select's vocabulary dropping
866
+ * a key or a section delivering its fields is not one, and an edit undone
867
+ * is no longer dirty. A control the last commit did not see compares with
868
+ * its value when it was first focused. Always false without
869
+ * `track-changes`.
870
+ *
871
+ * While dirty, the host matches `:state(dirty)` and the submit buttons of
872
+ * this form carry `data-ful-dirty`; a change of the state dispatches
873
+ * `dirty:change` with `{ dirty }`. Both are refreshed at the next frame
874
+ * after an edit, while the property is exact at any time.
875
+ * @returns {boolean}
876
+ */
877
+ get dirty(): boolean;
878
+ /**
879
+ * Records the values of the controls as saved, so that the form is no
880
+ * longer dirty. A submit commits by itself on success, and the form
881
+ * commits once its fields have rendered: a page calls this for a save of
882
+ * its own, or after writing the values it loaded. Does nothing without
883
+ * `track-changes`.
884
+ */
885
+ commit(): void;
886
+ /**
887
+ * Counts controls as edited by the user, for values the page writes on
888
+ * the user's behalf: assigning `value` reports nothing, as 0019 wants.
889
+ * @param {...Element} els each a control, or an element inside one
890
+ */
891
+ edited(...els: Element[]): void;
892
+ /**
893
+ * Marks the form busy or releases it. Calls nest: only the first
894
+ * `spinner(true)` and the matching last `spinner(false)` act, so a caller's
895
+ * own spin may wrap a submit.
896
+ *
897
+ * Busy, the host carries `aria-busy="true"` and each `ful-spinner` of this
898
+ * form is revealed; one without text of its own is given `role="status"`
899
+ * (unless it has a role) and a visually hidden localized `spinner.loading`
900
+ * label. Each submit and reset button of this form is held off with
901
+ * `aria-disabled="true"`, keeping its focus, and a click on it is refused
902
+ * ahead of any listener on the button. Holding the buttons off does not
903
+ * stop a programmatic `submit`.
904
+ *
905
+ * Released, the spinners are hidden and lose the label they were given, and
906
+ * each held button gets back the `aria-disabled` it had. A button that
907
+ * joined the form while it was busy keeps its own state.
908
+ * @param {boolean} spin
909
+ */
910
+ spinner(spin: boolean): void;
911
+ /**
912
+ * Writes a nested object onto the fields of this form through
913
+ * `Bindings.mutateIn`: fields the object does not name keep their values.
914
+ * @param {{ [x: string]: any; }} vs
915
+ */
916
+ set values(vs: {
917
+ [x: string]: any;
918
+ });
919
+ /**
920
+ * The fields of this form read into a nested object by
921
+ * `Bindings.extractFrom`, with no submitter, so no button contributes.
922
+ * @returns {any}
923
+ */
924
+ get values(): any;
925
+ /**
926
+ * Shows problems through `Bindings.errors`, replacing the ones shown
927
+ * before: each is pinned to the field it names, and the rest go to the
928
+ * form's `ful-errors` banners. An empty array clears them.
929
+ * @param {{ type: string; context?: string | null; reason: string; }[]} es
930
+ */
931
+ set errors(es: {
932
+ type: string;
933
+ context?: string | null;
934
+ reason: string;
935
+ }[]);
423
936
  }
424
- /** A labelled text input over any native type or textarea; the temporal inputs are its subclasses. */
937
+ /**
938
+ * A labelled text input over any native input type, or a `<textarea>` under
939
+ * `type="textarea"`; the temporal inputs are its subclasses. The label is the
940
+ * default slot, `info` follows it, and `before` and `after` are rendered as
941
+ * affixes around the control. Every `input-*` attribute is forwarded onto the
942
+ * control, after everything the field sets there, so it has the last word.
943
+ *
944
+ * `type`, `v-type`, `keep`, `reject`, `uppercase`, `trim`, `unsigned` and
945
+ * `autocomplete` are configuration: read once at the upgrade, so a later write
946
+ * to them changes nothing. `placeholder` stays live.
947
+ *
948
+ * - `type="numeric"` and `type="decimal"` render a text input carrying that
949
+ * `inputmode`, so the native `min`, `max` and `step` do not apply. Typing is
950
+ * filtered to digits and a leading minus; a decimal also keeps its first `.`
951
+ * or `,` and drops any later one; `unsigned` drops the minus.
952
+ * - `keep` and `reject` are regular expressions filtering what is typed:
953
+ * `keep` leaves every match, concatenated, and `reject` removes every match.
954
+ * Either replaces the type's own filter, and `keep` wins over `reject` with a
955
+ * warning. A pattern that does not compile is warned about once and filters
956
+ * nothing. The caret stays next to the same character when text before it
957
+ * is removed.
958
+ * - `autocomplete` is put on the control. Without one the field takes the
959
+ * `autocomplete` attribute of the nearest enclosing `<form>`, which the
960
+ * platform does not pass on because the control carries `form=""`.
961
+ * - `uppercase`, `trim` and `v-type` shape the value as it is read; see `value`.
962
+ * - The control's native `change` is stopped and the field dispatches its own
963
+ * once the user commits a new value, as a text input does. A date, time,
964
+ * datetime-local, month or week control fires its native `change` while its
965
+ * segments are typed, as soon as they make a valid value; for those the field
966
+ * reports when the control is left or Enter is pressed, at once for a value
967
+ * picked from the browser's picker, never with the dates a year passes
968
+ * through while it is typed. A control left incomplete reads as `null`, as
969
+ * the native one does.
970
+ *
971
+ * A subclass template may render more than one control, as the compare filters
972
+ * render the two bounds of a range: the first is `_input`, the one the field is
973
+ * named by, and every one of them gets the inputmode, `autocomplete`, the
974
+ * `input-` passthrough, the keystroke filter, the placeholder and the `change`
975
+ * reporting. The passthrough leaves `input-id` to the first alone, so no
976
+ * two controls share an id.
977
+ */
425
978
  declare class Input extends Field {
979
+ #private;
980
+ /** @type {string[]} */
426
981
  static observed: string[];
982
+ /** @type {string[]} */
427
983
  static attributes: string[];
428
984
  static slots: boolean;
429
985
  static template: string;
986
+ /**
987
+ * The inner control, set by `_build`: the `<input>`, or the `<textarea>`
988
+ * under `type="textarea"`. A subclass reads and writes it for the
989
+ * attributes it adds. Typed `any` so that a subclass can treat it as the
990
+ * one of the two it renders.
991
+ * @type {any}
992
+ */
430
993
  _input: any;
431
- _type(): any;
994
+ /**
995
+ * The type the field renders, which a subclass overrides to wrap another
996
+ * native type. The base answers the declared `type`, else `number` under
997
+ * `v-type="number"`, else `text`. It is also what selects the keystroke
998
+ * filter of `numeric` and `decimal` and the separator handling of `value`.
999
+ * @returns {string}
1000
+ */
1001
+ _type(): string;
1002
+ /**
1003
+ * @param {{ slots: Record<string, DocumentFragment> | undefined }} conf
1004
+ * @returns {{fragment: DocumentFragment, control: any, error: Element | null, label: HTMLLabelElement | null}}
1005
+ */
432
1006
  _build({ slots }: {
433
- slots: any;
1007
+ slots: Record<string, DocumentFragment> | undefined;
434
1008
  }): {
435
- fragment: any;
1009
+ fragment: DocumentFragment;
436
1010
  control: any;
437
- error: any;
438
- label: any;
1011
+ error: Element | null;
1012
+ label: HTMLLabelElement | null;
439
1013
  };
1014
+ /**
1015
+ * The control's text, shaped in this order: upper cased under `uppercase`,
1016
+ * trimmed under `trim`, `null` when that leaves it empty, every `,` read as
1017
+ * `.` for `type="decimal"`, and decoded to a number under
1018
+ * `v-type="number"`, where text that does not decode is answered as it is.
1019
+ * Writing `null`, `undefined` or `''` empties the control; anything else is
1020
+ * written as its string, replacing whatever was being typed, as a native
1021
+ * control's value does, and is not an edit: a date or time control that
1022
+ * has the focus reports no `change` for it when it is left. Answers a
1023
+ * `string`, a `number` or `null`; typed `any` because subclasses answer
1024
+ * other values. Before the render the same shaping reads the value last
1025
+ * written, or the `value` attribute, as its text, and a write is held for
1026
+ * the render to apply.
1027
+ * @type {any}
1028
+ */
440
1029
  get value(): any;
441
1030
  set value(value: any);
442
- get placeholder(): any;
443
- set placeholder(d: any);
1031
+ /**
1032
+ * The control's placeholder, `null` when there is none. The control always
1033
+ * carries one, a single space standing in for none, so `:placeholder-shown`
1034
+ * matches an empty control and a floating label can rely on it; the blank
1035
+ * one is neither answered here nor reflected onto the host.
1036
+ * @type {string | null}
1037
+ */
1038
+ get placeholder(): string | null;
1039
+ set placeholder(d: string | null);
444
1040
  }
445
- /** Formats the yyyy-mm-dd date in its content in the page's locale, or the one its locale attribute names. */
1041
+ /**
1042
+ * Formats the yyyy-mm-dd date in its content as a numeric date, in the locale
1043
+ * its `locale` attribute names, else the page's, else the platform default.
1044
+ * Blank content, or content that does not name a date, renders the `default`
1045
+ * attribute, or nothing without one.
1046
+ */
446
1047
  declare class LocalDate extends ParsedElement {
447
1048
  static attributes: string[];
448
1049
  render(): void;
449
1050
  }
450
- /** Formats the ISO instant in its content in the page's locale and timezone. */
1051
+ /**
1052
+ * Formats the ISO instant in its content as a numeric date and 24 hour time
1053
+ * with seconds, in the page's timezone and in the locale chosen as ful-local-date
1054
+ * chooses it. Blank content, or content that does not name an instant, renders
1055
+ * the `default` attribute, or nothing without one. A date-only value is read as
1056
+ * local midnight of that day.
1057
+ */
451
1058
  declare class Instant extends ParsedElement {
452
1059
  #private;
453
1060
  static attributes: string[];
454
1061
  render(): void;
455
- static isoToLocal(iso: any): string;
456
- static localToIso(local: any): string | null;
1062
+ /**
1063
+ * Converts an ISO instant to the `yyyy-mm-ddThh:mm:ss.sss` wall clock time
1064
+ * a datetime-local input takes, in the page's timezone. A date-only value is
1065
+ * read as local midnight of the day it names, not as UTC midnight.
1066
+ * @param {string} iso
1067
+ * @returns {string} with NaN fields for a value that does not parse
1068
+ */
1069
+ static isoToLocal(iso: string): string;
1070
+ /**
1071
+ * Converts a datetime-local wall clock time in the page's timezone to a UTC
1072
+ * ISO instant. A date-only value is read as local midnight of that day.
1073
+ * @param {string} local
1074
+ * @returns {string|null} null for a value that does not parse
1075
+ */
1076
+ static localToIso(local: string): string | null;
457
1077
  }
458
- /** A date input whose bounds accept a date, now, or an offset such as +1d. */
1078
+ /**
1079
+ * A date input whose `min` and `max` accept a yyyy-mm-dd date, `now` for
1080
+ * today, or an offset from today such as `+1d`, `-2m` or `+1y` (days, months,
1081
+ * years). A month offset landing past the end of the target month takes its
1082
+ * last day. Anything else is passed to the input unchanged. The `step`
1083
+ * attribute is applied before `min` and `max`.
1084
+ */
459
1085
  declare class InputLocalDate extends Input {
460
1086
  #private;
461
1087
  static observed: string[];
462
1088
  _type(): string;
463
- get min(): any;
464
- set min(v: any);
465
- get max(): any;
466
- set max(v: any);
467
- get step(): any;
468
- set step(v: any);
1089
+ /**
1090
+ * The resolved lower bound, a yyyy-mm-dd date or whatever the input was given.
1091
+ * @returns {string|null} null when unset
1092
+ */
1093
+ get min(): string | null;
1094
+ /** @param {string|null} v a date, `now` or an offset; null or blank removes the bound */
1095
+ set min(v: string | null);
1096
+ /**
1097
+ * The resolved upper bound, a yyyy-mm-dd date or whatever the input was given.
1098
+ * @returns {string|null} null when unset
1099
+ */
1100
+ get max(): string | null;
1101
+ /** @param {string|null} v a date, `now` or an offset; null or blank removes the bound */
1102
+ set max(v: string | null);
1103
+ /**
1104
+ * The input's native step, in days on a date input and in seconds on a time input.
1105
+ * @returns {string|null} null when unset
1106
+ */
1107
+ get step(): string | null;
1108
+ /** @param {string|null} v */
1109
+ set step(v: string | null);
469
1110
  }
470
- /** A time input whose bounds accept a time, now, or an hour or minute offset, snapped to the step grid. */
1111
+ /**
1112
+ * A time input whose `min` and `max` accept a hh:mm time, `now`, or an offset
1113
+ * from now such as `+2h` or `-30m` (hours, minutes: `m` is minutes here, not
1114
+ * months), wrapping around midnight. A bound resolved from `now` or an offset
1115
+ * is floored to the `step` grid (60 seconds when unset), so that the values
1116
+ * on the grid stay valid, and carries seconds only when the step is not whole
1117
+ * minutes. A literal bound, and anything else, is passed to the input unchanged.
1118
+ */
471
1119
  declare class InputLocalTime extends InputLocalDate {
472
1120
  #private;
473
1121
  _type(): string;
474
- get min(): any;
475
- set min(v: any);
476
- get max(): any;
477
- set max(v: any);
1122
+ /**
1123
+ * The resolved lower bound, a time or whatever the input was given.
1124
+ * @returns {string|null} null when unset
1125
+ */
1126
+ get min(): string | null;
1127
+ /** @param {string|null} v a time, `now` or an offset; null or blank removes the bound */
1128
+ set min(v: string | null);
1129
+ /**
1130
+ * The resolved upper bound, a time or whatever the input was given.
1131
+ * @returns {string|null} null when unset
1132
+ */
1133
+ get max(): string | null;
1134
+ /** @param {string|null} v a time, `now` or an offset; null or blank removes the bound */
1135
+ set max(v: string | null);
478
1136
  }
479
- /** A datetime input whose value is read and written as an ISO instant. */
1137
+ /**
1138
+ * A datetime-local input whose value and bounds are read and written as UTC
1139
+ * ISO instants, shown as wall clock time in the page's timezone. A date-only
1140
+ * value is read as local midnight of that day.
1141
+ */
480
1142
  declare class InputInstant extends Input {
481
1143
  static observed: string[];
482
1144
  _type(): string;
1145
+ /**
1146
+ * The entered time as a UTC ISO instant. Before the render it answers the
1147
+ * value last written, or the `value` attribute, as the rendered field
1148
+ * would, and a write is held for the render to apply.
1149
+ * @returns {string|null} null when empty or when the input holds text that does not parse
1150
+ */
483
1151
  get value(): string | null;
484
- set value(v: string | null);
1152
+ /** @param {string|null|undefined} v an ISO instant; a falsy value empties the input */
1153
+ set value(v: string | null | undefined);
1154
+ /**
1155
+ * The lower bound as a UTC ISO instant.
1156
+ * @returns {string|null} null when unset
1157
+ */
485
1158
  get min(): string | null;
1159
+ /** @param {string|null} v an ISO instant; a falsy value removes the bound */
486
1160
  set min(v: string | null);
1161
+ /**
1162
+ * The upper bound as a UTC ISO instant.
1163
+ * @returns {string|null} null when unset
1164
+ */
487
1165
  get max(): string | null;
1166
+ /** @param {string|null} v an ISO instant; a falsy value removes the bound */
488
1167
  set max(v: string | null);
489
- get step(): any;
490
- set step(v: any);
1168
+ /**
1169
+ * The input's native step, in seconds.
1170
+ * @returns {string|null} null when unset
1171
+ */
1172
+ get step(): string | null;
1173
+ /** @param {string|null} v */
1174
+ set step(v: string | null);
491
1175
  }
492
- /** A file input with an optional dropzone and item list, enforcing the size and count limits it declares. */
1176
+ /**
1177
+ * A file input with an optional dropzone and item list, enforcing the type,
1178
+ * size and count limits it declares.
1179
+ *
1180
+ * The `dropzone` and `item-list` attributes show the dropzone and the list of
1181
+ * chosen files, and `no-input` takes the picker control out of sight, leaving
1182
+ * it to the keyboard and to assistive technology. A `dropzone` slot replaces the default dropzone's content, and
1183
+ * an `items` slot holding a template replaces the stock item, rendered with the
1184
+ * same `files` overlay so that it reads the File objects it lists. Clicking the
1185
+ * dropzone opens the picker, and the host carries a `dragover` attribute while
1186
+ * a drag hovers it. A drop selects the files it carries, ignoring entries that
1187
+ * are not files; a drop with no file, or with several on a field that is not
1188
+ * `multiple`, changes nothing.
1189
+ *
1190
+ * Every selection, picked, dropped or assigned, goes through the constraints
1191
+ * in order: `accept` and `max-file-size` drop the files they refuse,
1192
+ * `max-total-size` and `max-files` clear the whole selection. Each violation
1193
+ * shows a warning. The change event fires for a pick, a drop and a removal
1194
+ * through an item's button; the `files`, `file` and `value` setters are silent,
1195
+ * like a native input's.
1196
+ *
1197
+ * While disabled or readonly, the picker, the dropzone and the item removals
1198
+ * do nothing, and a drop still does not reach the browser's default handling.
1199
+ */
493
1200
  declare class InputFile extends Input {
494
1201
  #private;
495
- /** how long a warning stands before the field retires it, matching the css fade */
1202
+ /**
1203
+ * Milliseconds a warning stays before the field removes it, whether or not
1204
+ * its css animation ran; read each time a warning is shown.
1205
+ * @type {number}
1206
+ */
496
1207
  static WARNING_TIMEOUT: number;
497
1208
  /**
498
- * A FileList holding exactly these files. The platform gives no way to
499
- * build one but through a DataTransfer, and every place that narrows a
500
- * selection rebuilt it by hand: five loops and three empty ones.
1209
+ * Builds a FileList holding exactly these files, the form `files` takes.
501
1210
  * @param {Iterable<File>} [files]
1211
+ * @returns {FileList}
502
1212
  */
503
1213
  static list(files?: Iterable<File>): FileList;
504
1214
  static observed: string[];
@@ -509,73 +1219,198 @@ declare class InputFile extends Input {
509
1219
  warning: string;
510
1220
  };
511
1221
  _build(conf: any): {
512
- fragment: any;
1222
+ fragment: DocumentFragment;
513
1223
  control: any;
514
- error: any;
515
- label: any;
1224
+ error: Element | null;
1225
+ label: HTMLLabelElement | null;
516
1226
  freeze: any;
517
1227
  };
518
- warning(key: any, args: any): void;
519
- get accept(): any;
520
- set accept(vs: any);
521
- get multiple(): any;
522
- set multiple(v: any);
523
- get files(): any;
524
- set files(vs: any);
525
- get file(): any;
526
- set file(v: any);
527
- get value(): any;
1228
+ /**
1229
+ * Shows a warning in the field's polite live region: the localized message
1230
+ * for an l10n key and its arguments. It is removed when its animation ends,
1231
+ * or after `WARNING_TIMEOUT` milliseconds at the latest. The warnings are
1232
+ * cleared whenever the selection changes.
1233
+ * @param {string} key
1234
+ * @param {Record<string, any>} [args]
1235
+ */
1236
+ warning(key: string, args?: Record<string, any>): void;
1237
+ /**
1238
+ * The accepted types, also set on the native input for its picker. A token
1239
+ * is a dot-prefixed extension matched against the end of the file name, a
1240
+ * mime type matched against the file's type with any parameters ignored, or
1241
+ * a family such as `image/*`; case is ignored, and any other token matches
1242
+ * nothing. A file matching no token is dropped with a warning; an empty list
1243
+ * accepts every file. Enforced from the next selection on.
1244
+ * @returns {string[]}
1245
+ */
1246
+ get accept(): string[];
1247
+ /** @param {string[]} vs */
1248
+ set accept(vs: string[]);
1249
+ /**
1250
+ * Whether the field takes several files; a single-file field ignores a drop of several.
1251
+ * @returns {boolean}
1252
+ */
1253
+ get multiple(): boolean;
1254
+ /** @param {boolean} v */
1255
+ set multiple(v: boolean);
1256
+ /**
1257
+ * The selected files, after the constraints; none before the render.
1258
+ * @returns {FileList}
1259
+ */
1260
+ get files(): FileList;
1261
+ /**
1262
+ * Replaces the selection and runs the constraints over it, refreshing the
1263
+ * warnings and the item list, without firing change.
1264
+ * @param {FileList} vs
1265
+ */
1266
+ set files(vs: FileList);
1267
+ /**
1268
+ * The first selected file.
1269
+ * @returns {File|null}
1270
+ */
1271
+ get file(): File | null;
1272
+ /**
1273
+ * Replaces the selection with this one file, or empties it, as `files` does.
1274
+ * @param {File|null|undefined} v
1275
+ */
1276
+ set file(v: File | null | undefined);
1277
+ /**
1278
+ * The names of the selected files: an array when `multiple`, otherwise the
1279
+ * one name or null. Before the render no file is selected yet.
1280
+ * @returns {string[]|string|null}
1281
+ */
1282
+ get value(): string[] | string | null;
1283
+ /**
1284
+ * Empties the selection for a falsy value and ignores any other, since a
1285
+ * file name cannot select a file. Before the render there is nothing to
1286
+ * empty, so it does nothing.
1287
+ * @param {any} v
1288
+ */
528
1289
  set value(v: any);
1290
+ /** Empties the selection, as a native file input's reset does, whatever the `value` attribute says. */
529
1291
  formResetCallback(): void;
530
- get totalsize(): any;
531
- get maxFiles(): any;
532
- set maxFiles(v: any);
533
- get maxFileSize(): any;
534
- set maxFileSize(v: any);
535
- get maxTotalSize(): any;
536
- set maxTotalSize(v: any);
537
- get itemList(): any;
538
- set itemList(v: any);
539
- get dropzone(): any;
540
- set dropzone(v: any);
1292
+ /**
1293
+ * The size of the selected files together, in bytes.
1294
+ * @returns {number}
1295
+ */
1296
+ get totalSize(): number;
1297
+ /**
1298
+ * The most files a selection may hold; a larger selection is cleared
1299
+ * entirely with a warning. Enforced from the next selection on.
1300
+ * @returns {number|null} null for no limit
1301
+ */
1302
+ get maxFiles(): number | null;
1303
+ /** @param {number|null} v */
1304
+ set maxFiles(v: number | null);
1305
+ /**
1306
+ * The largest size one file may have, in bytes; a larger file is dropped
1307
+ * from the selection with a warning. Enforced from the next selection on.
1308
+ * @returns {number|null} null for no limit
1309
+ */
1310
+ get maxFileSize(): number | null;
1311
+ /** @param {number|null} v */
1312
+ set maxFileSize(v: number | null);
1313
+ /**
1314
+ * The largest size the selected files may have together, in bytes; a
1315
+ * larger selection is cleared entirely with a warning. Enforced from the
1316
+ * next selection on.
1317
+ * @returns {number|null} null for no limit
1318
+ */
1319
+ get maxTotalSize(): number | null;
1320
+ /** @param {number|null} v */
1321
+ set maxTotalSize(v: number | null);
1322
+ /**
1323
+ * Whether the chosen files are listed, each with a button removing it; the
1324
+ * stylesheet shows the list from the reflected `item-list` attribute.
1325
+ * @returns {boolean}
1326
+ */
1327
+ get itemList(): boolean;
1328
+ /** @param {boolean} v */
1329
+ set itemList(v: boolean);
1330
+ /**
1331
+ * Whether the dropzone is shown; the stylesheet shows it from the reflected
1332
+ * `dropzone` attribute.
1333
+ * @returns {boolean}
1334
+ */
1335
+ get dropzone(): boolean;
1336
+ /** @param {boolean} v */
1337
+ set dropzone(v: boolean);
1338
+ /**
1339
+ * Whether the picker control is taken out of sight, the dropzone and the
1340
+ * item list carrying the field alone; the stylesheet hides it from the
1341
+ * reflected `no-input` attribute. The control stays in the tab order and
1342
+ * keeps the label, the description and the error, and the dropzone shows
1343
+ * the focus ring while it holds the focus.
1344
+ * @returns {boolean}
1345
+ */
1346
+ get noInput(): boolean;
1347
+ /** @param {boolean} v */
1348
+ set noInput(v: boolean);
541
1349
  }
542
1350
  /**
543
- * CSS anchor positioning for a popover and the invoker it belongs to, with the
544
- * hand-placed fallback for the platforms that do not have it.
1351
+ * Places a popover beside the element or the rectangle it belongs to.
1352
+ *
1353
+ * `wire` pairs a popover with one invoker for its lifetime and relies on css
1354
+ * anchor positioning, placing the popover in script only where the platform
1355
+ * lacks it or the caller asks for it. `show` places a popover once, beside
1356
+ * whatever anchor the call hands over.
545
1357
  */
546
1358
  declare class Anchors {
547
1359
  /**
548
- * Anchors a popover to its invoker.
1360
+ * Anchors a popover to its invoker for the popover's lifetime.
549
1361
  *
550
- * The invoker is given an `anchor-name` and the popover a `position-anchor`
551
- * pointing at it, which is what a stylesheet needs to place the popover
1362
+ * On every platform the invoker is given an inline `anchor-name` and the
1363
+ * popover a `position-anchor` naming it, `--` followed by a unique id
1364
+ * built from `prefix`. That is what a stylesheet needs to place the popover
552
1365
  * itself: the library's own menus say `top: anchor(bottom); left:
553
1366
  * anchor(left)`. **Writing that css is the caller's half of this.** Without
554
1367
  * it the popover lands wherever the user agent puts a popover, which is not
555
1368
  * beside the invoker.
556
1369
  *
557
- * Where the platform has no anchor positioning the popover is placed here
558
- * instead, beside the invoker whenever it opens, clamped into the viewport,
559
- * following it on scroll and resize, and cleaned up on close. That placement
560
- * draws the geometry the css above describes, so the two agree.
1370
+ * Where the platform has no css anchor positioning, or under `handPlace`,
1371
+ * the popover is placed in script instead: beside the invoker whenever it
1372
+ * opens, clamped 8px inside the viewport, placed again on every scroll and
1373
+ * resize while open, and its inline placement removed on close. The gap to
1374
+ * the invoker is read from the popover's css margins. From `beforetoggle`
1375
+ * to the placing, in a microtask before the `toggle` event, the popover
1376
+ * carries an inline `visibility: hidden`, so a stylesheet setting
1377
+ * `visibility` on it competes with that. A popover removed from the
1378
+ * document while open, or whose invoker was removed, stops being placed.
1379
+ *
1380
+ * The script placement puts a plain popover below the invoker,
1381
+ * start-aligned. A popover matching `ful-note`, `.ful-note` or
1382
+ * `[placement]` reads its `placement` attribute instead: `top`, `right`,
1383
+ * `left`, or `bottom` by default, centred on the invoker along the other
1384
+ * axis. Such a popover is also given `--ful-note-callout-inline` and
1385
+ * `--ful-note-callout-block`: the invoker's centre, in pixels, measured
1386
+ * from the popover's padding box, which stays on the invoker when the
1387
+ * viewport pushes the popover off it.
1388
+ *
1389
+ * Wiring the same pair twice adds its listeners twice.
561
1390
  *
562
1391
  * @param {HTMLElement} invoker the element the popover belongs to
563
1392
  * @param {HTMLElement} popover the `[popover]` element to place
564
1393
  * @param {object} [options]
565
- * @param {string} [options.prefix] prefixes the generated anchor name and id,
566
- * so the dom says which component a name belongs to
567
- * @param {boolean} [options.invoke] points the invoker's `popovertarget` at
568
- * the popover, giving toggle and light dismiss with no script of your own
569
- * @param {boolean} [options.expanded] keeps the invoker's `aria-expanded` in
570
- * step with the popover
571
- * @param {boolean} [options.stretch] widens the popover to its invoker, which
572
- * is what a combobox dropdown wants
573
- * @param {boolean} [options.handPlace] places here on every platform rather
574
- * than only as a fallback, which a popover asks for when it needs to know
575
- * where its invoker ended up: the tooltip's note points a callout at it, and
576
- * a pseudo-element cannot read an anchor outside its own containing block.
577
- * Such a popover declares no anchor placement in css, there being none to
578
- * agree with
1394
+ * @param {string} [options.prefix] prefixes the generated anchor name and
1395
+ * id, `ful-anchor` by default, so the dom says which component a name
1396
+ * belongs to
1397
+ * @param {boolean} [options.invoke] makes the invoker toggle the popover,
1398
+ * which light dismiss then closes with no script of your own. The popover
1399
+ * is given the generated id when it has none. A `button` or `input`
1400
+ * invoker gets a `popovertarget` naming it; any other element toggles it
1401
+ * on click and on the Enter and Space keys; a click on it while the
1402
+ * popover is open closes the popover rather than opening it again
1403
+ * @param {boolean} [options.expanded] sets the invoker's `aria-expanded` to
1404
+ * `false` at once and keeps it in step with the popover's `toggle` events
1405
+ * @param {boolean} [options.stretch] in the script placement, sizes the
1406
+ * popover to at least its invoker's width, capped by its own `max-width`
1407
+ * and the viewport, and flips it above the invoker where the viewport
1408
+ * leaves no room below, which is what a combobox dropdown wants. The
1409
+ * `placement` attribute is not read
1410
+ * @param {boolean} [options.handPlace] places in script on every platform
1411
+ * rather than only as a fallback, for a popover that needs to know where
1412
+ * its invoker ended up, such as a note pointing a callout at it. Such a
1413
+ * popover declares no anchor placement in css
579
1414
  */
580
1415
  static wire(invoker: HTMLElement, popover: HTMLElement, { prefix, invoke, expanded, stretch, handPlace }?: {
581
1416
  prefix?: string;
@@ -584,74 +1419,187 @@ declare class Anchors {
584
1419
  stretch?: boolean;
585
1420
  handPlace?: boolean;
586
1421
  }): void;
1422
+ /**
1423
+ * Shows a popover and places it beside an anchor, once. It serves a
1424
+ * popover shared by many invokers, each open handing over its own anchor,
1425
+ * and an anchor that is a rectangle rather than an element, such as one
1426
+ * read out of an iframe. Nothing is paired and nothing follows a later
1427
+ * scroll or resize; closing is the caller's, or the platform's for a
1428
+ * light-dismissing popover.
1429
+ *
1430
+ * The popover goes below the anchor, start-aligned, clamped 8px inside the
1431
+ * viewport. `flip` moves it above the anchor where there is no room below
1432
+ * and there is room above. The placement is written as inline `top`, `left`,
1433
+ * `right`, `bottom` and `margin`, which stay after the popover closes and
1434
+ * are overwritten by the next call.
1435
+ *
1436
+ * A popover not already open is shown with `showPopover()` and placed
1437
+ * before the call returns, so the first position anybody sees is the right
1438
+ * one and the caller can move the focus into it on the next line. One
1439
+ * already open is placed without being shown again.
1440
+ *
1441
+ * @param {HTMLElement} popover the `[popover]` element to show, or an
1442
+ * already-shown element to place
1443
+ * @param {Element|DOMRect} anchor the element, or the rectangle in
1444
+ * viewport coordinates, to place beside
1445
+ * @param {object} [options]
1446
+ * @param {boolean} [options.flip] place above the anchor where no room is
1447
+ * left below, `false` by default
1448
+ * @param {number} [options.gap] the distance from the anchor, in pixels,
1449
+ * `0` by default
1450
+ * @returns {HTMLElement} the popover
1451
+ */
1452
+ static show(popover: HTMLElement, anchor: Element | DOMRect, { flip, gap }?: {
1453
+ flip?: boolean;
1454
+ gap?: number;
1455
+ }): HTMLElement;
587
1456
  }
1457
+ export type SelectEntry = {
1458
+ key: any;
1459
+ label: string;
1460
+ metadata: any;
1461
+ };
1462
+ export type SelectHttpClient = {
1463
+ request(method: string, url: string): any;
1464
+ };
588
1465
  /**
589
1466
  * Fetches a select's whole vocabulary from a url and serves every later read
590
1467
  * from it. Concurrent callers share one request, the options may be cached in
591
- * local storage under a revision, and reconfiguring the url discards both.
1468
+ * local storage under a revision, and invalidating discards both.
592
1469
  */
593
1470
  declare class RemoteLoader {
594
1471
  #private;
1472
+ /**
1473
+ * @param {{ http: SelectHttpClient, url: string, method: string, responseMapper: (response: any) => SelectEntry[], prefetch: boolean, revision: string|null }} conf
1474
+ * a null revision never reads nor writes local storage
1475
+ */
595
1476
  constructor({ http, url, method, responseMapper, prefetch, revision }: {
596
- http: any;
597
- method: any;
598
- prefetch: any;
599
- responseMapper: any;
600
- revision: any;
601
- url: any;
1477
+ http: SelectHttpClient;
1478
+ url: string;
1479
+ method: string;
1480
+ responseMapper: (response: any) => SelectEntry[];
1481
+ prefetch: boolean;
1482
+ revision: string | null;
602
1483
  });
1484
+ /**
1485
+ * Fetches the vocabulary now when the loader was built with prefetch, and
1486
+ * does nothing otherwise.
1487
+ * @returns {Promise<void>}
1488
+ * @throws when the fetch fails, or when an invalidation supersedes it
1489
+ */
603
1490
  prefetch(): Promise<void>;
604
- exact(...keys: any[]): Promise<any>;
605
- load(needle: any): Promise<any>;
606
1491
  /**
607
- * Drops the cached vocabulary so the next question refetches it. Any fetch
608
- * still in flight is detached: its outcome belongs to the configuration that
609
- * started it and must neither be served nor stored for the new one.
1492
+ * The entries whose key reads as the same text as one of the keys, in
1493
+ * vocabulary order.
1494
+ * @param {...any} keys
1495
+ * @returns {Promise<SelectEntry[]>}
1496
+ * @throws when the fetch fails, or when an invalidation supersedes it
1497
+ */
1498
+ exact(...keys: any[]): Promise<SelectEntry[]>;
1499
+ /**
1500
+ * The entries whose label contains the needle, ignoring case; every entry
1501
+ * for a nullish or empty needle.
1502
+ * @param {string|null} [needle]
1503
+ * @returns {Promise<SelectEntry[]>}
1504
+ * @throws when the fetch fails, or when an invalidation supersedes it
1505
+ */
1506
+ load(needle?: string | null): Promise<SelectEntry[]>;
1507
+ /**
1508
+ * Drops the vocabulary held in memory so the next question refetches it,
1509
+ * which may still be answered from local storage under the same revision.
1510
+ * A fetch still in flight is detached: its outcome is neither served nor
1511
+ * stored, and the callers waiting on it reject.
1512
+ * @returns {Promise<void>}
610
1513
  */
611
1514
  invalidate(): Promise<void>;
612
- reconfigureUrl(url: any): Promise<void>;
613
1515
  }
614
1516
  /** Asks the endpoint per query instead of fetching the vocabulary once, for a list too large to hold in memory. */
615
1517
  declare class PartialRemoteLoader {
616
1518
  #private;
1519
+ /**
1520
+ * @param {{ http: SelectHttpClient, url: string, method: string, responseMapper: (response: any) => SelectEntry[] }} conf
1521
+ */
617
1522
  constructor({ http, url, method, responseMapper }: {
618
- http: any;
619
- method: any;
620
- responseMapper: any;
621
- url: any;
1523
+ http: SelectHttpClient;
1524
+ url: string;
1525
+ method: string;
1526
+ responseMapper: (response: any) => SelectEntry[];
622
1527
  });
623
1528
  /**
624
- * Nothing is held between queries, so there is no cache to drop: the method
625
- * exists so a caller can invalidate any loader without knowing which it has.
1529
+ * Does nothing, since nothing is held between queries.
1530
+ * @returns {Promise<void>}
626
1531
  */
627
1532
  invalidate(): Promise<void>;
628
- reconfigureUrl(url: any): Promise<void>;
629
- exact(...keys: any[]): Promise<any>;
630
- load(needle: any): Promise<any>;
631
- }
632
- /** Serves a select's options from an array held in memory, which is what the slotted `<option>` elements become. */
633
- declare class InMemoryLoader {
1533
+ /**
1534
+ * Asks the endpoint for the entries of the keys, sent as repeated `k`
1535
+ * parameters.
1536
+ * @param {...any} keys
1537
+ * @returns {Promise<SelectEntry[]>}
1538
+ * @throws when the request fails
1539
+ */
1540
+ exact(...keys: any[]): Promise<SelectEntry[]>;
1541
+ /**
1542
+ * Asks the endpoint for the entries matching the needle, sent as the `s`
1543
+ * parameter; the filtering is the endpoint's.
1544
+ * @param {string|null} [needle]
1545
+ * @returns {Promise<SelectEntry[]>}
1546
+ * @throws when the request fails
1547
+ */
1548
+ load(needle?: string | null): Promise<SelectEntry[]>;
1549
+ }
1550
+ /** Serves a select's options from an array held in memory, which is what the slotted `<option>` elements become. */
1551
+ declare class InMemoryLoader {
634
1552
  #private;
635
- constructor(data: any);
636
- update(data: any): void;
637
- /** The vocabulary is the data itself: update replaces it, so there is nothing to drop. */
1553
+ /**
1554
+ * @param {SelectEntry[]} data
1555
+ */
1556
+ constructor(data: SelectEntry[]);
1557
+ /**
1558
+ * Replaces the vocabulary every later question is answered from.
1559
+ * @param {SelectEntry[]} data
1560
+ */
1561
+ update(data: SelectEntry[]): void;
1562
+ /**
1563
+ * Does nothing: the vocabulary is the data itself, which only update replaces.
1564
+ * @returns {Promise<void>}
1565
+ */
638
1566
  invalidate(): Promise<void>;
639
- exact(...keys: any[]): any;
640
- load(needle: any): any;
1567
+ /**
1568
+ * The entries whose key reads as the same text as one of the keys, in
1569
+ * vocabulary order.
1570
+ * @param {...any} keys
1571
+ * @returns {SelectEntry[]}
1572
+ */
1573
+ exact(...keys: any[]): SelectEntry[];
1574
+ /**
1575
+ * The entries whose label contains the needle, ignoring case; every entry
1576
+ * for a nullish or empty needle.
1577
+ * @param {string|null} [needle]
1578
+ * @returns {SelectEntry[]}
1579
+ */
1580
+ load(needle?: string | null): SelectEntry[];
641
1581
  }
642
1582
  /**
643
1583
  * Builds the select's loader from its attributes: the slotted options in
644
- * memory, or a remote or chunked loader over src.
1584
+ * memory, or a remote or chunked loader over src. It is registered as the
1585
+ * `loaders:select` component.
645
1586
  *
646
- * A component registered under the `loader` attribute replaces this one and
647
- * must implement the same three methods, each answering `{ key, label,
648
- * metadata }` entries:
1587
+ * A component named by the select's `loader` attribute replaces this one: its
1588
+ * `create(el, conf)` answers an object with these methods, each answering
1589
+ * `{ key, label, metadata }` entries, directly or through a promise:
649
1590
  *
650
- * - `prefetch()` warms the vocabulary if it can, and resolves either way
651
1591
  * - `load(needle)` answers the entries matching the typed text, all of them
652
- * when the needle is nullish, which is the empty search the list opens with
1592
+ * when the needle is empty, which is the search the list opens with
653
1593
  * - `exact(...keys)` answers the entries for those keys, used to label a value
654
- * assigned without going through the list
1594
+ * assigned without going through the list; a key it leaves out is dropped
1595
+ * from the selection
1596
+ * - `prefetch()`, optional, warms the vocabulary; the select calls it at the
1597
+ * upgrade without waiting, and awaits it in `reload()`
1598
+ * - `invalidate()`, optional, drops whatever the loader holds, called by
1599
+ * `reload()`
1600
+ *
1601
+ * An entry whose `metadata.disabled` is truthy is shown and cannot be picked,
1602
+ * with `metadata.reason`, when present, as the row's title.
655
1603
  */
656
1604
  declare class SelectLoader {
657
1605
  #private;
@@ -661,131 +1609,541 @@ declare class SelectLoader {
661
1609
  * `mode: 'chunked'`, per query. A test, or a caller holding its own
662
1610
  * configuration, builds a loader this way; `create` is the same thing with
663
1611
  * an element's attributes parsed first.
664
- * @param {{ data?: any[], http?: any, url?: string, method?: string, mode?: string, prefetch?: boolean, revision?: string|null, responseMapper?: any }} conf
1612
+ *
1613
+ * A remote loader fetches once and filters in memory, caching the response
1614
+ * in local storage under `method@url` when a revision is given; a chunked
1615
+ * one sends every search as `s` and every key lookup as `k`. The response
1616
+ * mapper turns the parsed response body into entries.
1617
+ * @param {{ data?: SelectEntry[], url?: undefined, http?: undefined, method?: undefined, mode?: undefined, prefetch?: undefined, revision?: undefined, responseMapper?: undefined }
1618
+ * | { data?: undefined, url: string, http: SelectHttpClient, method?: string, mode?: string, prefetch?: boolean, revision?: string|null, responseMapper: (response: any) => SelectEntry[] }} conf
1619
+ * `method` defaults to POST
1620
+ * @returns {InMemoryLoader|PartialRemoteLoader|RemoteLoader}
665
1621
  */
666
1622
  static from({ data, http, url, method, mode, prefetch, revision, responseMapper }: {
667
- data?: any[];
668
- http?: any;
669
- url?: string;
1623
+ data?: SelectEntry[];
1624
+ url?: undefined;
1625
+ http?: undefined;
1626
+ method?: undefined;
1627
+ mode?: undefined;
1628
+ prefetch?: undefined;
1629
+ revision?: undefined;
1630
+ responseMapper?: undefined;
1631
+ } | {
1632
+ data?: undefined;
1633
+ url: string;
1634
+ http: SelectHttpClient;
670
1635
  method?: string;
671
1636
  mode?: string;
672
1637
  prefetch?: boolean;
673
1638
  revision?: string | null;
674
- responseMapper?: any;
1639
+ responseMapper: (response: any) => SelectEntry[];
1640
+ }): InMemoryLoader | PartialRemoteLoader | RemoteLoader;
1641
+ /**
1642
+ * Builds the loader an element's attributes describe. Without `src` the
1643
+ * vocabulary is the `<option>` elements of `conf.options`, each keyed by its
1644
+ * `value` attribute or else its trimmed text. With `src` the url is
1645
+ * requested through the `http-client` component with `method` (POST by
1646
+ * default): once on first use, or at the upgrade under `preload`, or per
1647
+ * query under `mode="chunked"`. A `revision` caches the whole response in local storage; a
1648
+ * valueless one takes the `revision` component, calling it when it is a
1649
+ * function, and warns and does not cache when none is registered.
1650
+ *
1651
+ * The response becomes entries through `k-expr` and `l-expr` evaluated on
1652
+ * each row of `d-expr` (the response itself by default), with `m-expr` as
1653
+ * the metadata (the row by default); else through the `response-mapper`
1654
+ * component; else each row is read as a `[key, label, metadata]` array.
1655
+ * @param {ParsedElement} el the element declaring the loader attributes
1656
+ * @param {{ options?: DocumentFragment }} conf
1657
+ * @returns {InMemoryLoader|PartialRemoteLoader|RemoteLoader}
1658
+ */
1659
+ static create(el: ParsedElement, conf: {
1660
+ options?: DocumentFragment;
675
1661
  }): InMemoryLoader | PartialRemoteLoader | RemoteLoader;
676
- static create(el: any, conf: any): InMemoryLoader | PartialRemoteLoader | RemoteLoader;
677
1662
  }
678
- /** The options popup of a select: listbox semantics, one loading claim per show, a localized empty state. */
1663
+ /**
1664
+ * The options popup of a select, a `<ful-dropdown>` the owner shows as a
1665
+ * popover: a spinner while the loader runs, a localized empty state, and a
1666
+ * `role="listbox"` menu of `role="option"` rows.
1667
+ *
1668
+ * The owner drives it and listens to it. A pick dispatches a bubbling
1669
+ * `change` whose detail is `{ index, entry }`, the row's index as a string
1670
+ * and the entry it was rendered from. The active row is announced by a
1671
+ * non-bubbling `activechange` whose detail is `{ id }`, the row's id or null
1672
+ * when none is active, for the owner to set `aria-activedescendant`. A
1673
+ * pressed row does not take the focus from the owner's control.
1674
+ */
679
1675
  declare class Dropdown extends ParsedElement {
680
1676
  #private;
1677
+ /**
1678
+ * `listbox` is the id given to the menu, so an owner can set
1679
+ * `aria-controls` before this element upgrades; a generated id otherwise.
1680
+ */
681
1681
  static attributes: string[];
1682
+ /** The default slot, when not blank, is the row template in place of the `options` template. */
682
1683
  static slots: boolean;
683
1684
  static template: string;
1685
+ /**
1686
+ * `options` renders one row per entry, each entry carrying its `index`
1687
+ * beside its own keys. A replacement must keep one `<li>` per entry, in
1688
+ * order, with the index as its `value`: picking reads the entry back from it.
1689
+ */
684
1690
  static templates: {
685
1691
  options: string;
686
1692
  };
1693
+ /**
1694
+ * @param {{ slots: Record<string, DocumentFragment> }} conf
1695
+ */
687
1696
  render({ slots }: {
688
- slots: any;
1697
+ slots: Record<string, DocumentFragment>;
689
1698
  }): void;
1699
+ /**
1700
+ * Picks the highlighted row, or the first enabled one when none is: hides
1701
+ * the dropdown and dispatches `change`. Does nothing when the highlighted
1702
+ * row is disabled, when no row is enabled, or while a search is loading
1703
+ * and the rows are hidden.
1704
+ */
690
1705
  acceptSelection(): void;
691
- update(values: any, keys?: any[]): void;
1706
+ /**
1707
+ * Renders the entries as rows and highlights the first one whose key is
1708
+ * picked, else the row the template marked `selected`, else the first
1709
+ * enabled row. A row is marked `picked` and
1710
+ * `aria-selected="true"` when its key reads as the same text as one of the keys, and
1711
+ * `aria-disabled="true"` when its `metadata.disabled` is truthy, titled with
1712
+ * `metadata.reason`. No entries shows the empty state instead of the menu.
1713
+ * @param {SelectEntry[]} values
1714
+ * @param {any[]} [keys] the keys currently selected
1715
+ * @throws {Error} when values is undefined
1716
+ */
1717
+ update(values: SelectEntry[], keys?: any[]): void;
1718
+ /**
1719
+ * Closes the popover, if open, and announces that no row is active. A
1720
+ * `show` still waiting on its loader is superseded: its outcome is neither
1721
+ * rendered nor thrown.
1722
+ */
692
1723
  hide(): void;
1724
+ /**
1725
+ * Whether the popover is open.
1726
+ * @returns {boolean}
1727
+ */
693
1728
  get shown(): boolean;
694
- show(loader: any, keys?: any[]): Promise<void>;
695
- moveOrShow(forward: any, loader: any, keys?: any[]): Promise<void>;
696
- jump(first: any): void;
697
- page(forward: any): void;
1729
+ /**
1730
+ * Opens the popover with the spinner showing, then renders what the loader
1731
+ * answers as `update` does. A show overtaken by a later `show` or by `hide`
1732
+ * before its loader settles leaves the dropdown alone and resolves, even
1733
+ * when its loader rejected.
1734
+ * @param {() => SelectEntry[]|Promise<SelectEntry[]>} loader
1735
+ * @param {any[]} [keys] the keys currently selected
1736
+ * @returns {Promise<void>}
1737
+ * @throws what the loader threw, after hiding the dropdown, when this show
1738
+ * is still the current one
1739
+ */
1740
+ show(loader: () => SelectEntry[] | Promise<SelectEntry[]>, keys?: any[]): Promise<void>;
1741
+ /**
1742
+ * Moves the highlight to the next or previous enabled row when the dropdown
1743
+ * is shown, staying put at the edge and while a search is loading, and
1744
+ * shows it otherwise.
1745
+ * @param {boolean} forward
1746
+ * @param {() => SelectEntry[]|Promise<SelectEntry[]>} loader used only to show
1747
+ * @param {any[]} [keys] the keys currently selected, used only to show
1748
+ * @returns {Promise<void>}
1749
+ * @throws what `show` throws
1750
+ */
1751
+ moveOrShow(forward: boolean, loader: () => SelectEntry[] | Promise<SelectEntry[]>, keys?: any[]): Promise<void>;
1752
+ /**
1753
+ * Highlights the first enabled row, or the last one. Does nothing while a
1754
+ * search is loading and the rows are hidden.
1755
+ * @param {boolean} first
1756
+ */
1757
+ jump(first: boolean): void;
1758
+ /**
1759
+ * Moves the highlight by as many enabled rows as the menu shows at once,
1760
+ * one when that cannot be measured, stopping at the last enabled row.
1761
+ * Does nothing while a search is loading.
1762
+ * @param {boolean} forward
1763
+ */
1764
+ page(forward: boolean): void;
698
1765
  }
699
- /** A combobox acting like a select over a loader's vocabulary, single or multiple. */
1766
+ /**
1767
+ * A `<ful-select>`: a combobox over a loader's vocabulary of
1768
+ * `{ key, label, metadata }` entries, holding one key or, under `multiple`,
1769
+ * several. Typing filters the list, opening it from rest offers the whole
1770
+ * vocabulary, and the keyboard follows the combobox pattern: arrows move and
1771
+ * open (Alt+ArrowDown opens, Alt+ArrowUp closes), Home, End, PageUp and
1772
+ * PageDown move in the open list, Enter accepts the highlighted entry or,
1773
+ * with the list closed, submits the form, Escape reverts the edit, and Tab
1774
+ * or leaving the field commits it. Emptying the text of a single select and
1775
+ * leaving the field clears it. Backspace with the caret at the start removes
1776
+ * the last entry, and ArrowLeft there moves the focus to the badges of a
1777
+ * multiple select, where Enter, Space, Backspace or Delete remove one.
1778
+ *
1779
+ * Slots: the default one is the label, `info` sits beside it, `before` and
1780
+ * `after` are affixes around the control, `options` holds the `<option>`
1781
+ * elements of an in-memory vocabulary, `dropdown` replaces the dropdown's
1782
+ * row template and `items` the `items` template.
1783
+ *
1784
+ * Every change of the selection made through the element dispatches a
1785
+ * bubbling `change` whose detail carries `value` and `entry`, as the getters
1786
+ * answer them; assigning `value` dispatches none.
1787
+ */
700
1788
  declare class Select extends Field {
701
1789
  #private;
1790
+ /**
1791
+ * Read once at the upgrade, so a later write changes nothing: `name`,
1792
+ * `loader` (the component building the loader, `loaders:select` by
1793
+ * default), `k-type` (`number` or `boolean` coerce the keys, strings
1794
+ * otherwise), and the loader configuration `SelectLoader.create` reads,
1795
+ * but for `src` and `method`, which stay live.
1796
+ */
702
1797
  static attributes: string[];
1798
+ /**
1799
+ * Beside the field's claims: `multiple`, `item-list`, `value` as a comma
1800
+ * separated list of keys, trimmed, whether or not the select is multiple,
1801
+ * and `src` and `method`.
1802
+ */
703
1803
  static observed: string[];
1804
+ /**
1805
+ * Reads a present but empty `value` attribute of a single select as the
1806
+ * empty key, which an `<option value="">` can carry; a multiple select reads
1807
+ * it as no keys.
1808
+ * @param {string} attr
1809
+ * @param {string|null} str
1810
+ * @returns {any}
1811
+ * @throws when the class declares no such attribute
1812
+ */
1813
+ unmarshal(attr: string, str: string | null): any;
704
1814
  static slots: boolean;
705
1815
  static template: string;
1816
+ /**
1817
+ * `items` renders the selection below the control, shown under `item-list`,
1818
+ * over `entries`, the selected entries in order. A replacement must keep one
1819
+ * `<ful-item>` per entry, in order, each with a `<button>` that removes it.
1820
+ */
706
1821
  static templates: {
707
1822
  items: string;
708
1823
  };
1824
+ /**
1825
+ * @param {{ slots: Record<string, DocumentFragment> }} conf
1826
+ * @returns {{ fragment: DocumentFragment, control: HTMLInputElement, error: Element|null, label: Element|null }}
1827
+ */
709
1828
  _build({ slots }: {
710
- slots: any;
1829
+ slots: Record<string, DocumentFragment>;
711
1830
  }): {
712
- fragment: any;
713
- control: any;
714
- error: any;
715
- label: any;
1831
+ fragment: DocumentFragment;
1832
+ control: HTMLInputElement;
1833
+ error: Element | null;
1834
+ label: Element | null;
716
1835
  };
717
- /** Hands the loader to the callback, for runtime reconfigurations. */
718
- withLoader(fn: any): Promise<any>;
719
- /**
720
- * Drops whatever the loader is holding and asks it about the current selection
721
- * again, which is what a select whose vocabulary depends on another control
722
- * needs when that control changes. A key the loader no longer knows is dropped
723
- * from the selection, so a value invalidated by the change does not survive it,
724
- * and one it still knows keeps its place with a fresh label.
1836
+ /**
1837
+ * Hands the loader to the callback, for runtime changes such as
1838
+ * `update(entries)` on an in-memory loader.
1839
+ * @template T
1840
+ * @param {(loader: any) => T|Promise<T>} fn
1841
+ * @returns {Promise<T>} what the callback answers
1842
+ */
1843
+ withLoader<T>(fn: (loader: any) => T | Promise<T>): Promise<T>;
1844
+ /**
1845
+ * Drops whatever the loader is holding, awaits its prefetch (which fetches
1846
+ * again when the select declares `preload`), and asks it about the current
1847
+ * selection again, which is what a select whose vocabulary depends on
1848
+ * another control needs when that control changes. A key the loader no
1849
+ * longer knows is dropped from the selection, so a value invalidated by the
1850
+ * change does not survive it, and one it still knows keeps its place with a
1851
+ * fresh label. The badges and items follow, an open dropdown runs its
1852
+ * search again, and no `change` is dispatched.
725
1853
  *
726
- * Pass a url first where the vocabulary lives at a different address:
1854
+ * Where the vocabulary lives at a different address, write `src` or call
1855
+ * `reconfigure`, which build the loader for the new url and reload.
1856
+ * @returns {Promise<void>}
1857
+ * @throws what the loader's invalidate, prefetch, key lookup or search
1858
+ * throws
1859
+ */
1860
+ reload(): Promise<void>;
1861
+ /**
1862
+ * The url the loader asks for the vocabulary, reflected as the `src`
1863
+ * attribute.
1864
+ * @returns {string|null}
1865
+ */
1866
+ get src(): string | null;
1867
+ /**
1868
+ * Points the select at another vocabulary: the dropdown closes, the loader
1869
+ * is built again through its component's `create`, as at the render, and
1870
+ * the select reloads as `reload()` does, so a selected key the new
1871
+ * vocabulary does not know is dropped and one it knows is labelled again,
1872
+ * with no `change` dispatched. A write that does not change the url does
1873
+ * nothing.
1874
+ *
1875
+ * The reload is not awaited: a failed fetch or lookup surfaces as an
1876
+ * unhandled rejection.
1877
+ * @param {string|null|undefined} value null or undefined removes the url,
1878
+ * which the default loader answers with the slotted options
1879
+ */
1880
+ set src(value: string | null | undefined);
1881
+ /**
1882
+ * The http method the default loader sends, reflected as the `method`
1883
+ * attribute; `POST` when absent.
1884
+ * @returns {string|null}
1885
+ */
1886
+ get method(): string | null;
1887
+ /**
1888
+ * Changes the http method the way `src` changes the url: the loader is
1889
+ * built again and the select reloads.
1890
+ * @param {string|null|undefined} value
1891
+ */
1892
+ set method(value: string | null | undefined);
1893
+ /**
1894
+ * Writes `src` and `method` together and reloads once: the loader is built
1895
+ * again for the pair when either changed, as a single write of either does,
1896
+ * and the select then reloads as `reload()` does, whether or not they
1897
+ * changed.
727
1898
  *
728
- * citta.addEventListener('change', async () => {
729
- * await cap.withLoader((l) => l.reconfigureUrl(`/api/cap?citta=${citta.value}`));
730
- * await cap.reload();
1899
+ * citta.addEventListener('change', () => {
1900
+ * cap.reconfigure({ src: `/api/cap?citta=${citta.value}` }, { notify: true });
731
1901
  * });
1902
+ * @param {{ src?: string|null, method?: string|null }} attributes a name
1903
+ * left out keeps its value, null removes the attribute
1904
+ * @param {{ notify?: boolean }} [options] `notify` dispatches `change` when
1905
+ * the reload dropped a selected key, so a form or a table filtered by the
1906
+ * select sees the value it lost; nothing is dispatched otherwise
1907
+ * @returns {Promise<void>} settling once the reload has
1908
+ * @throws what the loader's invalidate, prefetch or key lookup throws
1909
+ */
1910
+ reconfigure(attributes: {
1911
+ src?: string | null;
1912
+ method?: string | null;
1913
+ }, { notify }?: {
1914
+ notify?: boolean;
1915
+ }): Promise<void>;
1916
+ /**
1917
+ * The text typed to search the vocabulary: what the dropdown was last
1918
+ * asked to match. `''` while the field is not being searched, its control
1919
+ * then showing the selection's label rather than a query.
1920
+ * @returns {string}
1921
+ */
1922
+ get query(): string;
1923
+ /**
1924
+ * Selects the keys, coerced by `k-type`: a key, an array of keys, or null or
1925
+ * undefined for none. The empty string is a key like any other, and a
1926
+ * single select keeps only the first key. The keys are applied at once, each labeled by itself until the loader's `exact`
1927
+ * answers; a key it does not answer is then dropped, a key removed in the
1928
+ * meantime stays removed, and a later assignment wins over the late answer.
1929
+ * A failed lookup keeps the keys and surfaces as an unhandled rejection.
1930
+ * No `change` is dispatched. A key containing a
1931
+ * comma, which the value attribute cannot carry, is kept and warned about
1932
+ * once per element. Before the render the write is held for the render to
1933
+ * apply, over the `value` attribute, which it leaves alone.
1934
+ * @param {any} vs
732
1935
  */
733
- reload(): Promise<void>;
734
1936
  set value(vs: any);
1937
+ /**
1938
+ * The selected keys, in selection order, for a multiple select; the one
1939
+ * key, or null, for a single one. Before the render it answers the keys
1940
+ * last written, or the `value` attribute, coerced and cut to one key as the
1941
+ * rendered select would.
1942
+ * @returns {any}
1943
+ */
735
1944
  get value(): any;
736
- /** The selection as {key, label, metadata} entries, the change detail's vocabulary: the only one for a single select, every one when multiple. */
737
- get entry(): any;
738
- get multiple(): any;
739
- set multiple(v: any);
740
- get itemList(): any;
741
- set itemList(v: any);
1945
+ /**
1946
+ * The selection as entries, the ones in the `change` detail: an array for
1947
+ * a multiple select, the one entry or null for a single one. An entry whose
1948
+ * label has not been resolved yet carries its key as the label.
1949
+ * @returns {SelectEntry|SelectEntry[]|null}
1950
+ */
1951
+ get entry(): SelectEntry | SelectEntry[] | null;
1952
+ /**
1953
+ * The field's disabled claim; turning it on while the dropdown is open
1954
+ * closes it and drops a pending search.
1955
+ * @type {boolean}
1956
+ */
1957
+ get disabled(): boolean;
1958
+ set disabled(v: boolean);
1959
+ /**
1960
+ * The field's readonly claim; turning it on while the dropdown is open
1961
+ * closes it and drops a pending search.
1962
+ * @type {boolean}
1963
+ */
1964
+ get readonly(): boolean;
1965
+ set readonly(v: boolean);
1966
+ /**
1967
+ * Whether the select holds several keys: `value` and `entry` answer arrays,
1968
+ * the input stays empty and the selection shows as removable badges in the
1969
+ * control. Before the render it answers the `multiple` attribute.
1970
+ * @returns {boolean}
1971
+ */
1972
+ get multiple(): boolean;
1973
+ /**
1974
+ * Turning it off keeps only the first selected key; either way the badges,
1975
+ * the items and the input follow at once.
1976
+ * @param {boolean} v
1977
+ */
1978
+ set multiple(v: boolean);
1979
+ /**
1980
+ * Whether the selection shows as the `items` list below the control instead
1981
+ * of as badges in it.
1982
+ * @returns {boolean}
1983
+ */
1984
+ get itemList(): boolean;
1985
+ /** @param {boolean} v */
1986
+ set itemList(v: boolean);
742
1987
  }
743
- /** A group of radios declared as ful-radio children, a fieldset carrying the group semantics. */
1988
+ /**
1989
+ * A group of radios declared as `ful-radio` children. Each `ful-radio` becomes
1990
+ * a radio input carrying its attributes, labelled by its content; every
1991
+ * `input-*` attribute on the group is forwarded onto each radio as well. The
1992
+ * default slot is the `<legend>`, and `header` and `footer` slots frame the
1993
+ * list. The radios share a name of their own, minted afresh for each group
1994
+ * (from `name` when there is one) so that same-named groups stay apart, and
1995
+ * carry `form=""`, so the group is the only element the form sees. A radio's
1996
+ * native `change` is stopped and republished as the group's own.
1997
+ *
1998
+ * The host carries the `radiogroup` role, `aria-readonly`, `aria-required`,
1999
+ * `aria-invalid` and the description. The disabled and readonly claims act on
2000
+ * the group's `<fieldset>`, and readonly refuses every click inside it.
2001
+ * `focus()` lands on the first radio.
2002
+ */
744
2003
  declare class RadioGroup extends Field {
745
2004
  #private;
2005
+ /** @type {string[]} */
746
2006
  static attributes: string[];
747
2007
  static slots: boolean;
2008
+ /** @type {string} */
748
2009
  static ROLE: string;
749
2010
  static template: string;
750
2011
  /**
751
2012
  * @param {{slots: any}} conf
752
- * @returns {any}
2013
+ * @returns {any} the pieces: the first radio as the control, the fieldset
2014
+ * as the `claims` and `freeze` pieces, and the host as the `described` and
2015
+ * `announces` pieces
753
2016
  */
754
2017
  _build({ slots }: {
755
2018
  slots: any;
756
2019
  }): any;
2020
+ /**
2021
+ * The value of the checked radio, `null` when none is. Under
2022
+ * `type="boolean"` it answers `true` for a radio whose value is `true` and
2023
+ * `false` for any other. Writing a value checks the radio carrying it, and
2024
+ * writing `null` or a value no radio carries clears the selection. Before
2025
+ * the render it answers the value last written, or the `value` attribute,
2026
+ * when a `ful-radio` carries it, and a write is held for the render to
2027
+ * apply.
2028
+ * @type {string | boolean | null}
2029
+ */
757
2030
  get value(): string | boolean | null;
758
2031
  set value(value: string | boolean | null);
759
2032
  }
760
- /** A checkbox, or a switch under the type=switch claim. */
2033
+ /**
2034
+ * A checkbox, or a switch with the `switch` role under `type="switch"`. The
2035
+ * label is the default slot, pointed at the input with `for` and `id`, so a
2036
+ * click on it toggles the value. Every `input-*` attribute is forwarded onto
2037
+ * the input. The input's native `change` is stopped and republished as the
2038
+ * field's own. Readonly freezes the whole choice, label included: no click
2039
+ * inside it toggles the value while the claim holds.
2040
+ */
761
2041
  declare class Checkbox extends Field {
762
2042
  #private;
2043
+ /** @type {string[]} */
763
2044
  static attributes: string[];
2045
+ /** @type {string[]} */
764
2046
  static observed: string[];
765
2047
  static slots: boolean;
766
2048
  static template: string;
2049
+ /**
2050
+ * @param {{ slots: Record<string, DocumentFragment> | undefined }} conf
2051
+ * @returns {any} the pieces, the `ful-choice` container being the `freeze` piece
2052
+ */
767
2053
  _build({ slots }: {
768
- slots: any;
769
- }): {
770
- fragment: any;
771
- control: any;
772
- error: any;
773
- label: any;
774
- freeze: any;
775
- };
776
- get value(): any;
777
- set value(value: any);
2054
+ slots: Record<string, DocumentFragment> | undefined;
2055
+ }): any;
2056
+ /**
2057
+ * Whether the box is checked. The `value` attribute is read with the `bool`
2058
+ * mapper, where only the text `true` means checked. Before the render it
2059
+ * answers the value last written, or the `value` attribute, and a write is
2060
+ * held for the render to apply.
2061
+ * @type {boolean}
2062
+ */
2063
+ get value(): boolean;
2064
+ set value(value: boolean);
778
2065
  }
779
- /** The sort control of a table header: focusable, keyboard-activated, walking asc, desc, unsorted. */
2066
+ export type TablePageRequest = {
2067
+ page: number;
2068
+ size: number;
2069
+ };
2070
+ export type TableSortRequest = {
2071
+ sorter: string;
2072
+ order: string;
2073
+ };
2074
+ export type TablePageResponse = {
2075
+ data: any[];
2076
+ size: number;
2077
+ };
2078
+ export type TableRowLoader = {
2079
+ load(pageRequest: TablePageRequest, sortRequest: TableSortRequest | null, filterRequest: Record<string, any>): Promise<TablePageResponse>;
2080
+ };
2081
+ /**
2082
+ * @typedef {{ page: number, size: number }} TablePageRequest the zero based
2083
+ * index of the page and the number of rows in a page
2084
+ */
2085
+ /**
2086
+ * @typedef {{ sorter: string, order: string }} TableSortRequest the sort key
2087
+ * of a column and its direction, `asc` or `desc`
2088
+ */
2089
+ /**
2090
+ * @typedef {{ data: any[], size: number }} TablePageResponse the rows of the
2091
+ * requested page and the total number of rows across every page
2092
+ */
2093
+ /**
2094
+ * @typedef {{ load(pageRequest: TablePageRequest, sortRequest: TableSortRequest|null, filterRequest: Record<string, any>): Promise<TablePageResponse> }} TableRowLoader
2095
+ */
2096
+ /**
2097
+ * The sort control of a table header, `ful-sorter`: a `role="button"` with
2098
+ * `tabindex="0"`, activated by a click or by Enter, NumpadEnter or Space.
2099
+ *
2100
+ * Each activation dispatches `sort:requested` (bubbling, not cancelable) with
2101
+ * `detail.value` set to `{ sorter, order }`: `sorter` is the `sorter`
2102
+ * attribute, read once at the upgrade, and `order` is the one after the
2103
+ * current `order` in the cycle `asc`, `desc`, `null`. The button does not
2104
+ * change its own `order`: whoever handles the event sets it, which `ful-table`
2105
+ * does once the sorted page has loaded.
2106
+ *
2107
+ * The stylesheet draws an up arrow for `asc` and a down arrow for `desc`. An
2108
+ * unsorted button shows the up arrow dimmed, `asc` being the order its first
2109
+ * activation asks for.
2110
+ */
780
2111
  declare class SortButton extends ParsedElement {
781
2112
  #private;
782
2113
  static attributes: string[];
783
2114
  static observed: string[];
784
2115
  render(): void;
785
- get order(): any;
786
- set order(value: any);
2116
+ /**
2117
+ * The direction the column is sorted in, `null` when unsorted.
2118
+ * @returns {string|null}
2119
+ */
2120
+ get order(): string | null;
2121
+ /**
2122
+ * Reflects the direction to the `order` attribute and sets `aria-sort` on
2123
+ * the enclosing `<th>`, if any: `ascending` for `asc`, `descending` for any
2124
+ * other non-empty value, removed for an empty one.
2125
+ * @param {string|null} value an empty string or `null` means unsorted
2126
+ */
2127
+ set order(value: string | null);
787
2128
  }
788
- /** The pager: a window of page links around the current one, and the reload control. */
2129
+ /**
2130
+ * The pager, `ful-pagination`: a previous button, a window of page buttons
2131
+ * around the current page, a next button, a reload button and a line saying
2132
+ * which page of how many is shown.
2133
+ *
2134
+ * The window holds at most `pages` buttons (read once at the upgrade, default 5),
2135
+ * centred on the current page and shifted back on the last pages so it stays
2136
+ * full. The current page's button carries `aria-current="page"` and stays
2137
+ * enabled. Previous and next are disabled where there is no page to go to.
2138
+ *
2139
+ * A click on an enabled button other than the current page dispatches
2140
+ * `page:requested` (bubbling, not cancelable) with `detail.value` the zero based
2141
+ * index of the page asked for; the reload button asks for the current page.
2142
+ * The pager does not move itself: whoever handles the event calls `update`,
2143
+ * which `ful-table` does once the page has loaded.
2144
+ *
2145
+ * The icons are the `ful-icon` names in `Pagination.config`.
2146
+ */
789
2147
  declare class Pagination extends ParsedElement {
790
2148
  #private;
791
2149
  static observed: string[];
@@ -798,47 +2156,94 @@ declare class Pagination extends ParsedElement {
798
2156
  static template: string;
799
2157
  render(): void;
800
2158
  /**
801
- * Moves the pager to a page, a page count, or both, and repaints once. The
802
- * two are one state: writing them one at a time repainted the bar twice per
803
- * load, the first pass drawing the new page against the old count.
804
- * @param {{ current?: number|null, total?: number|null }} [state]
2159
+ * Moves the pager to a page, a page count, or both, reflects both to their
2160
+ * attributes and repaints the bar once. A key left out keeps its value; a
2161
+ * `null` one becomes 0. A count below one is shown as one empty page.
2162
+ *
2163
+ * The bar is replaced on every repaint. When the focus was inside it, it
2164
+ * moves to the equivalent control of the new bar: the button of the same
2165
+ * page, else the same enabled control, else the current page's button.
2166
+ * @param {{ current?: number|null, total?: number|null }} [state] `current`
2167
+ * is the zero based index of the page shown, `total` the number of pages
805
2168
  */
806
2169
  update({ current: toCurrent, total: toTotal }?: {
807
2170
  current?: number | null;
808
2171
  total?: number | null;
809
2172
  }): void;
2173
+ /**
2174
+ * The number of pages, as last written.
2175
+ * @returns {number}
2176
+ */
810
2177
  get total(): number;
811
- set total(value: number);
2178
+ /**
2179
+ * Same as `update({ total: value })`.
2180
+ * @param {number|null} value
2181
+ */
2182
+ set total(value: number | null);
2183
+ /**
2184
+ * The zero based index of the page shown.
2185
+ * @returns {number}
2186
+ */
812
2187
  get current(): number;
813
- set current(value: number);
814
- }
815
- /** Reads the schema declaration into the header and row templates a table renders from. */
816
- declare class TableSchemaParser {
817
- static parse(nodeOrFragment: any, template: any): {
818
- headersTemplate: any;
819
- rowsTemplate: any;
820
- sort: {
821
- sorter: string | null;
822
- order: string | null;
823
- };
824
- length: number;
825
- };
2188
+ /**
2189
+ * Same as `update({ current: value })`.
2190
+ * @param {number|null} value
2191
+ */
2192
+ set current(value: number | null);
826
2193
  }
827
2194
  /** Serves a table's rows from an array held in memory, applying the sort and the paging itself. */
828
2195
  declare class InMemoryTableLoader {
829
2196
  #private;
830
- constructor(data: any);
831
- load(pageRequest: any, sortRequest: any, filterRequest: any): Promise<{
832
- data: any;
833
- size: any;
834
- }>;
835
- update(data: any): void;
2197
+ /**
2198
+ * @param {any[]} data the rows
2199
+ */
2200
+ constructor(data: any[]);
2201
+ /**
2202
+ * Sorts a copy of the rows by the `sorter` property, rows missing it last
2203
+ * in either direction and rows with equal values in their original order,
2204
+ * then slices the requested page. The filters are ignored.
2205
+ * @param {TablePageRequest} pageRequest
2206
+ * @param {{ sorter?: string|null, order?: string|null } | null} sortRequest
2207
+ * `order` `desc` sorts descending, anything else ascending; no `sorter`
2208
+ * keeps the original order
2209
+ * @param {Record<string, any>} filterRequest not read
2210
+ * @returns {Promise<TablePageResponse>}
2211
+ */
2212
+ load(pageRequest: TablePageRequest, sortRequest: {
2213
+ sorter?: string | null;
2214
+ order?: string | null;
2215
+ } | null, filterRequest: Record<string, any>): Promise<TablePageResponse>;
2216
+ /**
2217
+ * Replaces the rows. The table shows them at its next load.
2218
+ * @param {any[]} data
2219
+ */
2220
+ update(data: any[]): void;
836
2221
  }
837
2222
  /** Requests one page of rows from a url, passing the page, the sort and the filters to the endpoint. */
838
2223
  declare class RemoteTableLoader {
839
2224
  #private;
840
- constructor(http: any, url: any, method: any, responseMapper?: (response: any) => any);
841
- load(pageRequest: any, sortRequest: any, filterRequest: any): Promise<any>;
2225
+ /**
2226
+ * @param {{ request(method: string, url: string): any }} http the `http-client` component
2227
+ * @param {string} url
2228
+ * @param {string} method
2229
+ * @param {(response: any) => TablePageResponse} [responseMapper] turns the
2230
+ * parsed json body into the page response; the identity when omitted
2231
+ */
2232
+ constructor(http: {
2233
+ request(method: string, url: string): any;
2234
+ }, url: string, method: string, responseMapper?: (response: any) => TablePageResponse);
2235
+ /**
2236
+ * Sends the query parameters `page`, `size`, `sort` as `sorter,order`
2237
+ * (left out when `sortRequest` is null) and `filters` as a json object of
2238
+ * the filters with a truthy value (left out when there is none), and
2239
+ * answers the json body through the response mapper.
2240
+ * @param {TablePageRequest} pageRequest
2241
+ * @param {TableSortRequest|null} sortRequest
2242
+ * @param {Record<string, any>} filterRequest
2243
+ * @returns {Promise<TablePageResponse>} rejecting with what the client's
2244
+ * `fetchJson()` rejects with
2245
+ */
2246
+ load(pageRequest: TablePageRequest, sortRequest: TableSortRequest | null, filterRequest: Record<string, any>): Promise<TablePageResponse>;
842
2247
  }
843
2248
  /**
844
2249
  * Builds the table's loader from its attributes: an in-memory one, or the
@@ -846,98 +2251,453 @@ declare class RemoteTableLoader {
846
2251
  *
847
2252
  * A component registered under the `loader` attribute replaces this one and
848
2253
  * must implement `load(pageRequest, sortRequest, filterRequest)`, answering
849
- * `{ data, page, size }` for the requested page. `pageRequest` carries the page
850
- * index and its size, `sortRequest` the column and direction, and
2254
+ * `{ data, size }`: the rows of the requested page and the total count of
2255
+ * rows. `pageRequest` carries the page index and its size, `sortRequest` the column and direction, and
851
2256
  * `filterRequest` the values of the filters in the slot.
852
2257
  */
853
2258
  declare class TableLoader {
854
- static create(el: any, conf: any): InMemoryTableLoader | RemoteTableLoader;
2259
+ /**
2260
+ * A table with a `src` attribute gets a `RemoteTableLoader` sending through
2261
+ * the `http-client` component with the `method` attribute or `GET`, and
2262
+ * mapping the response through the component named by `response-mapper`,
2263
+ * if any. A table without `src` gets an empty `InMemoryTableLoader`, which
2264
+ * `withLoader` reaches to give it rows.
2265
+ * @param {Element & { component(name: string): any }} el the table
2266
+ * @param {unknown} [conf] not read
2267
+ * @returns {InMemoryTableLoader | RemoteTableLoader}
2268
+ */
2269
+ static create(el: Element & {
2270
+ component(name: string): any;
2271
+ }, conf?: unknown): InMemoryTableLoader | RemoteTableLoader;
855
2272
  }
856
- /** A table loading its rows from a loader, with sorting, pagination and an optional filter form. */
2273
+ /**
2274
+ * A table, `ful-table`, loading its rows a page at a time from a loader, with
2275
+ * a header sorter per sortable column, a `ful-pagination` under it and an
2276
+ * optional filter form.
2277
+ *
2278
+ * The loader is the component named by `loader`, default `loaders:table`
2279
+ * (see `TableLoader`). The `schema` slot declares the columns (see
2280
+ * `TableSchemaParser.parse`); the `filters` slot is wrapped in a `ful-form`,
2281
+ * whose values are the filter request; the `caption`, `footer` and `empty`
2282
+ * slots go in the table's caption, its `<tfoot>` and the panel shown for a
2283
+ * load answering no rows. Attributes starting with `table-` are forwarded,
2284
+ * without the prefix, to the inner `<table>`.
2285
+ *
2286
+ * With `autoload` the first load starts at the upgrade, which does not wait
2287
+ * for it. Without it the table shows an initial panel until `load`,
2288
+ * `reload` or `resetWithFilter` is called.
2289
+ *
2290
+ * Inside the table, and not inside a `ful-table` nested in it, a
2291
+ * `page:requested` loads that page with the current size,
2292
+ * sort and filters; a `sort:requested` loads the current page with the new
2293
+ * sort (none for a `null` order) and, once that load is the one showing, sets
2294
+ * the order of the sorter that asked and clears the others; a `submit:success`
2295
+ * of the filter form loads the first page with the submitted request as the
2296
+ * filters. The first load goes through the filter form too: `reload`, and so
2297
+ * `autoload`, submits it (see `reload`), so a `submit` listener rewriting
2298
+ * `detail.request` shapes every load alike. The filters a page, a sort or a size change carries are those of
2299
+ * the last load asked for, whether it is still running, loaded or failed, so
2300
+ * they are the ones the filter form shows. The loads these start, and the autoload, are not awaited by
2301
+ * anything: a failure shows the error panel and its rejection is unhandled.
2302
+ */
857
2303
  declare class Table extends ParsedElement {
858
2304
  #private;
2305
+ /** Read once at the upgrade. */
859
2306
  static attributes: string[];
860
- /**
861
- * The page size stays live: a rows-per-page control is a normal thing to
862
- * put next to a table, and the size is the one piece of the request an
863
- * author changes after the table is up. The rest of the request is the
864
- * table's own state, moved by the pager, the sorters and the filter form.
865
- */
2307
+ /** These stay live after the upgrade: see `pageSize`, `src` and `method`. */
866
2308
  static observed: string[];
867
2309
  static slots: boolean;
868
2310
  static config: {
869
2311
  searchIcon: string;
2312
+ emptyIcon: string;
870
2313
  };
871
2314
  static template: string;
872
2315
  static templates: {
873
2316
  row: string;
874
2317
  };
875
- /** How many rows a page asks the loader for: the size the next load will carry. */
2318
+ /**
2319
+ * How many rows a page asks the loader for: the size the next load will
2320
+ * carry. Ten before the render.
2321
+ * @returns {number}
2322
+ */
876
2323
  get pageSize(): number;
877
2324
  /**
878
- * Changes the page size and reloads from the first page, the current index
879
- * meaning nothing under a new size. A table that has not loaded yet only
880
- * records it: writing the size is not a request to start loading, which is
881
- * what `autoload` and `reload()` are for.
2325
+ * Changes the page size and reloads from the first page, keeping the sort
2326
+ * and the filters. A table that has not loaded yet only records it:
2327
+ * writing the size is not a request to start loading, which is what
2328
+ * `autoload` and `reload()` are for. A write that does not change the size
2329
+ * does nothing. A write made before the render is replaced by the declared
2330
+ * `page-size` when the table renders, or by ten when that is not a number
2331
+ * above zero.
882
2332
  *
883
- * Absent or null is the default of ten, so removing the attribute restores
884
- * it rather than asking the loader for NaN rows.
2333
+ * The reload is not awaited: a failure shows the error panel and its
2334
+ * rejection is unhandled.
2335
+ * @param {number|string|null|undefined} value a number or a string of
2336
+ * one, such as a select's value; absent or null is the default of ten,
2337
+ * so removing the attribute restores it, and a value that is not a
2338
+ * finite number above zero is ignored
2339
+ */
2340
+ set pageSize(value: number | string | null | undefined);
2341
+ /**
2342
+ * The url the loader asks for rows, reflected as the `src` attribute.
2343
+ * @returns {string|null}
2344
+ */
2345
+ get src(): string | null;
2346
+ /**
2347
+ * Points the table at another url: the loader is built again through its
2348
+ * component's `create`, as at the render, and the table reloads from the
2349
+ * first page, keeping the size, the sort and the filters. A table that has
2350
+ * not loaded yet only rebuilds its loader. A write that does not change the
2351
+ * url does nothing.
2352
+ *
2353
+ * The reload is not awaited: a failure shows the error panel and its
2354
+ * rejection is unhandled.
2355
+ * @param {string|null|undefined} value null or undefined removes the url,
2356
+ * which the default loader answers with an empty in-memory loader
2357
+ */
2358
+ set src(value: string | null | undefined);
2359
+ /**
2360
+ * The http method the default loader sends with, reflected as the `method`
2361
+ * attribute; `GET` when absent.
2362
+ * @returns {string|null}
2363
+ */
2364
+ get method(): string | null;
2365
+ /**
2366
+ * Changes the http method the way `src` changes the url: the loader is
2367
+ * built again and the table reloads from the first page.
2368
+ * @param {string|null|undefined} value
2369
+ */
2370
+ set method(value: string | null | undefined);
2371
+ /**
2372
+ * Writes `src` and `method` together and loads once: the loader is built
2373
+ * again for the pair when either changed, the page going back to the first
2374
+ * one as a single write of either does, and the table then reloads as
2375
+ * `reload()` does, whether or not they changed. Unlike a write of either,
2376
+ * it loads a table that has not loaded yet.
2377
+ * @param {{ src?: string|null, method?: string|null }} attributes a name
2378
+ * left out keeps its value, null removes the attribute
2379
+ * @returns {Promise<void>} as `load`
2380
+ */
2381
+ reconfigure(attributes: {
2382
+ src?: string | null;
2383
+ method?: string | null;
2384
+ }): Promise<void>;
2385
+ /**
2386
+ * @param {{ slots: Record<string, DocumentFragment> }} c
2387
+ * @returns {Promise<void>}
2388
+ * @throws {Error} when the `schema` slot is missing or holds no `<schema>`
885
2389
  */
886
- set pageSize(value: number);
887
2390
  render({ slots }: {
888
- slots: any;
2391
+ slots: Record<string, DocumentFragment>;
889
2392
  }): Promise<void>;
2393
+ /**
2394
+ * Loads the last request that loaded, or, before any has, the first page
2395
+ * with the declared size and sort. A size written through `pageSize` since
2396
+ * then replaces the page with the first one at that size.
2397
+ *
2398
+ * Before any load has loaded, a table with a `filters` slot submits its
2399
+ * filter form instead, so the first request goes through the same
2400
+ * `submit` listeners as the later ones, and the load its `submit:success`
2401
+ * starts is the one awaited. A filter submit that is cancelled, fails or
2402
+ * is dropped for one in flight loads nothing, and the promise resolves.
2403
+ * @returns {Promise<void>} as `load`
2404
+ */
890
2405
  reload(): Promise<void>;
891
- load(pageRequest: any, sortRequest: any, filterRequest: any): Promise<void>;
892
- /** Hands the loader to the callback, for runtime reconfigurations. */
893
- withLoader(fn: any): Promise<any>;
894
- resetWithFilter(filterRequest: any): Promise<void>;
2406
+ /**
2407
+ * Asks the loader for a page and shows it. Its filters are, from now on,
2408
+ * the ones a page, a sort or a size change carries, even while it runs
2409
+ * or once it failed.
2410
+ *
2411
+ * While the load runs the table carries `aria-busy="true"`, and the rows
2412
+ * already shown stay; the spinner panel shows only when there are none.
2413
+ * When the loader answers, the rows and the pager are replaced, the empty
2414
+ * panel shows for an answer with no rows (the `empty` slot rendered with
2415
+ * `schema`, `pageRequest`, `filterRequest` and `pageResponse` in scope),
2416
+ * the request becomes the one
2417
+ * `reload` repeats; once the elements of the rows and of the empty panel
2418
+ * have rendered, `aria-busy` is removed and `load:success` is
2419
+ * dispatched (bubbling, not cancelable) with `detail` set to
2420
+ * `{ pageRequest, sortRequest, filterRequest, response }`. A page beyond
2421
+ * the last one the answer reports is not shown: the last page is loaded
2422
+ * instead, and the returned promise settles with that load.
2423
+ *
2424
+ * When the loader rejects, the rows are removed, the error panel shows the
2425
+ * `reason` of each of the error's `problems`, one per line, or the error as text,
2426
+ * `aria-busy` is removed, `load:failure` is dispatched (bubbling, not
2427
+ * cancelable) with `detail` set to
2428
+ * `{ pageRequest, sortRequest, filterRequest, exception }`, and the
2429
+ * returned promise rejects with the same error. An answer the table cannot
2430
+ * page fails the same way, with an error saying so: one whose `size` is
2431
+ * not a number, as a response of another shape read without a
2432
+ * `response-mapper` is, or a page size of zero.
2433
+ *
2434
+ * A load started after this one supersedes it: when this one's answer or
2435
+ * failure arrives it changes nothing, dispatches nothing and resolves;
2436
+ * superseded while its rows render, it leaves them to the newer load and
2437
+ * dispatches nothing.
2438
+ * @param {TablePageRequest} pageRequest
2439
+ * @param {TableSortRequest|null} sortRequest
2440
+ * @param {Record<string, any>} filterRequest
2441
+ * @returns {Promise<void>}
2442
+ * @throws what the loader rejects with, unless a newer load superseded this one
2443
+ */
2444
+ load(pageRequest: TablePageRequest, sortRequest: TableSortRequest | null, filterRequest: Record<string, any>): Promise<void>;
2445
+ /**
2446
+ * Hands the loader to the callback, for runtime reconfigurations, such as
2447
+ * `update(rows)` on the in-memory loader. The loader exists from the
2448
+ * render on. Nothing is reloaded.
2449
+ * @template T
2450
+ * @param {(loader: any) => T | Promise<T>} fn
2451
+ * @returns {Promise<T>} what `fn` returns or resolves to
2452
+ */
2453
+ withLoader<T>(fn: (loader: any) => T | Promise<T>): Promise<T>;
2454
+ /**
2455
+ * Loads the first page with the given filters, keeping the size and the sort.
2456
+ * @param {Record<string, any>} filterRequest
2457
+ * @returns {Promise<void>} as `load`
2458
+ */
2459
+ resetWithFilter(filterRequest: Record<string, any>): Promise<void>;
895
2460
  }
2461
+ /**
2462
+ * An invoker button paired with the `ul[popover][role=menu]` that follows it:
2463
+ * the chrome behind every filter's operator, sensitivity and boolean value.
2464
+ *
2465
+ * It fills the menu from a vocabulary, wires it the first time more than one
2466
+ * choice survives the whitelist, pins the button to a static glyph when a
2467
+ * single one does, takes the shared menu keyboard protocol, and keeps the
2468
+ * button's value, glyph and aria-label in step. A pick that changes the value
2469
+ * calls back; the host decides what that means.
2470
+ *
2471
+ * The button's `value` attribute is the store, as it is for a native control:
2472
+ * the menu protocol finds the current item by it, and nothing mirrors it.
2473
+ */
2474
+ declare class ChoiceButton {
2475
+ #private;
2476
+ /**
2477
+ * The declared choices narrowed to a vocabulary, in the declared order.
2478
+ * When none of them is in the vocabulary, or none is declared, the answer
2479
+ * is a copy of the whole vocabulary.
2480
+ * @param {string[] | null | undefined} declared
2481
+ * @param {string[]} vocabulary
2482
+ * @returns {string[]}
2483
+ */
2484
+ static narrow(declared: string[] | null | undefined, vocabulary: string[]): string[];
2485
+ /**
2486
+ * Takes the button's next sibling as its menu and listens for picks on it.
2487
+ * The menu is neither filled nor wired until `allowed` is first written.
2488
+ *
2489
+ * A click on an item, while `interactive()` answers true, sets the value,
2490
+ * hides the menu and, when the value changed, calls `onPick`.
2491
+ * @param {HTMLElement} button the invoker, whose next sibling is its menu
2492
+ * @param {{vocabulary: string[], glyphs?: Record<string,string>, labelFor?: (v: string) => string,
2493
+ * display?: ((v: string) => string)|null, interactive?: () => boolean, onPick?: (v: string) => void}} conf
2494
+ * `vocabulary` lists every choice; `glyphs` maps a choice to its compact
2495
+ * glyph; `labelFor` answers the localized word, used as the button's
2496
+ * `aria-label` and beside the glyph in the menu (the choice itself by
2497
+ * default); `display` answers the button's text (by default the glyph,
2498
+ * or the choice where it has none); `interactive` is asked before a
2499
+ * pick is taken; `onPick` receives a pick that changed the value
2500
+ */
2501
+ constructor(button: HTMLElement, { vocabulary, glyphs, labelFor, display, interactive, onPick }: {
2502
+ vocabulary: string[];
2503
+ glyphs?: Record<string, string>;
2504
+ labelFor?: (v: string) => string;
2505
+ display?: ((v: string) => string) | null;
2506
+ interactive?: () => boolean;
2507
+ onPick?: (v: string) => void;
2508
+ });
2509
+ /**
2510
+ * The choices the menu offers. Writing it narrows the declared set with
2511
+ * `narrow`, refills the menu, wires the popover and the menu keyboard the
2512
+ * first time more than one choice is allowed, and updates the button's
2513
+ * disabled state and popup attributes. When one choice is left the button
2514
+ * is pinned and the value is set to it.
2515
+ * @type {string[]}
2516
+ */
2517
+ get allowed(): string[];
2518
+ set allowed(declared: string[]);
2519
+ /**
2520
+ * The choice when the menu allows it, otherwise the first allowed one.
2521
+ * @param {string|null} choice
2522
+ * @returns {string}
2523
+ */
2524
+ preferring(choice: string | null): string;
2525
+ /**
2526
+ * Whether fewer than two choices are allowed. A pinned button is disabled,
2527
+ * opens no menu and shows the single choice as its value.
2528
+ * @returns {boolean}
2529
+ */
2530
+ get pinned(): boolean;
2531
+ /**
2532
+ * The host's fixed claim: hides the button with the `hidden` attribute
2533
+ * while `value` keeps answering.
2534
+ * @param {boolean} fixed
2535
+ */
2536
+ set fixed(fixed: boolean);
2537
+ /**
2538
+ * The current choice, stored as the button's `value` attribute, where the
2539
+ * menu keyboard also finds it. Writing it sets the button's text from
2540
+ * `display`, its `aria-label` from `labelFor`, and `aria-checked` on the
2541
+ * menu item carrying it. It does not call `onPick`.
2542
+ * @type {string | null}
2543
+ */
2544
+ get value(): string | null;
2545
+ set value(choice: string | null);
2546
+ /**
2547
+ * The host's disabled claim. The button is disabled while either the claim
2548
+ * or the pin holds, so lifting one does not enable it while the other holds.
2549
+ * @param {boolean} claimed
2550
+ */
2551
+ set claimed(claimed: boolean);
2552
+ }
2553
+ export type FilterCriterion = {
2554
+ label: string | null;
2555
+ operator: string | null;
2556
+ operands: string[];
2557
+ };
896
2558
  /**
897
2559
  * The shared shape of every operator-and-operands filter: an operator menu, one
898
2560
  * or two operands of the type the subclass declares, and a tuple that mirrors
899
2561
  * the data-jpa compare annotations.
2562
+ *
2563
+ * The value is `[operator, operand]`, or `[operator, from, to]` for BETWEEN,
2564
+ * the only operator that shows the second operand. The `operators` attribute
2565
+ * whitelists the menu; the `operator` attribute fixes the operator for the
2566
+ * element's life instead, hiding its button, and every tuple then carries it.
2567
+ * Declaring both warns and the singular wins; a fixed name outside the
2568
+ * vocabulary warns and fixes the default operator.
2569
+ *
2570
+ * Both operands mirror the disabled and readonly claims, and readonly also
2571
+ * refuses the clicks that would open the operator menu. The change event fires
2572
+ * when either operand changes or a different operator is picked.
900
2573
  */
901
2574
  declare class CompareFilter extends Input {
2575
+ #private;
902
2576
  static observed: string[];
903
- static OPERATORS: string[];
904
- static DEFAULT_OPERATOR: string;
2577
+ static attributes: string[];
905
2578
  static template: string;
906
2579
  _operator: any;
907
2580
  _container: any;
908
2581
  _value1: any;
909
2582
  _value2: any;
910
2583
  _build(conf: any): {
911
- fragment: any;
2584
+ fragment: DocumentFragment;
912
2585
  control: any;
913
- error: any;
914
- label: any;
2586
+ error: Element | null;
2587
+ label: HTMLLabelElement | null;
915
2588
  freeze: any;
916
2589
  also: any[];
917
2590
  };
918
2591
  _showDefaultOperator(): void;
2592
+ /**
2593
+ * Restores the declared `value` tuple, operator included, as a field reset
2594
+ * does. Without a `value` attribute it empties the operands and also shows
2595
+ * the default operator again, or the first whitelisted one where the
2596
+ * default is not allowed.
2597
+ */
919
2598
  formResetCallback(): void;
2599
+ /**
2600
+ * The native input type both operands render with.
2601
+ * @returns {string}
2602
+ */
920
2603
  _type(): string;
921
- _serialize(v: any): any;
922
- _deserialize(v: any): any;
2604
+ /**
2605
+ * Converts an operand from its input's value to the form the tuple carries.
2606
+ * @param {string} v
2607
+ * @returns {string|null}
2608
+ */
2609
+ _serialize(v: string): string | null;
2610
+ /**
2611
+ * Converts an operand from the tuple to the value its input shows.
2612
+ * @param {string} v
2613
+ * @returns {string}
2614
+ */
2615
+ _deserialize(v: string): string;
2616
+ /**
2617
+ * The operator shown on render and after a valueless reset, when the whitelist allows it.
2618
+ * @returns {string}
2619
+ */
923
2620
  _defaultOperator(): string;
2621
+ /**
2622
+ * Every operator the filter knows, which `operators` narrows.
2623
+ * @returns {string[]}
2624
+ */
924
2625
  _vocabulary(): string[];
925
2626
  _declaredOperators: any;
926
- get operators(): any;
927
- set operators(declared: any);
2627
+ /**
2628
+ * The operators the menu offers, narrowed to the vocabulary: an empty list,
2629
+ * or one where no name is known, offers all of it. A single operator pins
2630
+ * the button, and an assigned tuple then carries that operator whatever it
2631
+ * names. When the operator shown leaves the list, the first listed one is
2632
+ * shown. An assignment made before the upgrade is held narrowed and
2633
+ * answered back until the render, when the `operators` attribute is applied
2634
+ * over it, an absent attribute meaning the whole vocabulary. Assignments are
2635
+ * ignored while the `operator` attribute fixes the operator.
2636
+ * @returns {string[]|undefined} undefined before the upgrade when nothing was assigned
2637
+ */
2638
+ get operators(): string[] | undefined;
2639
+ /** @param {string[]|null} declared */
2640
+ set operators(declared: string[] | null);
2641
+ /**
2642
+ * The filter's tuple, `[operator, operand]` or `[operator, from, to]` for
2643
+ * BETWEEN, each operand converted by `_serialize` (an ISO instant for the
2644
+ * instant filter). Null while any operand the operator uses is empty.
2645
+ * Before the render it answers the tuple last written, or the `value`
2646
+ * attribute, as the rendered filter would show it.
2647
+ * @returns {any[]|null}
2648
+ */
928
2649
  get value(): any[] | null;
929
- set value(v: any[] | null);
2650
+ /**
2651
+ * Shows a tuple: the operator it names, or the first allowed one where the
2652
+ * whitelist, a pin or a fixed operator does not allow it, and its operands, an operand missing from a shorter tuple
2653
+ * leaving its input empty. Null or undefined empties both operands and
2654
+ * keeps the operator. Before the render the tuple is held for the render
2655
+ * to apply, over the `value` attribute, which it leaves alone.
2656
+ * @param {any[]|null|undefined} v
2657
+ */
2658
+ set value(v: any[] | null | undefined);
2659
+ /**
2660
+ * The tuple `_tuple` answers once `_applyTuple(v)` has shown `v`, read
2661
+ * from the declared `operator` and `operators` in place of the menu.
2662
+ * @param {any[]|null|undefined} v
2663
+ * @returns {any[]|null}
2664
+ */
2665
+ _shownTuple(v: any[] | null | undefined): any[] | null;
930
2666
  _tuple(): any[] | null;
931
2667
  _applyTuple(v: any): void;
932
2668
  _showOperator(operator: any): void;
933
- /** only a BETWEEN carries a second operand */
2669
+ /** Shows the second operand only while the operator is BETWEEN. */
934
2670
  _syncBetween(): void;
2671
+ /**
2672
+ * The operator and the operands the operator uses, as their inputs show
2673
+ * them rather than as serialized: a date filter describes what is in its
2674
+ * control, not the ISO string it sends. Null while every one of those
2675
+ * operands is empty, so a BETWEEN with one bound answers that bound while
2676
+ * its value is still null, and before the filter has rendered.
2677
+ * @returns {FilterCriterion|null}
2678
+ */
2679
+ get criterion(): FilterCriterion | null;
2680
+ /**
2681
+ * The field's own disabled claim. Setting it disables both operands and
2682
+ * every menu button the filter composes; a button pinned to one choice
2683
+ * stays disabled when the claim is lifted.
2684
+ * @returns {boolean}
2685
+ */
935
2686
  get disabled(): boolean;
2687
+ /** @param {boolean} d */
936
2688
  set disabled(d: boolean);
937
- /** every menu button the filter composes, so one claim reaches them all */
938
- _choices(): any[];
2689
+ /**
2690
+ * The menu buttons the filter composes, which the disabled claim reaches. A
2691
+ * subclass adding a menu appends its own to the base's.
2692
+ * @returns {ChoiceButton[]}
2693
+ */
2694
+ _choices(): ChoiceButton[];
939
2695
  }
940
- /** The compare filter over ISO instants, defaulting to LTE. */
2696
+ /**
2697
+ * The compare filter over ISO instants, defaulting to LTE. The operands are
2698
+ * shown as local date and time in the page's timezone and carried in the tuple
2699
+ * as UTC ISO instants.
2700
+ */
941
2701
  declare class InstantFilter extends CompareFilter {
942
2702
  _defaultOperator(): string;
943
2703
  _type(): string;
@@ -952,35 +2712,93 @@ declare class LocalDateFilter extends CompareFilter {
952
2712
  declare class NumberFilter extends CompareFilter {
953
2713
  _type(): string;
954
2714
  }
955
- /** The compare filter over text, carrying a case sensitivity beside the operator. */
2715
+ /**
2716
+ * The compare filter over text, defaulting to CONTAINS and adding CONTAINS,
2717
+ * STARTS_WITH and ENDS_WITH to the compare operators.
2718
+ *
2719
+ * Its tuple carries a case sensitivity after the operator, `[operator,
2720
+ * sensitivity, operand]` or `[operator, sensitivity, from, to]`, the
2721
+ * sensitivity being IGNORE_CASE (the default) or CASE_SENSITIVE and switched
2722
+ * through a second menu. The `sensitivities` attribute whitelists that menu and
2723
+ * `sensitivity` fixes one mode, with the same rules and warnings as
2724
+ * `operators` and `operator`.
2725
+ */
956
2726
  declare class TextFilter extends CompareFilter {
957
2727
  static observed: string[];
2728
+ static attributes: string[];
958
2729
  static template: string;
959
2730
  _defaultOperator(): string;
960
2731
  _vocabulary(): string[];
961
2732
  _sensitivityButton: any;
962
2733
  _build(conf: any): {
963
- fragment: any;
2734
+ fragment: DocumentFragment;
964
2735
  control: any;
965
- error: any;
966
- label: any;
2736
+ error: Element | null;
2737
+ label: HTMLLabelElement | null;
967
2738
  freeze: any;
968
2739
  also: any[];
969
2740
  };
970
2741
  _choices(): any[];
971
2742
  get _sensitivity(): any;
972
2743
  _declaredSensitivities: any;
973
- get sensitivities(): any;
974
- set sensitivities(declared: any);
2744
+ /**
2745
+ * The sensitivities the menu offers, narrowed to IGNORE_CASE and
2746
+ * CASE_SENSITIVE: an empty list, or one where no name is known, offers both.
2747
+ * When the mode shown leaves the list, the first listed one is shown. Held
2748
+ * before the upgrade and ignored under a fixed `sensitivity`, as `operators` is.
2749
+ * @returns {string[]|undefined} undefined before the upgrade when nothing was assigned
2750
+ */
2751
+ get sensitivities(): string[] | undefined;
2752
+ /** @param {string[]|null} declared */
2753
+ set sensitivities(declared: string[] | null);
2754
+ /**
2755
+ * The tuple with the sensitivity after the operator, `[operator,
2756
+ * sensitivity, ...operands]`. Null while any operand the operator uses is
2757
+ * empty. Before the render it answers the tuple last written, or the
2758
+ * `value` attribute, as the rendered filter would show it.
2759
+ * @returns {any[]|null}
2760
+ */
975
2761
  get value(): any[] | null;
976
- set value(v: any[] | null);
2762
+ /**
2763
+ * Shows a tuple as the compare filter does, reading the sensitivity at
2764
+ * index 1. A sensitivity the whitelist does not allow leaves the current
2765
+ * one in place. Null or undefined empties the operands and keeps the
2766
+ * operator and the sensitivity. Before the render the tuple is held, as
2767
+ * on the compare filters.
2768
+ * @param {any[]|null|undefined} v
2769
+ */
2770
+ set value(v: any[] | null | undefined);
2771
+ /**
2772
+ * Restores the declared `value` tuple, sensitivity included. Without a
2773
+ * `value` attribute it also shows the default operator and IGNORE_CASE
2774
+ * again, or the first whitelisted mode where IGNORE_CASE is not allowed.
2775
+ */
977
2776
  formResetCallback(): void;
978
2777
  }
979
- /** The boolean filter: an EQ or NEQ operator and an any/yes/no menu. */
2778
+ /**
2779
+ * The boolean filter: an EQ or NEQ operator menu and a value menu of any, yes
2780
+ * and no, the value button showing the localized word.
2781
+ *
2782
+ * The value is `[operator, 'true']` or `[operator, 'false']`, null while the
2783
+ * value menu is on any. The `operators` attribute whitelists the operator menu
2784
+ * as on the compare filters. The disabled claim reaches the value button and
2785
+ * both menus, and readonly refuses the clicks that would open them. The
2786
+ * required and readonly claims reflect on the host only: no `aria-required` or
2787
+ * `aria-readonly` is written, since the value button's role accepts neither.
2788
+ */
980
2789
  declare class BooleanFilter extends Field {
981
2790
  static observed: string[];
982
2791
  static slots: boolean;
2792
+ /**
2793
+ * The operators the filter knows, which `operators` narrows.
2794
+ * @type {string[]}
2795
+ */
983
2796
  static OPERATORS: string[];
2797
+ /**
2798
+ * The operator shown on render and after a valueless reset, or the first
2799
+ * whitelisted one where the whitelist excludes it.
2800
+ * @type {string}
2801
+ */
984
2802
  static DEFAULT_OPERATOR: string;
985
2803
  static template: string;
986
2804
  _operator: any;
@@ -989,212 +2807,886 @@ declare class BooleanFilter extends Field {
989
2807
  _build({ slots }: {
990
2808
  slots: any;
991
2809
  }): {
992
- fragment: any;
993
- control: any;
994
- error: any;
995
- label: any;
2810
+ fragment: DocumentFragment;
2811
+ control: HTMLElement;
2812
+ error: Element | null;
2813
+ label: HTMLLabelElement | null;
996
2814
  announces: null;
997
2815
  freeze: any;
998
2816
  };
999
2817
  _declaredOperators: any;
1000
- get operators(): any;
1001
- set operators(declared: any);
2818
+ /**
2819
+ * The operators the menu offers, narrowed to EQ and NEQ: an empty list, or
2820
+ * one where no name is known, offers both, and a single one pins the
2821
+ * button. When the operator shown leaves the list, the first listed one is
2822
+ * shown. Held before the upgrade and replaced by the `operators` attribute
2823
+ * at the render, as on the compare filters.
2824
+ * @returns {string[]|undefined} undefined before the upgrade when nothing was assigned
2825
+ */
2826
+ get operators(): string[] | undefined;
2827
+ /** @param {string[]|null} declared */
2828
+ set operators(declared: string[] | null);
2829
+ /** @returns {string[]} */
1002
2830
  _vocabulary(): string[];
1003
- get value(): any[] | null;
1004
- set value(v: any[] | null);
2831
+ /**
2832
+ * `[operator, token]`, the token being 'true' or 'false'; null while the
2833
+ * value menu is on any. Before the render it answers the tuple last
2834
+ * written, or the `value` attribute, as the rendered filter would show it.
2835
+ * @returns {string[]|null}
2836
+ */
2837
+ get value(): string[] | null;
2838
+ /**
2839
+ * Shows a tuple: its operator, or the first allowed one where the whitelist
2840
+ * does not allow it, and its token, a missing token meaning any. Null or undefined sets the value menu
2841
+ * back to any and keeps the operator. Before the render the tuple is held,
2842
+ * as on the compare filters.
2843
+ * @param {any[]|null|undefined} v
2844
+ */
2845
+ set value(v: any[] | null | undefined);
2846
+ /**
2847
+ * Restores the declared `value` tuple. Without a `value` attribute it sets
2848
+ * the value menu back to any and also shows the default operator again.
2849
+ */
2850
+ formResetCallback(): void;
2851
+ /**
2852
+ * The operator and the localized word the value menu shows, not the token.
2853
+ * Null while the value menu is on any, and before the filter has rendered.
2854
+ * @returns {FilterCriterion|null}
2855
+ */
2856
+ get criterion(): FilterCriterion | null;
2857
+ /**
2858
+ * The field's own disabled claim. Setting it disables the value button and
2859
+ * both menu buttons; an operator button pinned to one choice stays disabled
2860
+ * when the claim is lifted.
2861
+ * @returns {boolean}
2862
+ */
1005
2863
  get disabled(): boolean;
2864
+ /** @param {boolean} d */
1006
2865
  set disabled(d: boolean);
1007
2866
  }
1008
- /** An info icon button toggling a popover with a short explanation. */
2867
+ /**
2868
+ * Set membership over a select's vocabulary, answering for both of data-jpa's
2869
+ * set annotations: InEnum and InList take the same bare array of values and no
2870
+ * operator, differing only in what the server converts them to. A declared
2871
+ * `operator` attribute is sent in front of the keys instead, for a server field
2872
+ * that reads the set through a compare.
2873
+ */
2874
+ declare class InFilter extends Select {
2875
+ #private;
2876
+ static attributes: string[];
2877
+ /**
2878
+ * The chosen keys as an array, a single choice included, led by the
2879
+ * declared `operator` when there is one. Null while nothing is chosen, so
2880
+ * that a table drops the filter instead of sending an empty set that
2881
+ * matches nothing. Before the render it answers the keys the select answers
2882
+ * then.
2883
+ * @returns {any[]|null}
2884
+ */
2885
+ get value(): any[] | null;
2886
+ /**
2887
+ * Selects the keys of an array or a single bare key; a leading element equal
2888
+ * to the declared `operator` is dropped first, so the filter takes back what
2889
+ * it answered. A single select takes the first key. Null or undefined clears
2890
+ * the selection. Before the render the keys are held, as the select holds
2891
+ * them.
2892
+ * @param {any} v
2893
+ */
2894
+ set value(v: any);
2895
+ /**
2896
+ * The declared operator, or null, and the labels of the chosen entries. Null
2897
+ * while nothing is chosen, as before the filter has rendered.
2898
+ * @returns {FilterCriterion|null}
2899
+ */
2900
+ get criterion(): FilterCriterion | null;
2901
+ }
2902
+ /**
2903
+ * An icon button that toggles a native popover, a `ful-note`, holding the
2904
+ * default slot as a short explanation. The popover gives light dismiss and
2905
+ * Escape, and the button carries an `aria-expanded` kept in step with it and
2906
+ * a localized `aria-label`.
2907
+ *
2908
+ * The marker is the `ful-icon` that `Tooltip.config.icon` names for the whole
2909
+ * page, or the one the `icon` attribute names for a single tooltip. A name the
2910
+ * library does not paint is declared by the page as
2911
+ * `ful-icon[name='...'] { mask-image: ... }`.
2912
+ *
2913
+ * `placement` picks the side of the marker the note opens on: `top` (the
2914
+ * default), `bottom`, `left` or `right`. The note is placed in script on every
2915
+ * platform, centred on the marker, clamped into the viewport and following the
2916
+ * marker on scroll and resize, with a callout on the edge facing the marker.
2917
+ *
2918
+ * `describes` offers the note to the nearest ancestor that takes a description
2919
+ * (see `describable`), a field's control in practice: when it is taken, the
2920
+ * note becomes part of that control's accessible description and the marker
2921
+ * leaves the tab order, staying clickable. When nothing takes it, the marker
2922
+ * keeps its tab stop and a warning is logged.
2923
+ */
1009
2924
  declare class Tooltip extends ParsedElement {
2925
+ #private;
1010
2926
  static slots: boolean;
1011
2927
  static attributes: string[];
2928
+ /** The page-wide defaults: `icon` is the `ful-icon` name of the marker. */
1012
2929
  static config: {
1013
2930
  icon: string;
1014
2931
  };
1015
2932
  static template: string;
2933
+ /**
2934
+ * @param {{ slots: Record<string, DocumentFragment> }} c
2935
+ */
1016
2936
  render({ slots }: {
1017
- slots: any;
2937
+ slots: Record<string, DocumentFragment>;
1018
2938
  }): void;
1019
2939
  }
1020
- /** A modal dialog on the native platform, open()/ask() resolving with the closer's data-result. */
2940
+ export type DialogOutcome = {
2941
+ dismissed: boolean;
2942
+ result: string | null;
2943
+ response: any;
2944
+ };
2945
+ /**
2946
+ * How a dialog ended. `dismissed` is true for Escape, the close button, a
2947
+ * backdrop click, a `data-result=""` button, `close()` without a result and a
2948
+ * removal from the document; `result` is the `data-result` of the button, or
2949
+ * the argument of `close(result)`, that answered; `response` is what the form
2950
+ * answered with under `close-on-submit`. Whichever of `result` and `response`
2951
+ * did not answer is null, as both are on a dismissal.
2952
+ * @typedef {{ dismissed: boolean, result: string|null, response: any }} DialogOutcome
2953
+ */
2954
+ /**
2955
+ * A modal dialog on the native `<dialog>`, rendered with a header, a body and
2956
+ * a footer. `open()` and `ask()` show it and resolve with a `DialogOutcome`.
2957
+ *
2958
+ * The `header` attribute is the title, rendered as an `h2` that names the
2959
+ * dialog through `aria-labelledby`; without it the dialog is unnamed. The
2960
+ * `header` slot is rendered in the header before the title, the default slot
2961
+ * is the body, and the `buttons` slot is rendered in the footer. Without a
2962
+ * `buttons` slot, and without `close-on-submit`, the footer holds a localized
2963
+ * acknowledge button answering `acknowledged`.
2964
+ *
2965
+ * Any button inside the dialog carrying `data-result` closes it with that
2966
+ * result, an empty one being a dismissal. The header carries a close button,
2967
+ * and a press and release both on the backdrop close the dialog, each as a
2968
+ * dismissal, as Escape does.
2969
+ *
2970
+ * Those three gestures go through `dismiss(reason)`, the reason being
2971
+ * `button`, `backdrop` or `escape`: before it closes the dialog, the element
2972
+ * dispatches a cancelable, non-bubbling `dialog:dismiss` with
2973
+ * `detail: { reason }`, and a listener calling `preventDefault()` keeps it
2974
+ * open. Under `guard-dismiss`, a dismissal nobody refused, while a `ful-form`
2975
+ * of the dialog holds unsaved edits (see `Form.dirty`), first asks the user,
2976
+ * through `Dialog.confirm`, whether to close and drop them. A `data-result`
2977
+ * button, `close-on-submit`, `close()` and `update()` are answers or the page
2978
+ * deciding: they dispatch nothing and ask nothing.
2979
+ *
2980
+ * The platform lets a page refuse Escape once per user interaction: a second
2981
+ * Escape with no click or keystroke in between closes the dialog whatever the
2982
+ * page does, `requires-answer` and `guard-dismiss` included. That close is
2983
+ * still announced by a `dialog:dismiss`, which is then not cancelable.
2984
+ *
2985
+ * `requires-answer` withholds the close button and refuses Escape and the
2986
+ * backdrop, so the dialog is closed only by a button carrying `data-result`
2987
+ * or from code, through `close()` or `dismiss()`.
2988
+ *
2989
+ * `close-on-submit` closes the dialog when a `ful-form` that is a direct child
2990
+ * of the body dispatches `submit:success`, answering with its
2991
+ * `detail.response`. A form deeper in the body, such as the filter form of a
2992
+ * `ful-table`, does not close it. The listener sits on the body, so a form
2993
+ * delivered by `update()` is covered as well.
2994
+ *
2995
+ * Every close dispatches a non-bubbling `close` event on the element carrying
2996
+ * the `DialogOutcome` as `detail`, before the pending `ask()` calls resolve.
2997
+ * An element anywhere in the page carrying `dialog-target` set to the
2998
+ * element's id opens it on click.
2999
+ *
3000
+ * The chrome is styled by class as well as by tag, so a plain `<dialog
3001
+ * class="ful-dialog">` written by a page gets the same look: the tag form
3002
+ * matches a direct child, and `ful-dialog-header`, `ful-dialog-body` and
3003
+ * `ful-dialog-footer` match at any depth, as a dialog whose content is wrapped
3004
+ * in a form needs.
3005
+ */
1021
3006
  declare class Dialog extends ParsedElement {
1022
3007
  #private;
1023
3008
  static attributes: string[];
1024
3009
  static slots: boolean;
1025
3010
  static template: string;
3011
+ /**
3012
+ * @param {{ slots: Record<string, DocumentFragment> }} c
3013
+ */
1026
3014
  render({ slots }: {
1027
- slots: any;
3015
+ slots: Record<string, DocumentFragment>;
1028
3016
  }): void;
3017
+ /**
3018
+ * Resolves every pending `ask()` and `open()` with the outcome so far,
3019
+ * a dismissal for a dialog still open, so that no caller waits on a dialog
3020
+ * that left the document. The native dialog is not closed and no `close`
3021
+ * event is dispatched.
3022
+ */
1029
3023
  disconnectedCallback(): void;
1030
- open(): Promise<any>;
1031
- ask(): Promise<any>;
1032
3024
  /**
1033
- * Re-fires section:requested on the body, open or closed: the explicit
1034
- * request for a body that wants refreshing. A failed refresh paints its
1035
- * problems, nothing rejects: there is no caller to reject towards.
3025
+ * The same as `ask()`.
3026
+ * @returns {Promise<DialogOutcome>}
3027
+ */
3028
+ open(): Promise<DialogOutcome>;
3029
+ /**
3030
+ * Shows the dialog as a modal and resolves with how it ends.
3031
+ *
3032
+ * The call that opens the dialog resets the outcome of the previous
3033
+ * opening, hides the error and loading sections, and dispatches
3034
+ * `section:requested` on the element (bubbling, with
3035
+ * `detail: { section, first, name: null, index: null }`, `section` being
3036
+ * the body and `first` true until a delivery succeeds), so a
3037
+ * listener can fill the body. A failed request is painted into the body
3038
+ * and does not reject. On a dialog already open nothing is shown or
3039
+ * requested, and the call waits for the same ending as the calls before it;
3040
+ * one the page moved while open, which is no longer modal, opens anew.
3041
+ * @returns {Promise<DialogOutcome>} resolving on close, or on removal from
3042
+ * the document with a dismissal
3043
+ */
3044
+ ask(): Promise<DialogOutcome>;
3045
+ /**
3046
+ * Empties and hides the body, shows the loading section, opens the dialog
3047
+ * if it is closed, and fills the body with what the callback resolves to.
3048
+ * A rejection is shown, as the problems' reasons, in the error section,
3049
+ * which has `role="alert"`, and the body is hidden. The title stays the
3050
+ * `header` attribute.
3051
+ *
3052
+ * No `section:requested` is dispatched, and no `ask()` waiter is added:
3053
+ * listen for the `close` event to learn how the dialog ended. When a newer
3054
+ * `update()` starts before the callback settles, this call paints nothing
3055
+ * and resolves `null`, the body being the newer call's; it still rejects
3056
+ * with the callback's error.
3057
+ * @param {() => Node|string|Promise<Node|string>} cb producing the body content
3058
+ * @returns {Promise<Element|null>} the body section, in the document, or
3059
+ * `null` for a call a newer one overtook
3060
+ * @throws {any} what the callback threw or rejected with
3061
+ */
3062
+ update(cb: () => Node | string | Promise<Node | string>): Promise<Element | null>;
3063
+ /**
3064
+ * Dispatches `section:requested` for the body again, as `ask()` does,
3065
+ * whether the dialog is open or closed. A failed request is painted into
3066
+ * the body.
3067
+ * @returns {Promise<any[]|undefined>} the listeners' answers, undefined when
3068
+ * nobody answered or the request failed; it never rejects
1036
3069
  */
1037
3070
  refresh(): Promise<any[] | undefined>;
1038
- close(result: any): void;
3071
+ /**
3072
+ * Dismisses the dialog as the user would: dispatches the cancelable
3073
+ * `dialog:dismiss` with `detail: { reason }` and, under `guard-dismiss`,
3074
+ * asks before dropping unsaved edits, closing the dialog as a dismissal
3075
+ * unless either refused. `requires-answer` does not refuse it, as it does
3076
+ * not refuse `close()`. Does nothing on a closed dialog.
3077
+ * @param {string} reason why it is dismissed, such as `navigation`
3078
+ * @returns {Promise<boolean>} whether the dialog is closed, false when a
3079
+ * listener or the user kept it open
3080
+ */
3081
+ dismiss(reason: string): Promise<boolean>;
3082
+ /**
3083
+ * Closes an open dialog, doing nothing on a closed one. A non-empty result
3084
+ * answers as the button carrying that `data-result` would; no result, or
3085
+ * an empty one, is a dismissal.
3086
+ * @param {string} [result]
3087
+ */
3088
+ close(result?: string): void;
3089
+ /**
3090
+ * Appends a new `ful-dialog` to `document.body`, asks it, and removes it
3091
+ * once it is answered or its render fails.
3092
+ *
3093
+ * A string body is set as text and never parsed as markup; a node is
3094
+ * appended as it is. Each button is either a `[result, label, className?]`
3095
+ * tuple, rendered as a `button` carrying `data-result`, or a node the
3096
+ * caller built, placed in the footer in the order given. With no buttons
3097
+ * the dialog shows its localized acknowledge button.
3098
+ * @param {string} header the title
3099
+ * @param {string|Node} body
3100
+ * @param {([string, string, string?]|Node)[]} [buttons]
3101
+ * @param {{ className?: string }} [options] `className` is set as the class
3102
+ * of the `ful-dialog` element
3103
+ * @returns {Promise<DialogOutcome>} rejecting with what the render threw
3104
+ */
3105
+ static ask(header: string, body: string | Node, buttons?: ([string, string, string?] | Node)[], { className }?: {
3106
+ className?: string;
3107
+ }): Promise<DialogOutcome>;
3108
+ /**
3109
+ * Asks a yes/no question through `Dialog.ask`, with a cancel button and a
3110
+ * confirm button carrying the `ful-button` class.
3111
+ * @param {string} header the title
3112
+ * @param {string|Node} body as `Dialog.ask` takes it
3113
+ * @param {{ confirm?: string, cancel?: string }} [labels] the button labels,
3114
+ * `dialog.confirm` and `dialog.cancel` localized by default
3115
+ * @param {{ className?: string }} [options] as `Dialog.ask` takes them
3116
+ * @returns {Promise<boolean>} true only for the confirm button; the cancel
3117
+ * button, the close button and Escape answer false
3118
+ */
3119
+ static confirm(header: string, body: string | Node, labels?: {
3120
+ confirm?: string;
3121
+ cancel?: string;
3122
+ }, options?: {
3123
+ className?: string;
3124
+ }): Promise<boolean>;
1039
3125
  }
1040
3126
  /**
1041
- * A side panel drawer on the native dialog platform, update() owning its
1042
- * open-deliver cycle.
3127
+ * A side panel on the native `<dialog>`, shown as a modal: a header holding
3128
+ * the title and a localized close button, then loading, error and content
3129
+ * sections, the default slot being the content.
3130
+ *
3131
+ * The `header` attribute is the initial title, rendered as an `h2` that names
3132
+ * the panel through `aria-labelledby`. The `header` slot is rendered in the
3133
+ * header before the title and outside it, so setting the title as text does
3134
+ * not remove it. `placement` is copied onto the native dialog: `end` (the
3135
+ * default, when absent) or `start`, following the writing direction.
1043
3136
  *
1044
- * The `header` slot is content beside the title, before it: an icon, a badge, a
1045
- * status. It sits outside the heading rather than in it because `update()` sets
1046
- * the title through `textContent`, which would take anything nested there with
1047
- * it.
3137
+ * The close button, Escape, and a press and release both on the backdrop all
3138
+ * go through `dismiss(reason)`, the reason being `button`, `backdrop` or
3139
+ * `escape`: before it closes the drawer, the element dispatches a cancelable,
3140
+ * non-bubbling `drawer:dismiss` with `detail: { reason }`, and a listener
3141
+ * calling `preventDefault()` keeps it open. Under `guard-dismiss`, a dismissal
3142
+ * nobody refused, while a `ful-form` of the drawer holds unsaved edits (see
3143
+ * `Form.dirty`), first asks the user, through `Dialog.confirm`, whether to
3144
+ * close and drop them. A `data-result` button, `close-on-submit`, `close()`
3145
+ * and `update()` are answers or the page deciding: they dispatch nothing and
3146
+ * ask nothing. The platform lets a page refuse Escape once per user
3147
+ * interaction: a second Escape with no click or keystroke in between closes
3148
+ * the drawer at once, without the slide, `guard-dismiss` included, and its
3149
+ * `drawer:dismiss` is then not cancelable.
3150
+ *
3151
+ * Any button inside the drawer carrying `data-result` closes it with that
3152
+ * result, an empty one being a dismissal. Every close dispatches a
3153
+ * non-bubbling `close` event on the element carrying the `DialogOutcome` as
3154
+ * `detail`, before the pending `ask()` calls resolve.
3155
+ *
3156
+ * `close-on-submit` closes the drawer when a `ful-form` that is a direct child
3157
+ * of the content section dispatches `submit:success`, carrying its
3158
+ * `detail.response`, which may be null. A form deeper in the content, such as
3159
+ * the filter form of a `ful-table`, does not close it. The listener sits on
3160
+ * the content section, so a form delivered by `update()` is covered as well.
3161
+ *
3162
+ * An element anywhere in the page carrying `dialog-target` set to the
3163
+ * element's id calls `open()` on click.
1048
3164
  */
1049
3165
  declare class Drawer extends ParsedElement {
1050
3166
  #private;
1051
3167
  static attributes: string[];
1052
3168
  static slots: boolean;
1053
3169
  static template: string;
3170
+ /**
3171
+ * @param {{ slots: Record<string, DocumentFragment> }} c
3172
+ */
1054
3173
  render({ slots }: {
1055
- slots: any;
3174
+ slots: Record<string, DocumentFragment>;
1056
3175
  }): void;
1057
- get title(): any;
1058
- set title(v: any);
1059
3176
  /**
1060
- * Opens the drawer under the given title and waits for the callback: a
1061
- * resolved value paints the content section (which is returned), a
1062
- * rejection paints the problems and travels to the caller, and an update
1063
- * superseded by a newer one paints nothing.
3177
+ * The title, read and written as the heading's text.
3178
+ * @type {string}
3179
+ */
3180
+ get header(): string;
3181
+ /** @param {string|null|undefined} v written as text, `''` for null or undefined */
3182
+ set header(v: string | null | undefined);
3183
+ /**
3184
+ * Sets the title, empties and hides the content section, shows the loading
3185
+ * section, opens the drawer if it is closed (putting back one that is
3186
+ * sliding out), and fills the content with what the callback resolves to.
3187
+ * A rejection is shown, as the problems' reasons, in the error section,
3188
+ * which has `role="alert"`, and the content is hidden.
3189
+ *
3190
+ * No `section:requested` is dispatched. When a newer `update()` starts
3191
+ * before the callback settles, this call paints nothing and resolves
3192
+ * `null`, the content section being the newer call's; it still rejects with
3193
+ * the callback's error.
3194
+ * @param {string} header the title, set as text
3195
+ * @param {() => Node|string|Promise<Node|string>} cb producing the content
3196
+ * @returns {Promise<Element|null>} the content section, in the document, or
3197
+ * `null` for a call a newer one overtook
3198
+ * @throws {any} what the callback threw or rejected with
1064
3199
  */
1065
- update(title: any, cb: any): Promise<any>;
3200
+ update(header: string, cb: () => Node | string | Promise<Node | string>): Promise<Element | null>;
1066
3201
  /**
1067
- * Re-fires section:requested on the content, open or closed: the explicit
1068
- * request for a body that wants refreshing. A failed refresh paints its
1069
- * problems, nothing rejects: update() stays the rejecting call.
3202
+ * Dispatches `section:requested` for the content again, as `open()` does,
3203
+ * whether the drawer is open or closed. A failed request is painted into
3204
+ * the content section.
3205
+ * @returns {Promise<any[]|undefined>} the listeners' answers, undefined when
3206
+ * nobody answered or the request failed; it never rejects
1070
3207
  */
1071
3208
  refresh(): Promise<any[] | undefined>;
1072
- open(): void;
1073
- close(): void;
3209
+ /**
3210
+ * Resolves every pending `ask()` and `open()` with the outcome so far,
3211
+ * a dismissal for a drawer still open, so that no caller waits on a drawer
3212
+ * that left the document. The native dialog is not closed and no `close`
3213
+ * event is dispatched.
3214
+ */
3215
+ disconnectedCallback(): void;
3216
+ /**
3217
+ * The same as `ask()`.
3218
+ * @returns {Promise<{ dismissed: boolean, result: string|null, response: any }>}
3219
+ */
3220
+ open(): Promise<{
3221
+ dismissed: boolean;
3222
+ result: string | null;
3223
+ response: any;
3224
+ }>;
3225
+ /**
3226
+ * Shows the drawer as a modal and resolves with how it ends.
3227
+ *
3228
+ * The call that opens the drawer resets the outcome of the previous
3229
+ * opening, hides the error and loading sections, and dispatches
3230
+ * `section:requested` on the element (bubbling, with
3231
+ * `detail: { section, first, name: null, index: null }`, `section` being
3232
+ * the content section and `first` true until a delivery succeeds),
3233
+ * so a listener can fill the content. A failed request is painted into the
3234
+ * content section and does not reject. On a drawer already open nothing is
3235
+ * shown or requested, except that one sliding out stops and stays open,
3236
+ * forgetting the result it was closing with, and the call waits for the
3237
+ * same ending as the calls before it, which is how a caller learns the
3238
+ * outcome of the opening an `update()` made. One the page moved while open,
3239
+ * which is no longer modal, opens anew.
3240
+ * @returns {Promise<{ dismissed: boolean, result: string|null, response: any }>} resolving on
3241
+ * close, or on removal from the document with a dismissal
3242
+ */
3243
+ ask(): Promise<{
3244
+ dismissed: boolean;
3245
+ result: string | null;
3246
+ response: any;
3247
+ }>;
3248
+ /**
3249
+ * Dismisses the drawer as the user would: dispatches the cancelable
3250
+ * `drawer:dismiss` with `detail: { reason }` and, under `guard-dismiss`,
3251
+ * asks before dropping unsaved edits, sliding the drawer out as a
3252
+ * dismissal unless either refused. Does nothing on a drawer that is closed
3253
+ * or already sliding out.
3254
+ * @param {string} reason why it is dismissed, such as `navigation`
3255
+ * @returns {Promise<boolean>} whether the drawer is closed or closing,
3256
+ * false when a listener or the user kept it open
3257
+ */
3258
+ dismiss(reason: string): Promise<boolean>;
3259
+ /**
3260
+ * Slides the drawer out and closes it when the animation ends, setting the
3261
+ * `closing` attribute on the native dialog meanwhile; the `close` event is
3262
+ * dispatched then, after this call has returned. An animation the page
3263
+ * cancels, by hiding the drawer or an ancestor of it, closes it as one
3264
+ * that ended does. Where the user prefers reduced motion, or no animation
3265
+ * runs, it closes at once. A non-empty result answers as the button
3266
+ * carrying that `data-result` would; no result, or an empty one, is a
3267
+ * dismissal. Does nothing on a drawer that is closed or already sliding
3268
+ * out.
3269
+ * @param {string} [result]
3270
+ */
3271
+ close(result?: string): void;
1074
3272
  }
1075
- /** A transient feedback region: each show() stacks a toast that retires on its own timer. */
3273
+ /**
3274
+ * A menu of commands on the native popover platform. The menu is the popover
3275
+ * itself: light dismiss and Escape come from the browser.
3276
+ *
3277
+ * A menu naming an invoker by id through `for` belongs to that element: the
3278
+ * invoker toggles it, and it is anchored to the invoker through
3279
+ * `Anchors.wire`, placed in script where the platform has no css anchor
3280
+ * positioning. A menu without `for` is shared: one menu serves many
3281
+ * invokers, such as the "more" button of every row of a list, and a page
3282
+ * opens it beside the invoker it is for with `showFor(invoker)`.
3283
+ *
3284
+ * The items are the buttons and `a[href]` links written as its direct
3285
+ * children, kept as they are. Activation is the item's own click, which the
3286
+ * menu follows by hiding itself, so a page listens for the click where it
3287
+ * already listens. The menu fires no events of its own.
3288
+ *
3289
+ * The keyboard is the ARIA menu protocol: the focus moves to the
3290
+ * first item as the menu opens, the arrows, Home and End walk the items,
3291
+ * typing searches their text, Enter and Space activate the focused one, and
3292
+ * Escape and a close from inside the menu hand the focus back to the invoker
3293
+ * that opened it.
3294
+ */
3295
+ declare class Menu extends ParsedElement {
3296
+ #private;
3297
+ /** @type {string[]} */
3298
+ static attributes: string[];
3299
+ /** Upgrades the menu, and has a shared one that came back follow the presses on its invoker again. */
3300
+ connectedCallback(): void;
3301
+ /** Stops a shared menu following the presses while it is out of the document. */
3302
+ disconnectedCallback(): void;
3303
+ /**
3304
+ * Gives the menu the `menu` role and an empty `popover` attribute unless
3305
+ * it already has one, and marks the items. With `for`, read here once, it
3306
+ * also gives the invoker `aria-haspopup="menu"`, an `aria-expanded` kept
3307
+ * in step, and the toggle of the menu.
3308
+ * @returns {void}
3309
+ * @throws {Error} when `for` is given and names no element in the document at the render
3310
+ */
3311
+ render(): void;
3312
+ /**
3313
+ * Opens a shared menu, one without `for`, beside `invoker`, or moves it
3314
+ * there when it is open for another one. The menu goes below the invoker,
3315
+ * or above it where there is no room below, and is placed once, as
3316
+ * `Anchors.show` places it. The invoker gets `aria-haspopup="menu"` and an
3317
+ * `aria-expanded` that reads true until the menu closes, and the focus
3318
+ * goes back to it on Escape. A call for the invoker whose press has just
3319
+ * closed the menu does nothing, so a second click on the same button
3320
+ * closes the menu rather than opening it again.
3321
+ * @param {HTMLElement} invoker the element the menu opens for
3322
+ * @returns {void}
3323
+ * @throws {Error} when the menu names its invoker through `for`
3324
+ */
3325
+ showFor(invoker: HTMLElement): void;
3326
+ /**
3327
+ * The commands, in the order the arrows walk them: the menu's direct
3328
+ * `button` and `a[href]` children, minus those carrying a `disabled` or
3329
+ * `hidden` attribute, which keep their role and leave the walk. Reading it
3330
+ * also marks any child added since the render, giving it the `menuitem`
3331
+ * role, unless it has a role already, and a `tabIndex` of -1.
3332
+ * @type {HTMLElement[]}
3333
+ */
3334
+ get items(): HTMLElement[];
3335
+ }
3336
+ export type ToastOptions = {
3337
+ /**
3338
+ * the theme class and
3339
+ * the announcement, `info` when absent or unknown
3340
+ */
3341
+ severity?: 'info' | 'success' | 'warning' | 'error';
3342
+ /**
3343
+ * the milliseconds before the toast retires, the
3344
+ * region's own timeout when absent
3345
+ */
3346
+ timeout?: number;
3347
+ /**
3348
+ * a button labelled
3349
+ * with `label` as text, beside the message
3350
+ */
3351
+ action?: {
3352
+ label: any;
3353
+ onClick?: () => void;
3354
+ };
3355
+ };
3356
+ /**
3357
+ * @typedef {object} ToastOptions
3358
+ * @property {'info'|'success'|'warning'|'error'} [severity] the theme class and
3359
+ * the announcement, `info` when absent or unknown
3360
+ * @property {number} [timeout] the milliseconds before the toast retires, the
3361
+ * region's own timeout when absent
3362
+ * @property {{ label: any, onClick?: () => void }} [action] a button labelled
3363
+ * with `label` as text, beside the message
3364
+ */
3365
+ /**
3366
+ * A region of transient messages, with `role="region"` and a localized
3367
+ * `aria-label`. Each `show()` appends a `ful-toast` that retires on its own
3368
+ * timer, so concurrent toasts stack.
3369
+ *
3370
+ * The region is a manual popover, whatever `popover` the page declared, and
3371
+ * each `show()` shows it again, so it is on top of the top layer when a toast
3372
+ * arrives: above a modal dialog or drawer opened before it. While a modal is
3373
+ * open the page outside it is inert, the region included: its toasts show and
3374
+ * retire on their timers, and their buttons answer once the modal closes.
3375
+ *
3376
+ * `timeout` is the region's default timer in milliseconds, 5000 when absent
3377
+ * or zero.
3378
+ *
3379
+ * A `show-toast` event dispatched on the document, or bubbling to it, shows its
3380
+ * `detail` in every connected region that has rendered, `detail.message` being
3381
+ * the message and the other fields the `ToastOptions`. The document listener is
3382
+ * added once, so a second region shows each toast once, and an event with no
3383
+ * detail is ignored.
3384
+ */
1076
3385
  declare class Toasts extends ParsedElement {
1077
3386
  #private;
1078
3387
  static attributes: string[];
3388
+ /** Upgrades the region, and makes a re-attached one answer `show-toast` again. */
1079
3389
  connectedCallback(): void;
3390
+ /** Stops the region answering `show-toast` while it is out of the document. */
1080
3391
  disconnectedCallback(): void;
1081
3392
  render(): void;
1082
3393
  /**
1083
- * Appends a toast carrying the message (a Failure shows its problems'
1084
- * reasons, one per line), severity picking the theme and the announcement,
1085
- * the toast retiring through its own timer or its dismiss button.
3394
+ * Appends a `ful-toast` holding the message as text, a `Failure` showing
3395
+ * its problems' reasons one per line and anything else its string form
3396
+ * (`''` for null or undefined). The toast carries the severity as a class
3397
+ * and has `role="alert"` for `error` and `role="status"` otherwise.
3398
+ *
3399
+ * The toast retires when its timer runs out, when its localized dismiss
3400
+ * button is clicked, or when its action button is clicked, after
3401
+ * `action.onClick` runs. The timer pauses while the pointer is over the
3402
+ * toast or the focus is inside it, and resumes with the time that was left
3403
+ * once both have gone. A retiring toast holding the focus moves it to the
3404
+ * region, which has `tabindex="-1"` for this. The toast is removed once its
3405
+ * `ful-toast-out` animation ends, or at once where the user prefers reduced
3406
+ * motion or no animation runs.
3407
+ *
3408
+ * A connected region is shown again as a popover, which puts it above
3409
+ * whatever opened in the top layer since; the focus stays where it was.
1086
3410
  * @param {any} message
1087
- * @param {any} [options] severity and timeout
1088
- * @returns {HTMLElement}
3411
+ * @param {ToastOptions} [options]
3412
+ * @returns {HTMLElement} the toast, already in the region
1089
3413
  */
1090
- show(message: any, options?: any): HTMLElement;
3414
+ show(message: any, options?: ToastOptions): HTMLElement;
1091
3415
  }
1092
3416
  /**
1093
3417
  * A tab panel: one visible panel at a time, announced through the tab pattern
1094
- * (a tablist of tab buttons, each panel a tabpanel named by its tab). The tabs
1095
- * are declared as <tab> elements in the tabs slot, the panels as the slotless
1096
- * children, paired in order. Entering a panel fires the section:requested
1097
- * family on it (generic and #index, panels being nameless) and awaits the
1098
- * answers, so a panel can deliver itself asynchronously.
3418
+ * (a tablist of tab buttons, each panel a tabpanel labelled by its tab). The
3419
+ * tabs are declared as `<tab>` elements in the `tabs` slot, the panels as the
3420
+ * slotless children, paired in order. Each `<tab>` becomes a `button` with the
3421
+ * `tab` role carrying the tab's content, inside a `ful-tablist` with the
3422
+ * `tablist` role. A panel that already has an id keeps it and its tab points at
3423
+ * it; a panel without one gets a generated id. Every panel carries
3424
+ * `tabindex="0"`, so the visible one follows its tab in the tab order and the
3425
+ * hidden ones stay out of it.
3426
+ *
3427
+ * The tabs use a roving tabindex and automatic activation: a click activates
3428
+ * its tab, and on the tablist ArrowRight and ArrowLeft move to the next and
3429
+ * previous tab, wrapping around, while Home and End jump to the first and the
3430
+ * last, moving the focus with the activation.
3431
+ *
3432
+ * Entering a panel fires `section:requested` and `section:requested:#<index>`
3433
+ * on the host, bubbling, with detail `{ name: null, section, index, first }`,
3434
+ * and awaits the answers registered through `AsyncEvents.asyncOn`, so a panel
3435
+ * can deliver itself asynchronously: see `SectionRequests`.
3436
+ *
3437
+ * The tablist stays on one line. When the tabs do not fit it scrolls sideways,
3438
+ * and a previous and a next button appear at the side that has more tabs,
3439
+ * each bringing the next hidden tab into view. They stay out of the tab order:
3440
+ * the keyboard moves through the tabs with the arrow keys. The active tab is
3441
+ * kept in view when it changes and when the tablist changes width.
1099
3442
  */
1100
3443
  declare class Tabs extends ParsedElement {
1101
3444
  #private;
1102
3445
  static slots: boolean;
1103
3446
  static observed: string[];
1104
3447
  static template: string;
3448
+ /**
3449
+ * Pairs the declared tabs with the panels in order. When the counts
3450
+ * differ, the unpaired tabs and panels are left in the dom as they are and
3451
+ * a warning is logged.
3452
+ * @param {{ slots: Record<string, DocumentFragment> }} conf
3453
+ * @returns {void}
3454
+ */
1105
3455
  render({ slots }: {
1106
- slots: any;
3456
+ slots: Record<string, DocumentFragment>;
1107
3457
  }): void;
3458
+ /**
3459
+ * The index of the visible panel, reflected to the `active` attribute.
3460
+ * Writing it clamps the value to the paired tabs, a value that is not a
3461
+ * number counting as 0, and selects that tab and shows its panel. When the
3462
+ * index changes on a rendered element it dispatches `tabs:change` on the
3463
+ * host (not bubbling) with detail `{ active, previous }`, then fires the
3464
+ * section requests for the entered panel. The first write, which applies
3465
+ * the attribute after the render, fires the requests without the event.
3466
+ * A failed delivery paints the panel's error and rejects nowhere.
3467
+ * @type {number}
3468
+ */
1108
3469
  get active(): number;
1109
3470
  /**
1110
- * Re-fires the section:requested family on the panel (by index or the
1111
- * panel element itself), whether active or not: the explicit request for a
1112
- * content that wants refreshing. A failed refresh paints its problems,
1113
- * nothing rejects: there is no caller to reject towards.
3471
+ * Fires the section requests again for a panel, whether it is visible or
3472
+ * not, with `first` false once an earlier delivery succeeded.
3473
+ * @param {number | Element} ref the panel's index, or the panel itself
3474
+ * @returns {Promise<any[] | undefined> | undefined} a promise resolving to
3475
+ * the answers, or to undefined when nobody listened or the delivery
3476
+ * failed (its error painted in the panel); undefined, with a warning, when
3477
+ * `ref` names no panel
1114
3478
  */
1115
- refresh(ref: any): Promise<any[] | undefined> | undefined;
3479
+ refresh(ref: number | Element): Promise<any[] | undefined> | undefined;
1116
3480
  set active(v: number);
1117
3481
  }
1118
3482
  /**
1119
- * An accordion over native details/summary disclosures: the platform carries
1120
- * the semantics, the keyboard and the toggling, the chrome paints the group.
1121
- * With the exclusive claim the render assigns one shared name to every panel,
1122
- * which is the platform's own exclusive grouping: opening one closes the others.
3483
+ * An accordion over native `details`/`summary` disclosures: the browser
3484
+ * provides the semantics, the keyboard and the toggling, and the element adds
3485
+ * the group's styling. The `details` written as its children are rendered,
3486
+ * unchanged, inside a `ful-accordion-group`.
1123
3487
  */
1124
3488
  declare class Accordion extends ParsedElement {
1125
3489
  #private;
1126
3490
  static slots: boolean;
3491
+ /** @type {string[]} */
1127
3492
  static observed: string[];
1128
3493
  static template: string;
3494
+ /**
3495
+ * @param {{ slots: Record<string, DocumentFragment> | undefined }} c
3496
+ * @returns {void}
3497
+ */
1129
3498
  render({ slots }: {
1130
- slots: any;
3499
+ slots: Record<string, DocumentFragment> | undefined;
1131
3500
  }): void;
3501
+ /**
3502
+ * Whether opening one panel closes the others, reflected to the
3503
+ * `exclusive` attribute; only `true` turns it on. Setting it gives the
3504
+ * group's direct `details` children one shared, newly generated `name`,
3505
+ * the platform's exclusive grouping, or removes their `name` when off.
3506
+ * A `details` added to the group afterwards is not named until the next
3507
+ * write.
3508
+ * @type {boolean}
3509
+ */
1132
3510
  get exclusive(): boolean;
1133
3511
  set exclusive(v: boolean);
1134
3512
  }
1135
3513
  /**
1136
- * A wizard: a progress of steps over one-of-N sections, the homeinsurance
1137
- * layout distilled. The steps are declared as <step> elements in the steps
1138
- * slot, the sections as the slotless children, paired in order; each section
1139
- * may carry a data-step name, which is what move() answers to. The current
1140
- * step is the aria-current=step claim, carried in lockstep by the step and
1141
- * its section: the chrome (including which section is shown) follows the
1142
- * claim alone, so the markup state and the style can never disagree. The
1143
- * progress chrome shows the current step alone by default; the progress
1144
- * attribute picks another shape over the same claims (timeline, dots, none).
1145
- * Entering a section
1146
- * fires the section:requested family on it and awaits the answers, so a
1147
- * section can deliver itself asynchronously; move() resolves when the entered
1148
- * section is painted, and rejects when its delivery fails.
3514
+ * A wizard: a progress of steps over one-of-N sections. The steps are declared
3515
+ * as `<step>` elements in the `steps` slot, the sections as the slotless
3516
+ * children, paired in order; each section may carry a `data-step` name, which
3517
+ * is what `move()` and `refresh()` answer to. The steps become the items of an
3518
+ * `ol` inside `ful-steps`, labelled by the localized `wizard.progress`.
3519
+ *
3520
+ * The current step is the `aria-current="step"` claim, carried by the step and
3521
+ * its section together, and the stylesheet hides every `section` child that
3522
+ * does not carry it. A section that carries the claim in the markup when the
3523
+ * wizard renders stays current; otherwise the first section is. Every paired
3524
+ * section gets `tabindex="-1"`, so a move can focus it.
3525
+ *
3526
+ * Entering a section, the first one at the render included, fires
3527
+ * `section:requested`, `section:requested:#<index>` and, when the section has a
3528
+ * `data-step`, `section:requested:<name>` on the host, bubbling, with detail
3529
+ * `{ name, section, index, first }`, and awaits the answers registered through
3530
+ * `AsyncEvents.asyncOn`: see `SectionRequests`. A failure of the initial entry
3531
+ * paints the section's error and rejects nowhere.
1149
3532
  */
1150
3533
  declare class Wizard extends ParsedElement {
1151
3534
  #private;
1152
3535
  static slots: boolean;
1153
3536
  static observed: string[];
1154
3537
  static template: string;
3538
+ /**
3539
+ * Pairs the declared steps with the sections in order. When the counts
3540
+ * differ, the unpaired steps and sections are left in the dom as they are
3541
+ * and a warning is logged.
3542
+ * @param {{ slots: Record<string, DocumentFragment> }} conf
3543
+ * @returns {void}
3544
+ */
1155
3545
  render({ slots }: {
1156
- slots: any;
3546
+ slots: Record<string, DocumentFragment>;
1157
3547
  }): void;
3548
+ /**
3549
+ * The zero-based position of the current step.
3550
+ * @type {number}
3551
+ */
1158
3552
  get index(): number;
1159
- get step(): any;
1160
- get progress(): any;
1161
- set progress(v: any);
3553
+ /**
3554
+ * The `data-step` of the current section, or null when it has none or no
3555
+ * section is paired.
3556
+ * @type {string | null}
3557
+ */
3558
+ get step(): string | null;
3559
+ /**
3560
+ * The shape of the progress chrome, reflected to the `progress` attribute:
3561
+ * unset shows the current step alone, `timeline` every step as done,
3562
+ * current or future, `dots` one dot per step, `none` no progress at all.
3563
+ * The value only drives the stylesheet; the moves are the same in every
3564
+ * shape.
3565
+ * @type {string | null}
3566
+ */
3567
+ get progress(): string | null;
3568
+ set progress(v: string | null);
3569
+ /**
3570
+ * Moves to the following step; at the last step nothing happens.
3571
+ * @returns {Promise<any[] | undefined> | undefined} what `move()` answers
3572
+ * @throws what the entered section's delivery rejected with, as the rejection
3573
+ * of the returned promise
3574
+ */
1162
3575
  next(): Promise<any[] | undefined> | undefined;
3576
+ /**
3577
+ * Moves to the preceding step; at the first step nothing happens.
3578
+ * @returns {Promise<any[] | undefined> | undefined} what `move()` answers
3579
+ * @throws what the entered section's delivery rejected with, as the rejection
3580
+ * of the returned promise
3581
+ */
1163
3582
  prev(): Promise<any[] | undefined> | undefined;
1164
- move(ref: any): Promise<any[] | undefined> | undefined;
1165
3583
  /**
1166
- * Re-fires the section:requested family on the named section (or the
1167
- * section element itself), whether active or not: the explicit request for a
1168
- * content that wants refreshing. A failed refresh paints its problems,
1169
- * nothing rejects: move() stays the rejecting call.
3584
+ * Moves to the section carrying `data-step="<ref>"`. A move makes that step
3585
+ * and section current, moves the focus to the section, dispatches
3586
+ * `wizard:change` on the host (not bubbling) with detail `{ index, step }`,
3587
+ * then fires the section requests for it.
3588
+ * @param {string} ref the section's `data-step`
3589
+ * @returns {Promise<any[] | undefined> | undefined} a promise resolving to
3590
+ * the answers once they are delivered, or to undefined when nobody
3591
+ * listened; undefined when the section is already current, and undefined
3592
+ * with a warning when no section carries `ref` or the one carrying it has
3593
+ * no step paired with it
3594
+ * @throws what the entered section's delivery rejected with, as the rejection
3595
+ * of the returned promise, after its error is painted in the section
1170
3596
  */
1171
- refresh(ref: any): Promise<any[] | undefined> | undefined;
3597
+ move(ref: string): Promise<any[] | undefined> | undefined;
3598
+ /**
3599
+ * Fires the section requests again for a section, whether it is current or
3600
+ * not, with `first` false once an earlier delivery succeeded.
3601
+ * @param {string | Element} ref the section's `data-step`, or the section
3602
+ * itself
3603
+ * @returns {Promise<any[] | undefined> | undefined} a promise resolving to
3604
+ * the answers, or to undefined when nobody listened or the delivery failed
3605
+ * (its error painted in the section); undefined, with a warning, when `ref`
3606
+ * names no section or one with no step paired with it
3607
+ */
3608
+ refresh(ref: string | Element): Promise<any[] | undefined> | undefined;
1172
3609
  }
1173
3610
  /**
1174
3611
  * Registers everything ful provides on a registry: the elements, the loader
1175
3612
  * components, an http client, and the translations for the configured
1176
- * language. A page calls `registry.plugin(new Plugin({…})).configure()` once.
3613
+ * language. A page calls `registry.plugin(new Plugin({…})).configure()` once;
3614
+ * `registry.plugin` calls this plugin's `configure`.
1177
3615
  */
1178
3616
  declare class Plugin {
1179
3617
  #private;
1180
3618
  /**
1181
- * @param {{ language?: string, translations?: Record<string, any>, httpClient?: any }} [options]
3619
+ * @param {{ language?: string, translations?: Record<string, any>, httpClient?: HttpClient }} [options]
1182
3620
  * `language` is fixed for the page: a full BCP-47 tag or a primary subtag,
1183
- * defaulting to the browser's language. `translations` is a flat
1184
- * active-language map applied over the built-in translations: reword built-in
3621
+ * defaulting to `navigator.language`, then to 'en', an empty one included.
3622
+ * It is canonicalized (`IT-it` and `it_IT` both read `it-IT`), and one that
3623
+ * is no language tag at all throws here, rather than at the first message
3624
+ * or number formatted in it. Its primary subtag picks the built-in
3625
+ * translations (en, it, es, fr); the messages
3626
+ * are the english ones, overridden by that language's, overridden by
3627
+ * `translations`, so a language without built-in translations reads in
3628
+ * english. `translations` is a flat map of keys to messages: reword built-in
1185
3629
  * keys ('pagination.showing', …) or add your own ('checkout.total', …).
1186
3630
  * `httpClient` is the client every ful component fetches through, registered
1187
- * as the `http-client` component: where an unauthorized session goes is an
1188
- * application decision, so a page that does not want the default's redirect
1189
- * to '/' builds its own.
3631
+ * as the `http-client` component; without one, `configure` builds a client
3632
+ * that sends the csrf token and navigates to '/' on a 401 response.
3633
+ * @throws {RangeError} when `language` is not a language tag
1190
3634
  */
1191
3635
  constructor(options?: {
1192
3636
  language?: string;
1193
3637
  translations?: Record<string, any>;
1194
- httpClient?: any;
3638
+ httpClient?: HttpClient;
1195
3639
  });
1196
- configure(registry: any): void;
3640
+ /**
3641
+ * Defines on `registry` the `l10n` module, the `http-client` component,
3642
+ * every `ful-*` element, the `loaders:select`, `loaders:form` and
3643
+ * `loaders:table` components, and an overlay publishing `l10n` (the merged
3644
+ * messages) and `locale` (the `language`, canonicalized) to every template.
3645
+ * @param {{ defineModule(name: string, module: any): any, defineComponent(name: string, component: any): any, defineElement(tag: string, klass: CustomElementConstructor): any, defineOverlay(...data: any[]): any }} registry
3646
+ * the ftl `Registry`, each of those methods answering the registry itself
3647
+ * @returns {void}
3648
+ */
3649
+ configure(registry: {
3650
+ defineModule(name: string, module: any): any;
3651
+ defineComponent(name: string, component: any): any;
3652
+ defineElement(tag: string, klass: CustomElementConstructor): any;
3653
+ defineOverlay(...data: any[]): any;
3654
+ }): void;
3655
+ }
3656
+ export { Accordion, Anchors, AsyncEvents, Bindings, BooleanFilter, Checkbox, Claims, CompareFilter, Dialog, Drawer, Dropdown, Field, Form, FormLoader, InFilter, Input, InputFile, InputInstant, InputLocalDate, InputLocalTime, Instant, InstantFilter, LocalDate, LocalDateFilter, LocalStorage, Menu, NumberFilter, PageLeave, Pagination, Plugin, RadioGroup, Select, SelectLoader, SessionStorage, SortButton, Table, TableLoader, Tabs, TextFilter, Timing, Toasts, Tooltip, VersionedLocalStorage, VersionedSessionStorage, Wizard, describable };
3657
+
3658
+ declare global {
3659
+ interface HTMLElementTagNameMap {
3660
+ 'ful-tooltip': Tooltip;
3661
+ 'ful-dialog': Dialog;
3662
+ 'ful-drawer': Drawer;
3663
+ 'ful-menu': Menu;
3664
+ 'ful-toasts': Toasts;
3665
+ 'ful-tabs': Tabs;
3666
+ 'ful-accordion': Accordion;
3667
+ 'ful-wizard': Wizard;
3668
+ 'ful-form': Form;
3669
+ 'ful-checkbox': Checkbox;
3670
+ 'ful-input': Input;
3671
+ 'ful-input-file': InputFile;
3672
+ 'ful-local-date': LocalDate;
3673
+ 'ful-instant': Instant;
3674
+ 'ful-input-local-date': InputLocalDate;
3675
+ 'ful-input-local-time': InputLocalTime;
3676
+ 'ful-input-instant': InputInstant;
3677
+ 'ful-radio-group': RadioGroup;
3678
+ 'ful-table': Table;
3679
+ 'ful-pagination': Pagination;
3680
+ 'ful-sorter': SortButton;
3681
+ 'ful-filter-instant': InstantFilter;
3682
+ 'ful-filter-local-date': LocalDateFilter;
3683
+ 'ful-filter-number': NumberFilter;
3684
+ 'ful-filter-boolean': BooleanFilter;
3685
+ 'ful-filter-text': TextFilter;
3686
+ 'ful-filter-in': InFilter;
3687
+ 'ful-select': Select;
3688
+ 'ful-dropdown': Dropdown;
3689
+ }
1197
3690
  }
1198
- export { Accordion, Anchors, AsyncEvents, Bindings, BooleanFilter, Checkbox, Claims, CompareFilter, Dialog, Drawer, Dropdown, Field, Form, FormLoader, Input, InputFile, InputInstant, InputLocalDate, InputLocalTime, Instant, InstantFilter, LocalDate, LocalDateFilter, LocalStorage, NumberFilter, Pagination, Plugin, RadioGroup, Select, SelectLoader, SessionStorage, SortButton, Table, TableLoader, TableSchemaParser, Tabs, TextFilter, Timing, Toasts, Tooltip, VersionedLocalStorage, VersionedSessionStorage, Wizard };
1199
3691
 
1200
3692
  export as namespace ful;