@optionfactory/fml 9.0.0-rc3 → 9.0.0-rc5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ful.d.mts CHANGED
@@ -102,6 +102,41 @@ declare class Claims {
102
102
  /** Supersedes every claim without holding a new one. */
103
103
  invalidate(): void;
104
104
  }
105
+ export type Describable = {
106
+ describedBy(el: HTMLElement): boolean;
107
+ };
108
+ /**
109
+ * The protocol by which content standing inside a field becomes part of the
110
+ * accessible description of that field's control.
111
+ *
112
+ * A field owns its control's `aria-describedby`: it is the only thing that
113
+ * knows which element the description belongs on, and it already writes the
114
+ * entry for its own error region. Content the author slotted into the field
115
+ * cannot write that attribute itself without becoming a second owner of it, and
116
+ * it cannot be wired by the field either, because a slotted custom element
117
+ * renders after the field has mounted and has nothing to point at when the
118
+ * field looks.
119
+ *
120
+ * So the content asks, once it has something to offer. `describable(el)`
121
+ * answers the nearest ancestor that accepts a description, and the caller hands
122
+ * its element to that ancestor's `describedBy`, which answers whether it was
123
+ * taken. Nothing here names a field or a tooltip: the relation is expressed as
124
+ * a capability, so the two ends need not import each other, which matters
125
+ * because the library's own arrow runs from the forms to the disclosures.
126
+ *
127
+ * The lookup lives here rather than at its one call site so the protocol has a
128
+ * name, a place to be documented and a single definition to change.
129
+ */
130
+ /**
131
+ * @typedef {{ describedBy(el: HTMLElement): boolean }} Describable
132
+ */
133
+ /**
134
+ * The nearest ancestor of `el` that accepts elements into the description of
135
+ * whatever it considers its control, or null when nothing in the ancestry does.
136
+ * @param {Element} el
137
+ * @returns {(Element & Describable) | null}
138
+ */
139
+ declare const describable: (el: Element) => (Element & Describable) | null;
105
140
  /**
106
141
  * Sleeping, debouncing and throttling. Debounce and throttle both return the
107
142
  * wrapped function together with a cancel function.
@@ -226,6 +261,34 @@ declare class Field extends ParsedElement {
226
261
  /** the role the element internals carry, 'presentation' unless the control is its own */
227
262
  static ROLE: string;
228
263
  constructor();
264
+ /**
265
+ * Adds an element to the accessible description of the field's control and
266
+ * answers whether the field took it.
267
+ *
268
+ * A field takes one whenever it is offered, before its own render as
269
+ * readily as after: content slotted into a field is a custom element of its
270
+ * own and may upgrade on either side of the field it stands in, which
271
+ * happens in both directions in practice, a tooltip beating an async select
272
+ * to its render while losing to a plain input. A description handed over
273
+ * early waits here and is written the moment the field has somewhere to
274
+ * write it, so the caller never has to know the order.
275
+ *
276
+ * The reference lands on the element handed over rather than on a wrapper
277
+ * around it: a hidden element is included in a description only where it is
278
+ * named directly, and content that reaches the description through a
279
+ * wrapper is skipped while it is hidden. A popover closed until someone
280
+ * opens it is exactly that, so the caller passes the popover itself.
281
+ *
282
+ * An attribute rather than `ariaDescribedByElements`: the property reflects
283
+ * to nothing, so the description would live in the accessibility tree alone
284
+ * and vanish entirely on a browser without aria element reflection.
285
+ *
286
+ * This is the field's half of the description protocol; `describable` in
287
+ * `ful/descriptions.mjs` is the half the content uses to find the field.
288
+ * @param {HTMLElement} el
289
+ * @returns {boolean}
290
+ */
291
+ describedBy(el: HTMLElement): boolean;
229
292
  focus(options: any): void;
