@optionfactory/fml 9.0.0-rc3 → 9.0.0-rc30

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