@optionfactory/fml 9.0.0-rc23 → 9.0.0-rc25

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
@@ -363,6 +363,11 @@ declare class Bindings {
363
363
  * `rest` being the remainder of the path (`''` on an exact match) that a
364
364
  * composite can use to route the problem to an inner control.
365
365
  *
366
+ * A control counts only when it can show a problem, through a
367
+ * `setCustomValidity` of its own: a form-associated element that has none is
368
+ * skipped, so its problems fall to a shorter prefix or to the banner instead
369
+ * of being dropped.
370
+ *
366
371
  * Every other problem, and a field problem naming no control, is shown in
367
372
  * each `ful-errors` banner of the form: the banner gets `role="alert"`, is
368
373
  * revealed and holds the reasons one per line. With no such problem the
@@ -616,6 +621,14 @@ declare class Field extends ParsedElement {
616
621
  * accessibility tree.
617
622
  * - `also` are further controls that mirror `disabled` and `readOnly`
618
623
  * beside the claim target.
624
+ *
625
+ * The pieces are elements the field rendered itself, native controls or
626
+ * wrappers of its own. A ful field is not one: it has no `readOnly`, its
627
+ * host's role takes no aria, and a label's `for` names its host rather than
628
+ * its control. A composite built from ful fields answers the inner field as
629
+ * `control`, which forwards `focus()`, lets it render its own label with
630
+ * `label` and `announces` null, and forwards the claims in its setters,
631
+ * calling super first.
619
632
  * @param {{ slots: Record<string, DocumentFragment> | undefined }} conf
620
633
  * @returns {any} the pieces, or a promise of them
621
634
  * @throws {Error} when the subclass does not implement it
@@ -727,7 +740,8 @@ declare class FormLoader {
727
740
  *
728
741
  * Attributes on the host starting with `form-` are forwarded, without the
729
742
  * 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.
743
+ * well and stays on the host, the fields reading it from either; a
744
+ * `form-autocomplete` has the last word on the native form.
731
745
  * `clear-invalid-on-change` clears the custom validity of a field of this form
732
746
  * when a `change` event bubbles from it; the field's form is read from its
733
747
  * `form` property, or from `internals.form` for a form-associated element
@@ -2931,14 +2945,15 @@ declare class Dialog extends ParsedElement {
2931
2945
  *
2932
2946
  * No `section:requested` is dispatched, and no `ask()` waiter is added:
2933
2947
  * listen for the `close` event to learn how the dialog ended. When a newer
2934
- * `update()` starts before the callback settles, this call paints nothing:
2935
- * it resolves with the body regardless, and still rejects with the
2936
- * callback's error.
2948
+ * `update()` starts before the callback settles, this call paints nothing
2949
+ * and resolves `null`, the body being the newer call's; it still rejects
2950
+ * with the callback's error.
2937
2951
  * @param {() => Node|string|Promise<Node|string>} cb producing the body content
2938
- * @returns {Promise<Element>} the body section, in the document
2952
+ * @returns {Promise<Element|null>} the body section, in the document, or
2953
+ * `null` for a call a newer one overtook
2939
2954
  * @throws {any} what the callback threw or rejected with
2940
2955
  */
2941
- update(cb: () => Node | string | Promise<Node | string>): Promise<Element>;
2956
+ update(cb: () => Node | string | Promise<Node | string>): Promise<Element | null>;
2942
2957
  /**
2943
2958
  * Dispatches `section:requested` for the body again, as `ask()` does,
2944
2959
  * whether the dialog is open or closed. A failed request is painted into
@@ -3067,15 +3082,16 @@ declare class Drawer extends ParsedElement {
3067
3082
  * which has `role="alert"`, and the content is hidden.
3068
3083
  *
3069
3084
  * No `section:requested` is dispatched. When a newer `update()` starts
3070
- * before the callback settles, this call paints nothing: it resolves with
3071
- * the content section regardless, and still rejects with the callback's
3072
- * error.
3085
+ * before the callback settles, this call paints nothing and resolves
3086
+ * `null`, the content section being the newer call's; it still rejects with
3087
+ * the callback's error.
3073
3088
  * @param {string} header the title, set as text
3074
3089
  * @param {() => Node|string|Promise<Node|string>} cb producing the content
3075
- * @returns {Promise<Element>} the content section, in the document
3090
+ * @returns {Promise<Element|null>} the content section, in the document, or
3091
+ * `null` for a call a newer one overtook
3076
3092
  * @throws {any} what the callback threw or rejected with
3077
3093
  */
3078
- update(header: string, cb: () => Node | string | Promise<Node | string>): Promise<Element>;
3094
+ update(header: string, cb: () => Node | string | Promise<Node | string>): Promise<Element | null>;
3079
3095
  /**
3080
3096
  * Dispatches `section:requested` for the content again, as `open()` does,
3081
3097
  * whether the drawer is open or closed. A failed request is painted into
@@ -3145,11 +3161,15 @@ declare class Drawer extends ParsedElement {
3145
3161
  close(result?: string): void;
3146
3162
  }
3147
3163
  /**
3148
- * A menu of commands on the native popover platform, opened by the element
3149
- * whose id `for` names. The menu is the popover itself: light dismiss and
3150
- * Escape come from the browser, and it is anchored to its invoker through
3164
+ * A menu of commands on the native popover platform. The menu is the popover
3165
+ * itself: light dismiss and Escape come from the browser.
3166
+ *
3167
+ * A menu naming an invoker by id through `for` belongs to that element: the
3168
+ * invoker toggles it, and it is anchored to the invoker through
3151
3169
  * `Anchors.wire`, placed in script where the platform has no css anchor
3152
- * positioning.
3170
+ * positioning. A menu without `for` is shared: one menu serves many
3171
+ * invokers, such as the "more" button of every row of a list, and a page
3172
+ * opens it beside the invoker it is for with `showFor(invoker)`.
3153
3173
  *
3154
3174
  * The items are the buttons and `a[href]` links written as its direct
3155
3175
  * children, kept as they are. Activation is the item's own click, which the
@@ -3159,7 +3179,8 @@ declare class Drawer extends ParsedElement {
3159
3179
  * The keyboard is the ARIA menu protocol: the focus moves to the
3160
3180
  * first item as the menu opens, the arrows, Home and End walk the items,
3161
3181
  * typing searches their text, Enter and Space activate the focused one, and
3162
- * Escape and a close from inside the menu hand the focus back to the invoker.
3182
+ * Escape and a close from inside the menu hand the focus back to the invoker
3183
+ * that opened it.
3163
3184
  */
3164
3185
  declare class Menu extends ParsedElement {
3165
3186
  #private;
@@ -3167,13 +3188,27 @@ declare class Menu extends ParsedElement {
3167
3188
  static attributes: string[];
3168
3189
  /**
3169
3190
  * Gives the menu the `menu` role and an empty `popover` attribute unless
3170
- * it already has one, gives the invoker `aria-haspopup="menu"`, an
3171
- * `aria-expanded` kept in step, and the toggle of the menu, and marks the
3172
- * items. `for` is read here, once.
3191
+ * it already has one, and marks the items. With `for`, read here once, it
3192
+ * also gives the invoker `aria-haspopup="menu"`, an `aria-expanded` kept
3193
+ * in step, and the toggle of the menu.
3173
3194
  * @returns {void}
3174
- * @throws {Error} when `for` names no element in the document at the render
3195
+ * @throws {Error} when `for` is given and names no element in the document at the render
3175
3196
  */
3176
3197
  render(): void;
3198
+ /**
3199
+ * Opens a shared menu, one without `for`, beside `invoker`, or moves it
3200
+ * there when it is open for another one. The menu goes below the invoker,
3201
+ * or above it where there is no room below, and is placed once, as
3202
+ * `Anchors.show` places it. The invoker gets `aria-haspopup="menu"` and an
3203
+ * `aria-expanded` that reads true until the menu closes, and the focus
3204
+ * goes back to it on Escape. A call for the invoker whose press has just
3205
+ * closed the menu does nothing, so a second click on the same button
3206
+ * closes the menu rather than opening it again.
3207
+ * @param {HTMLElement} invoker the element the menu opens for
3208
+ * @returns {void}
3209
+ * @throws {Error} when the menu names its invoker through `for`
3210
+ */
3211
+ showFor(invoker: HTMLElement): void;
3177
3212
  /**
3178
3213
  * The commands, in the order the arrows walk them: the menu's direct
3179
3214
  * `button` and `a[href]` children, minus those carrying a `disabled` or
package/dist/ful.iife.js CHANGED
@@ -693,6 +693,11 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
693
693
  * `rest` being the remainder of the path (`''` on an exact match) that a
694
694
  * composite can use to route the problem to an inner control.
695
695
  *
696
+ * A control counts only when it can show a problem, through a
697
+ * `setCustomValidity` of its own: a form-associated element that has none is
698
+ * skipped, so its problems fall to a shorter prefix or to the banner instead
699
+ * of being dropped.
700
+ *
696
701
  * Every other problem, and a field problem naming no control, is shown in
697
702
  * each `ful-errors` banner of the form: the banner gets `role="alert"`, is
698
703
  * revealed and holds the reasons one per line. With no such problem the
@@ -731,7 +736,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
731
736
  const parts = name.split('.');
732
737
  for (let i = parts.length; i !== 0; --i) {
733
738
  const prefix = parts.slice(0, i).join('.');
734
- const targets = named.get(prefix) ?? [];
739
+ const targets = (named.get(prefix) ?? []).filter((el) => typeof el.setCustomValidity === 'function');
735
740
  if (targets.length === 0) {
736
741
  continue;
737
742
  }
@@ -1189,6 +1194,14 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
1189
1194
  * accessibility tree.
1190
1195
  * - `also` are further controls that mirror `disabled` and `readOnly`
1191
1196
  * beside the claim target.
1197
+ *
1198
+ * The pieces are elements the field rendered itself, native controls or
1199
+ * wrappers of its own. A ful field is not one: it has no `readOnly`, its
1200
+ * host's role takes no aria, and a label's `for` names its host rather than
1201
+ * its control. A composite built from ful fields answers the inner field as
1202
+ * `control`, which forwards `focus()`, lets it render its own label with
1203
+ * `label` and `announces` null, and forwards the claims in its setters,
1204
+ * calling super first.
1192
1205
  * @param {{ slots: Record<string, DocumentFragment> | undefined }} conf
1193
1206
  * @returns {any} the pieces, or a promise of them
1194
1207
  * @throws {Error} when the subclass does not implement it
@@ -1345,7 +1358,8 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
1345
1358
  *
1346
1359
  * Attributes on the host starting with `form-` are forwarded, without the
1347
1360
  * prefix, to the native form. `autocomplete` is copied onto the native form as
1348
- * well and stays on the host, the fields reading it from either.
1361
+ * well and stays on the host, the fields reading it from either; a
1362
+ * `form-autocomplete` has the last word on the native form.
1349
1363
  * `clear-invalid-on-change` clears the custom validity of a field of this form
1350
1364
  * when a `change` event bubbles from it; the field's form is read from its
1351
1365
  * `form` property, or from `internals.form` for a form-associated element
@@ -1380,8 +1394,8 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
1380
1394
  const form = document.createElement('form');
1381
1395
  this.form = form;
1382
1396
  form.setAttribute('novalidate', '');
1383
- index_mjs.Attributes.forward('form-', this, form);
1384
1397
  index_mjs.Attributes.set(form, 'autocomplete', this.declared('autocomplete'));
1398
+ index_mjs.Attributes.forward('form-', this, form);
1385
1399
  form.replaceChildren(...this.childNodes);
1386
1400
  form.addEventListener('submit', async (e) => {
1387
1401
  e.preventDefault();
@@ -4576,7 +4590,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
4576
4590
  return;
4577
4591
  }
4578
4592
  /** @type {HTMLInputElement|null} */
4579
- const el = this.querySelector(`input[type=radio][value=${CSS.escape(String(value))}]`);
4593
+ const el = this.querySelector(`input[type=radio][value="${CSS.escape(String(value))}"]`);
4580
4594
  if (el === null) {
4581
4595
  clear();
4582
4596
  return;
@@ -5569,7 +5583,9 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
5569
5583
  * `items` answers the items in the order they are walked, and is called on
5570
5584
  * every gesture, so a menu that fills itself later needs no rewiring.
5571
5585
  *
5572
- * @param {HTMLElement} invoker the element the menu belongs to, and where the focus goes back
5586
+ * @param {() => HTMLElement|null} invoker answers the element the menu belongs to
5587
+ * now, where the focus goes back; called on every gesture, so a menu shared
5588
+ * by several invokers hands the focus back to the one that opened it
5573
5589
  * @param {HTMLElement} menu the `[popover]` holding the items
5574
5590
  * @param {{items: () => HTMLElement[], current?: () => HTMLElement|null|undefined}} conf
5575
5591
  * `items` answers the items; `current` answers the item an opening focuses
@@ -5579,7 +5595,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
5579
5595
  let typedAt = 0;
5580
5596
  menu.addEventListener('beforetoggle', (/** @type any */ evt) => {
5581
5597
  if (evt.newState === 'closed' && menu.contains(document.activeElement)) {
5582
- invoker.focus();
5598
+ invoker()?.focus();
5583
5599
  }
5584
5600
  });
5585
5601
  menu.addEventListener('toggle', (/** @type any */ evt) => {
@@ -5623,11 +5639,11 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
5623
5639
  case 'Space': {
5624
5640
  evt.preventDefault();
5625
5641
  item.click();
5626
- invoker.focus();
5642
+ invoker()?.focus();
5627
5643
  return;
5628
5644
  }
5629
5645
  case 'Escape': {
5630
- invoker.focus();
5646
+ invoker()?.focus();
5631
5647
  return;
5632
5648
  }
5633
5649
  }
@@ -5852,7 +5868,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
5852
5868
  }
5853
5869
  #wire() {
5854
5870
  Anchors.wire(this.#button, this.#menu, { prefix: 'ful-filter-menu', invoke: true, expanded: true });
5855
- wireMenuKeys(this.#button, this.#menu, {
5871
+ wireMenuKeys(() => this.#button, this.#menu, {
5856
5872
  items: () => this.#items(),
5857
5873
  current: () => this.#items().find((a) => a.getAttribute('value') === this.value),
5858
5874
  });
@@ -6850,11 +6866,12 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
6850
6866
  * `show`, and fills the content with what the callback resolves to. A
6851
6867
  * rejection is shown, as the problems' reasons, in the error section, and
6852
6868
  * the content is hidden. When a newer `update()` starts before the callback
6853
- * settles, this call paints nothing: it resolves with the section regardless,
6854
- * and still rejects with the callback's error.
6869
+ * settles, this call paints nothing and resolves `null`, the section being
6870
+ * the newer call's; it still rejects with the callback's error.
6855
6871
  * @param {() => Node|string|Promise<Node|string>} cb producing the content
6856
6872
  * @param {() => void} show opens the dialog when it is closed
6857
- * @returns {Promise<HTMLElement>} the content section
6873
+ * @returns {Promise<HTMLElement|null>} the content section, or `null` for a
6874
+ * call a newer one overtook
6858
6875
  * @throws {any} what the callback threw or rejected with
6859
6876
  */
6860
6877
  async update(cb, show) {
@@ -6867,7 +6884,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
6867
6884
  try {
6868
6885
  const delivered = await cb();
6869
6886
  if (claim.stale) {
6870
- return this.#section;
6887
+ return null;
6871
6888
  }
6872
6889
  this.#section.replaceChildren(delivered);
6873
6890
  this.#loading?.setAttribute('hidden', '');
@@ -7113,11 +7130,12 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
7113
7130
  *
7114
7131
  * No `section:requested` is dispatched, and no `ask()` waiter is added:
7115
7132
  * listen for the `close` event to learn how the dialog ended. When a newer
7116
- * `update()` starts before the callback settles, this call paints nothing:
7117
- * it resolves with the body regardless, and still rejects with the
7118
- * callback's error.
7133
+ * `update()` starts before the callback settles, this call paints nothing
7134
+ * and resolves `null`, the body being the newer call's; it still rejects
7135
+ * with the callback's error.
7119
7136
  * @param {() => Node|string|Promise<Node|string>} cb producing the body content
7120
- * @returns {Promise<Element>} the body section, in the document
7137
+ * @returns {Promise<Element|null>} the body section, in the document, or
7138
+ * `null` for a call a newer one overtook
7121
7139
  * @throws {any} what the callback threw or rejected with
7122
7140
  */
7123
7141
  async update(cb) {
@@ -7407,12 +7425,13 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
7407
7425
  * which has `role="alert"`, and the content is hidden.
7408
7426
  *
7409
7427
  * No `section:requested` is dispatched. When a newer `update()` starts
7410
- * before the callback settles, this call paints nothing: it resolves with
7411
- * the content section regardless, and still rejects with the callback's
7412
- * error.
7428
+ * before the callback settles, this call paints nothing and resolves
7429
+ * `null`, the content section being the newer call's; it still rejects with
7430
+ * the callback's error.
7413
7431
  * @param {string} header the title, set as text
7414
7432
  * @param {() => Node|string|Promise<Node|string>} cb producing the content
7415
- * @returns {Promise<Element>} the content section, in the document
7433
+ * @returns {Promise<Element|null>} the content section, in the document, or
7434
+ * `null` for a call a newer one overtook
7416
7435
  * @throws {any} what the callback threw or rejected with
7417
7436
  */
7418
7437
  async update(header, cb) {
@@ -7561,11 +7580,15 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
7561
7580
  }
7562
7581
 
7563
7582
  /**
7564
- * A menu of commands on the native popover platform, opened by the element
7565
- * whose id `for` names. The menu is the popover itself: light dismiss and
7566
- * Escape come from the browser, and it is anchored to its invoker through
7583
+ * A menu of commands on the native popover platform. The menu is the popover
7584
+ * itself: light dismiss and Escape come from the browser.
7585
+ *
7586
+ * A menu naming an invoker by id through `for` belongs to that element: the
7587
+ * invoker toggles it, and it is anchored to the invoker through
7567
7588
  * `Anchors.wire`, placed in script where the platform has no css anchor
7568
- * positioning.
7589
+ * positioning. A menu without `for` is shared: one menu serves many
7590
+ * invokers, such as the "more" button of every row of a list, and a page
7591
+ * opens it beside the invoker it is for with `showFor(invoker)`.
7569
7592
  *
7570
7593
  * The items are the buttons and `a[href]` links written as its direct
7571
7594
  * children, kept as they are. Activation is the item's own click, which the
@@ -7575,34 +7598,44 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
7575
7598
  * The keyboard is the ARIA menu protocol: the focus moves to the
7576
7599
  * first item as the menu opens, the arrows, Home and End walk the items,
7577
7600
  * typing searches their text, Enter and Space activate the focused one, and
7578
- * Escape and a close from inside the menu hand the focus back to the invoker.
7601
+ * Escape and a close from inside the menu hand the focus back to the invoker
7602
+ * that opened it.
7579
7603
  */
7580
7604
  class Menu extends index_mjs.ParsedElement {
7581
7605
  /** @type {string[]} */
7582
7606
  static attributes = ['for'];
7607
+ /** @type {HTMLElement|null} */
7608
+ #invoker = null;
7609
+ /** @type {HTMLElement|null} */
7610
+ #pressed = null;
7583
7611
  /**
7584
7612
  * Gives the menu the `menu` role and an empty `popover` attribute unless
7585
- * it already has one, gives the invoker `aria-haspopup="menu"`, an
7586
- * `aria-expanded` kept in step, and the toggle of the menu, and marks the
7587
- * items. `for` is read here, once.
7613
+ * it already has one, and marks the items. With `for`, read here once, it
7614
+ * also gives the invoker `aria-haspopup="menu"`, an `aria-expanded` kept
7615
+ * in step, and the toggle of the menu.
7588
7616
  * @returns {void}
7589
- * @throws {Error} when `for` names no element in the document at the render
7617
+ * @throws {Error} when `for` is given and names no element in the document at the render
7590
7618
  */
7591
7619
  render() {
7592
7620
  const named = this.declared('for');
7593
7621
  const invoker = named ? document.getElementById(named) : null;
7594
- if (!invoker) {
7622
+ if (named && !invoker) {
7595
7623
  throw new Error(
7596
- `ful-menu names no invoker: for="${named ?? ''}" must name an element already in the page when the menu upgrades`,
7624
+ `ful-menu names no invoker: for="${named}" must name an element already in the page when the menu upgrades`,
7597
7625
  );
7598
7626
  }
7599
7627
  this.setAttribute('role', 'menu');
7600
7628
  if (!this.hasAttribute('popover')) {
7601
7629
  this.setAttribute('popover', '');
7602
7630
  }
7603
- invoker.setAttribute('aria-haspopup', 'menu');
7604
- Anchors.wire(invoker, this, { prefix: 'ful-menu', invoke: true, expanded: true });
7605
- wireMenuKeys(invoker, this, { items: () => this.items });
7631
+ if (invoker) {
7632
+ this.#invoker = invoker;
7633
+ invoker.setAttribute('aria-haspopup', 'menu');
7634
+ Anchors.wire(invoker, this, { prefix: 'ful-menu', invoke: true, expanded: true });
7635
+ } else {
7636
+ this.#wireShared();
7637
+ }
7638
+ wireMenuKeys(() => this.#invoker, this, { items: () => this.items });
7606
7639
  this.#mark();
7607
7640
  this.addEventListener('click', (evt) => {
7608
7641
  const target = /** @type HTMLElement */ (evt.target);
@@ -7611,6 +7644,43 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
7611
7644
  }
7612
7645
  });
7613
7646
  }
7647
+ /**
7648
+ * Opens a shared menu, one without `for`, beside `invoker`, or moves it
7649
+ * there when it is open for another one. The menu goes below the invoker,
7650
+ * or above it where there is no room below, and is placed once, as
7651
+ * `Anchors.show` places it. The invoker gets `aria-haspopup="menu"` and an
7652
+ * `aria-expanded` that reads true until the menu closes, and the focus
7653
+ * goes back to it on Escape. A call for the invoker whose press has just
7654
+ * closed the menu does nothing, so a second click on the same button
7655
+ * closes the menu rather than opening it again.
7656
+ * @param {HTMLElement} invoker the element the menu opens for
7657
+ * @returns {void}
7658
+ * @throws {Error} when the menu names its invoker through `for`
7659
+ */
7660
+ showFor(invoker) {
7661
+ if (this.declared('for')) {
7662
+ throw new Error('ful-menu: showFor serves a menu without for; a menu with for is opened by its invoker');
7663
+ }
7664
+ const pressed = this.#pressed;
7665
+ this.#pressed = null;
7666
+ if (pressed === invoker) {
7667
+ return;
7668
+ }
7669
+ if (this.#invoker && this.#invoker !== invoker) {
7670
+ this.#invoker.setAttribute('aria-expanded', 'false');
7671
+ }
7672
+ this.#invoker = invoker;
7673
+ invoker.setAttribute('aria-haspopup', 'menu');
7674
+ invoker.setAttribute('aria-expanded', 'true');
7675
+ //the previous placement wrote margin: 0 inline, hiding the stylesheet's gap
7676
+ this.style.margin = '';
7677
+ const gap = Number.parseFloat(getComputedStyle(this).marginBlockStart) || 0;
7678
+ const moving = this.matches(':popover-open');
7679
+ Anchors.show(this, invoker, { flip: true, gap });
7680
+ if (moving) {
7681
+ this.items[0]?.focus();
7682
+ }
7683
+ }
7614
7684
  /**
7615
7685
  * The commands, in the order the arrows walk them: the menu's direct
7616
7686
  * `button` and `a[href]` children, minus those carrying a `disabled` or
@@ -7622,6 +7692,21 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
7622
7692
  get items() {
7623
7693
  return this.#mark().filter((item) => !item.hasAttribute('disabled') && !item.hasAttribute('hidden'));
7624
7694
  }
7695
+ #wireShared() {
7696
+ document.addEventListener(
7697
+ 'pointerdown',
7698
+ (evt) => {
7699
+ const target = /** @type Node */ (evt.target);
7700
+ this.#pressed = this.matches(':popover-open') && this.#invoker?.contains(target) ? this.#invoker : null;
7701
+ },
7702
+ true,
7703
+ );
7704
+ this.addEventListener('toggle', (/** @type any */ evt) => {
7705
+ if (evt.newState === 'closed') {
7706
+ this.#invoker?.setAttribute('aria-expanded', 'false');
7707
+ }
7708
+ });
7709
+ }
7625
7710
  /** @returns {HTMLElement[]} every item, disabled and hidden ones included, once marked */
7626
7711
  #mark() {
7627
7712
  const all = /** @type HTMLElement[] */ ([...this.querySelectorAll(':scope > button, :scope > a[href]')]);