230
293
  /**
231
294
  * Clears or reports one validation problem: the text lands on the field's
@@ -336,8 +399,9 @@ declare class Field extends ParsedElement {
336
399
  * three claims reach it
337
400
  * - `error` is the field's live region
338
401
  * - `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
402
+ * - `described` moves the description off the control and onto another
403
+ * element, the host where no single control can carry it: the error
404
+ * region and anything `describedBy` is later handed both land there
341
405
  * - `claims` moves the three claims onto a wrapper the field disables as a
342
406
  * whole, leaving focus and aria on the control
343
407
  * - `announces` is the element whose role carries `aria-readonly` and
@@ -1005,8 +1069,24 @@ declare class BooleanFilter extends Field {
1005
1069
  get disabled(): boolean;
1006
1070
  set disabled(d: boolean);
1007
1071
  }
1008
- /** An info icon button toggling a popover with a short explanation. */
1072
+ /**
1073
+ * An info icon button toggling a popover with a short explanation.
1074
+ *
1075
+ * The marker is the page's `config.icon`, and the `icon` attribute names a
1076
+ * `ful-icon` for the tooltip that means something other than plain information:
1077
+ * a caveat, a warning, a setting. A name the library does not paint is the
1078
+ * page's own, declared as `ful-icon[name='...'] { mask-image: ... }`.
1079
+ *
1080
+ * `describes` is for the tooltip standing in a field: the note becomes part of
1081
+ * the accessible description of that field's control, so it is announced on
1082
+ * reaching the field rather than only on opening the marker, and the marker
1083
+ * leaves the tab order, so a form of hinted fields costs no extra keystrokes to
1084
+ * walk. The marker stays clickable, and stays a tab stop wherever the note was
1085
+ * not taken, a tooltip claiming `describes` outside a field among them: the
1086
+ * stop only goes where something else delivers the content.
1087
+ */
1009
1088
  declare class Tooltip extends ParsedElement {
1089
+ #private;
1010
1090
  static slots: boolean;
1011
1091
  static attributes: string[];
1012
1092
  static config: {
@@ -1017,7 +1097,26 @@ declare class Tooltip extends ParsedElement {
1017
1097
  slots: any;
1018
1098
  }): void;
1019
1099
  }
1020
- /** A modal dialog on the native platform, open()/ask() resolving with the closer's data-result. */
1100
+ /**
1101
+ * A modal dialog on the native platform, open()/ask() resolving with the
1102
+ * closer's data-result.
1103
+ *
1104
+ * The header carries a close button, as the drawer's does: Escape dismisses a
1105
+ * modal on its own, but nothing says so, and a dialog whose only exit is a key
1106
+ * you have to know about leaves a pointer with nowhere to go. It answers the way
1107
+ * Escape does, with null.
1108
+ *
1109
+ * `requires-answer` is for the dialog that must be answered: the close button is not
1110
+ * rendered and Escape is refused, so the only way out is a button that carries a
1111
+ * result. It has to be both, a close button withheld while Escape still worked
1112
+ * being decoration rather than a rule.
1113
+ *
1114
+ * The chrome is reachable by class as well as by tag, so a plain `<dialog
1115
+ * class="ful-dialog">` written by a page gets the same look whatever its
1116
+ * structure: the tag form matches a direct child, and `ful-dialog-header`,
1117
+ * `ful-dialog-body` and `ful-dialog-footer` match at any depth, which is what a
1118
+ * dialog whose content is wrapped in a form needs.
1119
+ */
1021
1120
  declare class Dialog extends ParsedElement {
1022
1121
  #private;
1023
1122
  static attributes: string[];
@@ -1195,6 +1294,6 @@ declare class Plugin {
1195
1294
  });
1196
1295
  configure(registry: any): void;
1197
1296
  }
1198
- export { Accordion, Anchors, AsyncEvents, Bindings, BooleanFilter, Checkbox, Claims, CompareFilter, Dialog, Drawer, Dropdown, Field, Form, FormLoader, Input, InputFile, InputInstant, InputLocalDate, InputLocalTime, Instant, InstantFilter, LocalDate, LocalDateFilter, LocalStorage, NumberFilter, Pagination, Plugin, RadioGroup, Select, SelectLoader, SessionStorage, SortButton, Table, TableLoader, TableSchemaParser, Tabs, TextFilter, Timing, Toasts, Tooltip, VersionedLocalStorage, VersionedSessionStorage, Wizard };
1297
+ export { Accordion, Anchors, AsyncEvents, Bindings, BooleanFilter, Checkbox, Claims, CompareFilter, Dialog, Drawer, Dropdown, Field, Form, FormLoader, Input, InputFile, InputInstant, InputLocalDate, InputLocalTime, Instant, InstantFilter, LocalDate, LocalDateFilter, LocalStorage, NumberFilter, Pagination, Plugin, RadioGroup, Select, SelectLoader, SessionStorage, SortButton, Table, TableLoader, TableSchemaParser, Tabs, TextFilter, Timing, Toasts, Tooltip, VersionedLocalStorage, VersionedSessionStorage, Wizard, describable };
1199
1298
 
