@optionfactory/fml 9.0.0-rc2 → 9.0.0-rc21

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