1200
1299
  export as namespace ful;
package/dist/ful.iife.js CHANGED
@@ -233,6 +233,48 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
233
233
  }
234
234
  }
235
235
 
236
+ /**
237
+ * The protocol by which content standing inside a field becomes part of the
238
+ * accessible description of that field's control.
239
+ *
240
+ * A field owns its control's `aria-describedby`: it is the only thing that
241
+ * knows which element the description belongs on, and it already writes the
242
+ * entry for its own error region. Content the author slotted into the field
243
+ * cannot write that attribute itself without becoming a second owner of it, and
244
+ * it cannot be wired by the field either, because a slotted custom element
245
+ * renders after the field has mounted and has nothing to point at when the
246
+ * field looks.
247
+ *
248
+ * So the content asks, once it has something to offer. `describable(el)`
249
+ * answers the nearest ancestor that accepts a description, and the caller hands
250
+ * its element to that ancestor's `describedBy`, which answers whether it was
251
+ * taken. Nothing here names a field or a tooltip: the relation is expressed as
252
+ * a capability, so the two ends need not import each other, which matters
253
+ * because the library's own arrow runs from the forms to the disclosures.
254
+ *
255
+ * The lookup lives here rather than at its one call site so the protocol has a
256
+ * name, a place to be documented and a single definition to change.
257
+ */
258
+
259
+ /**
260
+ * @typedef {{ describedBy(el: HTMLElement): boolean }} Describable
261
+ */
262
+
263
+ /**
264
+ * The nearest ancestor of `el` that accepts elements into the description of
265
+ * whatever it considers its control, or null when nothing in the ancestry does.
266
+ * @param {Element} el
267
+ * @returns {(Element & Describable) | null}
268
+ */
269
+ const describable = (el) => {
270
+ for (let at = el.parentElement; at; at = at.parentElement) {
271
+ if (typeof (/** @type {any} */ (at).describedBy) === 'function') {
272
+ return /** @type {any} */ (at);
273
+ }
274
+ }
275
+ return null;
276
+ };
277
+
236
278
  /**
237
279
  * Sleeping, debouncing and throttling. Debounce and throttle both return the
238
280
  * wrapped function together with a cancel function.
@@ -627,6 +669,9 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
627
669
  /** the role the element internals carry, 'presentation' unless the control is its own */
628
670
  static ROLE = 'presentation';
629
671
  #control;
672
+ #described;
673
+ #descriptions = [];
674
+ #errorId = null;
630
675
  #fieldError;
631
676
  #claims;
632
677
  #announces;
@@ -678,17 +723,16 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
678
723
  true,
679
724
  );
680
725
  }
681
- //the error region describes the control, or the host where there is no
726
+ //the description lands on the control, or on the host where there is no
682
727
  //single control to describe (a radio group's legend names its fieldset)
728
+ this.#described = described ?? control;
683
729
  if (error) {
684
- //an attribute, not the aria element property: the property reflects to
685
- //nothing, so the description lived in the accessibility tree alone and
686
- //vanished entirely on a browser without aria element reflection
687
- if (!error.id) {
688
- error.id = index_mjs.Attributes.uid('ful-field-error');
689
- }
690
- (described ?? control).setAttribute('aria-describedby', error.id);
730
+ //named for what it is, the generic id being for whoever brings no name
731
+ error.id = error.id || index_mjs.Attributes.uid('ful-field-error');
732
+ this.#errorId = error.id;
691
733
  }
734
+ //anything handed over before the field had a target lands here
735
+ this.#describe();
692
736
  if (label) {
693
737
  Field.#name(this, label, control);
694
738
  }
@@ -723,6 +767,61 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
723
767
  static #submitsOnEnter(el) {
724
768
  return el instanceof HTMLInputElement && !['file', 'button', 'submit', 'reset', 'image'].includes(el.type);
725
769
  }
770
+ /**
771
+ * Adds an element to the accessible description of the field's control and
772
+ * answers whether the field took it.
773
+ *
774
+ * A field takes one whenever it is offered, before its own render as
775
+ * readily as after: content slotted into a field is a custom element of its
776
+ * own and may upgrade on either side of the field it stands in, which
777
+ * happens in both directions in practice, a tooltip beating an async select
778
+ * to its render while losing to a plain input. A description handed over
779
+ * early waits here and is written the moment the field has somewhere to
780
+ * write it, so the caller never has to know the order.
781
+ *
782
+ * The reference lands on the element handed over rather than on a wrapper
783
+ * around it: a hidden element is included in a description only where it is
784
+ * named directly, and content that reaches the description through a
785
+ * wrapper is skipped while it is hidden. A popover closed until someone
786
+ * opens it is exactly that, so the caller passes the popover itself.
787
+ *
788
+ * An attribute rather than `ariaDescribedByElements`: the property reflects
789
+ * to nothing, so the description would live in the accessibility tree alone
790
+ * and vanish entirely on a browser without aria element reflection.
791
+ *
792
+ * This is the field's half of the description protocol; `describable` in
793
+ * `ful/descriptions.mjs` is the half the content uses to find the field.
794
+ * @param {HTMLElement} el
795
+ * @returns {boolean}
796
+ */
797
+ describedBy(el) {
798
+ if (!el) {
799
+ return false;
800
+ }
801
+ if (!el.id) {
802
+ el.id = index_mjs.Attributes.uid('ful-described');
803
+ }
804
+ if (!this.#descriptions.includes(el.id)) {
805
+ this.#descriptions.push(el.id);
806
+ }
807
+ this.#describe();
808
+ return true;
809
+ }
810
+ /**
811
+ * Writes the description the field has collected, the error region last:
812
+ * the standing explanations are what the field always says, the problem is
813
+ * the news. The field owns the attribute outright rather than appending to
814
+ * whatever is there, so the order does not depend on who arrived when.
815
+ */
816
+ #describe() {
817
+ if (!this.#described) {
818
+ return;
819
+ }
820
+ const ids = [...this.#descriptions, this.#errorId].filter((id) => id);
821
+ if (ids.length) {
822
+ this.#described.setAttribute('aria-describedby', ids.join(' '));
823
+ }
824
+ }
726
825
  focus(options) {
727
826
  this.#control?.focus(options);
728
827
  }
@@ -959,8 +1058,9 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
959
1058
  * three claims reach it
960
1059
  * - `error` is the field's live region
961
1060
  * - `label`, when given, names the control and focuses it on click
962
- * - `described` moves the error's description off the control and onto
963
- * another element, the host where no single control can carry it
1061
+ * - `described` moves the description off the control and onto another
1062
+ * element, the host where no single control can carry it: the error
1063
+ * region and anything `describedBy` is later handed both land there
964
1064
  * - `claims` moves the three claims onto a wrapper the field disables as a
965
1065
  * whole, leaving focus and aria on the control
966
1066
  * - `announces` is the element whose role carries `aria-readonly` and
@@ -4690,42 +4790,97 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
4690
4790
  });
4691
4791
  };
4692
4792
 
4693
- /** An info icon button toggling a popover with a short explanation. */
4793
+ /**
4794
+ * An info icon button toggling a popover with a short explanation.
4795
+ *
4796
+ * The marker is the page's `config.icon`, and the `icon` attribute names a
4797
+ * `ful-icon` for the tooltip that means something other than plain information:
4798
+ * a caveat, a warning, a setting. A name the library does not paint is the
4799
+ * page's own, declared as `ful-icon[name='...'] { mask-image: ... }`.
4800
+ *
4801
+ * `describes` is for the tooltip standing in a field: the note becomes part of
4802
+ * the accessible description of that field's control, so it is announced on
4803
+ * reaching the field rather than only on opening the marker, and the marker
4804
+ * leaves the tab order, so a form of hinted fields costs no extra keystrokes to
4805
+ * walk. The marker stays clickable, and stays a tab stop wherever the note was
4806
+ * not taken, a tooltip claiming `describes` outside a field among them: the
4807
+ * stop only goes where something else delivers the content.
4808
+ */
4694
4809
  class Tooltip extends index_mjs.ParsedElement {
4695
4810
  static slots = true;
4696
- static attributes = ['placement'];
4811
+ static attributes = ['placement', 'icon', 'describes:presence'];
4697
4812
  static config = {
4698
4813
  icon: 'info-circle-fill',
4699
4814
  };
4700
4815
  static template = `
4701
- <button type="button" class="ful-tip" data-ref="trigger" data-tpl-aria-label="#l10n:t('info.tooltip')"><ful-icon data-tpl-name="config.icon" aria-hidden="true"></ful-icon></button>
4816
+ <button type="button" class="ful-tip" data-ref="trigger" data-tpl-aria-label="#l10n:t('info.tooltip')"><ful-icon data-tpl-name="icon ?? config.icon" aria-hidden="true"></ful-icon></button>
4702
4817
  <ful-note popover data-ref="content">{{{{ slots.default }}}}</ful-note>
4703
4818
  `;
4704
4819
  render({ slots }) {
4705
- const fragment = this.template().withOverlay({ slots }).render();
4820
+ const fragment = this.template().withOverlay({ slots, icon: this.declared('icon') }).render();
4706
4821
  const trigger = fragment.querySelector('[data-ref=trigger]');
4707
4822
  const content = fragment.querySelector('[data-ref=content]');
4708
4823
  //placed here rather than by the anchor css: the note draws a callout that
4709
4824
  //has to point at the trigger wherever the viewport left room for the note,
4710
4825
  //which is a measurement the stylesheet cannot make for a pseudo-element
4711
4826
  Anchors.wire(trigger, content, { prefix: 'ful-tooltip', invoke: true, expanded: true, handPlace: true });
4712
- const placement = this.declared('placement');
4713
- if (placement) {
4714
- content.setAttribute('placement', placement);
4715
- }
4827
+ //above the marker by default: a note opening downwards covers the control
4828
+ //the marker explains, the marker riding the field's label
4829
+ content.setAttribute('placement', this.declared('placement') ?? 'top');
4716
4830
  this.replaceChildren(fragment);
4831
+ if (this.declared('describes')) {
4832
+ Tooltip.#describe(this, trigger, content);
4833
+ }
4834
+ }
4835
+ /**
4836
+ * Offers the note to the field the tooltip stands in, and takes the trigger
4837
+ * out of the tab order only where the offer was accepted: a note nothing
4838
+ * carries is reachable by the keyboard through the marker alone, so
4839
+ * dropping the stop there would leave it reachable by nothing at all.
4840
+ *
4841
+ * The offer goes through the description protocol rather than naming a
4842
+ * field, the library's own arrow running from the forms to the disclosures.
4843
+ */
4844
+ static #describe(tooltip, trigger, content) {
4845
+ if (!describable(tooltip)?.describedBy(content)) {
4846
+ console.warn('a ful-tooltip declares describes but stands in nothing that takes a description', tooltip);
4847
+ return;
4848
+ }
4849
+ trigger.tabIndex = -1;
4717
4850
  }
4718
4851
  }
4719
4852
 
4720
- /** A modal dialog on the native platform, open()/ask() resolving with the closer's data-result. */
4853
+ /**
4854
+ * A modal dialog on the native platform, open()/ask() resolving with the
4855
+ * closer's data-result.
4856
+ *
4857
+ * The header carries a close button, as the drawer's does: Escape dismisses a
4858
+ * modal on its own, but nothing says so, and a dialog whose only exit is a key
4859
+ * you have to know about leaves a pointer with nowhere to go. It answers the way
4860
+ * Escape does, with null.
4861
+ *
4862
+ * `requires-answer` is for the dialog that must be answered: the close button is not
4863
+ * rendered and Escape is refused, so the only way out is a button that carries a
4864
+ * result. It has to be both, a close button withheld while Escape still worked
4865
+ * being decoration rather than a rule.
4866
+ *
4867
+ * The chrome is reachable by class as well as by tag, so a plain `<dialog
4868
+ * class="ful-dialog">` written by a page gets the same look whatever its
4869
+ * structure: the tag form matches a direct child, and `ful-dialog-header`,
4870
+ * `ful-dialog-body` and `ful-dialog-footer` match at any depth, which is what a
4871
+ * dialog whose content is wrapped in a form needs.
4872
+ */
4721
4873
  class Dialog extends index_mjs.ParsedElement {
4722
- static attributes = ['header'];
4874
+ static attributes = ['header', 'requires-answer:presence'];
4723
4875
  static slots = true;
4724
4876
  static template = `
4725
4877
  <dialog data-ref="dialog" class="ful-dialog">
4726
- <header data-tpl-if="header"><h2>{{ header }}</h2></header>
4727
- <div data-ref="body">{{{{ slots.default }}}}</div>
4728
- <footer>
4878
+ <header data-tpl-if="header || !requiresAnswer" class="ful-dialog-header">
4879
+ <h2 data-tpl-if="header">{{ header }}</h2>
4880
+ <button data-tpl-if="!requiresAnswer" type="button" data-ref="close" data-tpl-aria-label="#l10n:t('dialog.close')"><ful-icon name="x-lg" aria-hidden="true"></ful-icon></button>
4881
+ </header>
4882
+ <div data-ref="body" class="ful-dialog-body">{{{{ slots.default }}}}</div>
4883
+ <footer class="ful-dialog-footer">
4729
4884
  <button type="button" data-ref="acknowledge" data-result="acknowledged" data-tpl-if="!slots.buttons" data-tpl-aria-label="#l10n:t('dialog.acknowledge')">{{ #l10n:t('dialog.acknowledge') }}</button>
4730
4885
  {{{{ slots.buttons }}}}
4731
4886
  </footer>
@@ -4736,8 +4891,9 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
4736
4891
  #requests = new SectionRequests();
4737
4892
  #resolvers = [];
4738
4893
  render({ slots }) {
4894
+ const requiresAnswer = this.declared('requires-answer');
4739
4895
  const fragment = this.template()
4740
- .withOverlay({ slots, header: this.declared('header') ?? '' })
4896
+ .withOverlay({ slots, header: this.declared('header') ?? '', requiresAnswer })
4741
4897
  .render();
4742
4898
  this.#dialog = fragment.querySelector('[data-ref=dialog]');
4743
4899
  this.#body = fragment.querySelector('[data-ref=body]');
@@ -4755,6 +4911,17 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
4755
4911
  this.#dialog.close(result);
4756
4912
  }
4757
4913
  });
4914
+ //dismissal, not an answer: the waiters are settled with null, as Escape does.
4915
+ //Optional because a subclass overriding the template owns what it renders
4916
+ fragment
4917
+ .querySelector('[data-ref=close]')
4918
+ ?.addEventListener('click', () => this.#dialog.close(''));
4919
+ if (requiresAnswer) {
4920
+ //the platform's own dismissal, refused where the dialog must be
4921
+ //answered: cancel fires for Escape and for a close request the
4922
+ //browser makes on its own, and preventing it leaves the dialog open
4923
+ this.#dialog.addEventListener('cancel', (/** @type any */ e) => e.preventDefault());
4924
+ }
4758
4925
  this.replaceChildren(fragment);
4759
4926
  wireTargets();
4760
4927
  }
@@ -5343,6 +5510,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
5343
5510
  'filters.boolean.false': 'No',
5344
5511
  'info.tooltip': 'More information',
5345
5512
  'dialog.acknowledge': 'Got it',
5513
+ 'dialog.close': 'Close',
5346
5514
  'drawer.close': 'Close',
5347
5515
  'spinner.loading': 'Loading…',
5348
5516
  'toast.region': 'Notifications',
@@ -5384,6 +5552,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
5384
5552
  'filters.boolean.false': 'No',
5385
5553
  'info.tooltip': 'Maggiori informazioni',
5386
5554
  'dialog.acknowledge': 'Ho capito',
5555
+ 'dialog.close': 'Chiudi',
5387
5556
  'drawer.close': 'Chiudi',
5388
5557
  'spinner.loading': 'Caricamento…',
5389
5558
  'toast.region': 'Notifiche',
@@ -5425,6 +5594,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
5425
5594
  'filters.boolean.false': 'No',
5426
5595
  'info.tooltip': 'Más información',
5427
5596
  'dialog.acknowledge': 'Entendido',
5597
+ 'dialog.close': 'Cerrar',
5428
5598
  'drawer.close': 'Cerrar',
5429
5599
  'spinner.loading': 'Cargando…',
5430
5600
  'toast.region': 'Notificaciones',
@@ -5469,6 +5639,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
5469
5639
  'filters.boolean.false': 'Non',
5470
5640
  'info.tooltip': 'Plus d’informations',
5471
5641
  'dialog.acknowledge': 'J’ai compris',
5642
+ 'dialog.close': 'Fermer',
5472
5643
  'drawer.close': 'Fermer',
5473
5644
  'spinner.loading': 'Chargement…',
5474
5645
  'toast.region': 'Notifications',
@@ -5598,6 +5769,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
5598
5769
  exports.VersionedLocalStorage = VersionedLocalStorage;
5599
5770
  exports.VersionedSessionStorage = VersionedSessionStorage;
5600
5771
  exports.Wizard = Wizard;
5772
+ exports.describable = describable;
5601
5773
 
5602
5774
  return exports;
5603
5775