@fusion.dev/helix-headless 0.22.0 → 0.24.0
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/custom-elements.json +48397 -26631
- package/dist/announcement/root/announcement-root.d.ts +19 -5
- package/dist/area-chart/index.d.ts +1 -0
- package/dist/area-chart/index.js +1 -0
- package/dist/area-chart/root/area-chart-root.d.ts +182 -0
- package/dist/area-chart/root/area-chart-root.js +205 -0
- package/dist/carousel/root/carousel-root.d.ts +1 -0
- package/dist/carousel/root/carousel-root.js +18 -2
- package/dist/chat/index.d.ts +1 -1
- package/dist/chat/message/chat-message.d.ts +7 -0
- package/dist/chat/message/chat-message.js +2 -1
- package/dist/chat/root/chat-root.d.ts +195 -4
- package/dist/chat/root/chat-root.js +316 -23
- package/dist/checkbox-group/root/checkbox-group-root.d.ts +2 -0
- package/dist/checkbox-group/root/checkbox-group-root.js +12 -2
- package/dist/code-block/index.d.ts +1 -0
- package/dist/code-block/index.js +1 -0
- package/dist/code-block/patch.d.ts +62 -0
- package/dist/code-block/patch.js +93 -0
- package/dist/code-block/root/code-block-root.d.ts +82 -17
- package/dist/code-block/root/code-block-root.js +83 -6
- package/dist/color-picker/root/color-picker-root.d.ts +4 -3
- package/dist/color-picker/root/color-picker-root.js +4 -1
- package/dist/data-table/root/data-table-root.d.ts +195 -23
- package/dist/data-table/root/data-table-root.js +308 -27
- package/dist/description-list/index.d.ts +2 -0
- package/dist/description-list/index.js +2 -0
- package/dist/description-list/item/description-list-item.d.ts +39 -0
- package/dist/description-list/item/description-list-item.js +26 -0
- package/dist/description-list/root/description-list-root.d.ts +56 -0
- package/dist/description-list/root/description-list-root.js +21 -0
- package/dist/element-events.generated.js +27 -6
- package/dist/filter-input/chip/filter-chip.d.ts +114 -0
- package/dist/filter-input/chip/filter-chip.js +61 -0
- package/dist/filter-input/field/filter-field.d.ts +128 -0
- package/dist/filter-input/field/filter-field.js +70 -0
- package/dist/filter-input/grammar.d.ts +96 -0
- package/dist/filter-input/grammar.js +133 -0
- package/dist/filter-input/index.d.ts +5 -0
- package/dist/filter-input/index.js +5 -0
- package/dist/filter-input/menu/filter-menu.d.ts +153 -0
- package/dist/filter-input/menu/filter-menu.js +164 -0
- package/dist/filter-input/operator/filter-operator.d.ts +105 -0
- package/dist/filter-input/operator/filter-operator.js +39 -0
- package/dist/filter-input/root/filter-input-root.d.ts +118 -9
- package/dist/filter-input/root/filter-input-root.js +96 -2
- package/dist/format-date/root/format-date-root.js +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/line-chart/root/line-chart-root.d.ts +9 -2
- package/dist/listbox/root/listbox-root.d.ts +8 -7
- package/dist/listbox/root/listbox-root.js +7 -2
- package/dist/menu/item/menu-item.d.ts +54 -6
- package/dist/menu/item/menu-item.js +31 -6
- package/dist/menu/root/menu-root.d.ts +23 -0
- package/dist/menu/root/menu-root.js +13 -2
- package/dist/notifications/group/notification-group.d.ts +129 -0
- package/dist/notifications/group/notification-group.js +106 -0
- package/dist/notifications/index.d.ts +1 -0
- package/dist/notifications/index.js +1 -0
- package/dist/notifications/notification/notification.d.ts +39 -13
- package/dist/notifications/notification/notification.js +11 -8
- package/dist/notifications/root/notifications-root.d.ts +57 -1
- package/dist/notifications/root/notifications-root.js +35 -4
- package/dist/number-input/root/number-input-root.d.ts +21 -1
- package/dist/number-input/root/number-input-root.js +18 -5
- package/dist/object-inspector/index.d.ts +1 -0
- package/dist/object-inspector/index.js +1 -0
- package/dist/object-inspector/root/object-inspector-root.d.ts +183 -0
- package/dist/object-inspector/root/object-inspector-root.js +348 -0
- package/dist/outline/root/outline-root.d.ts +16 -1
- package/dist/outline/root/outline-root.js +27 -9
- package/dist/radio/group/radio-group.d.ts +1 -0
- package/dist/scatter-chart/index.d.ts +1 -0
- package/dist/scatter-chart/index.js +1 -0
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +144 -0
- package/dist/scatter-chart/root/scatter-chart-root.js +205 -0
- package/dist/skip-link/index.d.ts +1 -0
- package/dist/skip-link/index.js +1 -0
- package/dist/skip-link/root/skip-link-root.d.ts +81 -0
- package/dist/skip-link/root/skip-link-root.js +58 -0
- package/dist/speed-dial/root/speed-dial-root.d.ts +1 -0
- package/dist/speed-dial/root/speed-dial-root.js +6 -2
- package/dist/steps/root/steps-root.d.ts +1 -0
- package/dist/table/column/table-column.d.ts +79 -0
- package/dist/table/column/table-column.js +125 -5
- package/dist/table/root/table-root.d.ts +2 -0
- package/dist/table/root/table-root.js +12 -3
- package/dist/time-picker/root/time-picker-root.d.ts +9 -0
- package/dist/time-picker/root/time-picker-root.js +5 -0
- package/dist/toast/root/toast-root.d.ts +2 -0
- package/dist/tooltip/root/tooltip-root.js +1 -1
- package/dist/tour/root/tour-root.d.ts +1 -0
- package/dist/tree/item/tree-item.d.ts +7 -4
- package/dist/tree/item/tree-item.js +4 -1
- package/dist/video/root/video-root.d.ts +23 -1
- package/dist/video/root/video-root.js +42 -22
- package/dist/virtual-list/root/virtual-list-root.d.ts +10 -6
- package/dist/virtual-list/root/virtual-list-root.js +10 -4
- package/package.json +5 -5
- package/types/jsx.d.ts +4535 -346
- package/types/svelte.d.ts +11 -0
- package/types/vue.d.ts +737 -39
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AriaForwardController, HelixFormElement, PopoverController } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
import type { HelixHeadlessFilterField } from '../field/filter-field.js';
|
|
3
4
|
declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinTokensInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
|
|
4
5
|
/**
|
|
5
6
|
* @summary A field that narrows something else, beside what is already narrowing it.
|
|
@@ -22,13 +23,32 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
|
|
|
22
23
|
* `helix-dismiss` its own remove control fires, so a consumer who handled the
|
|
23
24
|
* pointer has already handled the keyboard.
|
|
24
25
|
*
|
|
25
|
-
* **It
|
|
26
|
-
* `
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
26
|
+
* **It reads what was declared, and nothing that was not.** Write
|
|
27
|
+
* `FilterField`s — usually inside the `FilterMenu` slotted into `start`, so the
|
|
28
|
+
* picker and the text work off one set — and Enter reads the box against them:
|
|
29
|
+
* `disposition:refunded,settled` and `-disposition:refunded` announce
|
|
30
|
+
* `helix-facet`, exactly as ticking those values in the picker would, and the
|
|
31
|
+
* text they were read from goes. Everything else stays where it was typed, so
|
|
32
|
+
* `refund tuesday disposition:settled` is a filter and a search at once.
|
|
33
|
+
* `grammar.ts` carries the whole rule and is worth reading before relying on it;
|
|
34
|
+
* the short version is that Helix owns the punctuation and the consumer owns
|
|
35
|
+
* every word in it. A field with no `FilterField` in it parses nothing, which is
|
|
36
|
+
* the behavior this had before it could.
|
|
37
|
+
*
|
|
38
|
+
* **A field whose values cannot be listed belongs here rather than in the
|
|
39
|
+
* picker.** `assignee:ada` is a filter and nobody wants to enumerate every name
|
|
40
|
+
* in the company, but a menu page for it would have nothing on it to press.
|
|
41
|
+
* Written directly inside this element it is read by the grammar and never
|
|
42
|
+
* offered as a page: unslotted, so nothing draws it, which is the same trick
|
|
43
|
+
* `FilterField` already plays on its own children.
|
|
44
|
+
*
|
|
45
|
+
* **It can suggest what to type next, and deciding what to suggest is still the
|
|
46
|
+
* consumer's.** Slot `helix-option` elements into `suggestions` and the field
|
|
47
|
+
* shows them, moves through them with the arrows, and lets Enter press one; the
|
|
48
|
+
* option fires its own `helix-select` and the consumer decides what choosing
|
|
49
|
+
* meant. Keys before a colon and values after it are the same panel with
|
|
50
|
+
* different contents, and `readFilterText` is exported for a consumer narrowing
|
|
51
|
+
* that panel against what a reader has half typed.
|
|
32
52
|
*
|
|
33
53
|
* **The panel opens when there is something in it and closes when there is
|
|
34
54
|
* not**, rather than on a choice. A reader who picks a key wants its values
|
|
@@ -55,7 +75,12 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
|
|
|
55
75
|
* The tokens are neither, because they are not state. They are the consumer's
|
|
56
76
|
* own elements in a slot, so there is nothing for this to own and nothing to
|
|
57
77
|
* report back: a token goes when the consumer removes it from their markup,
|
|
58
|
-
* having been asked by `helix-dismiss`.
|
|
78
|
+
* having been asked by `helix-dismiss`. **A `FilterChip` naming a declared field
|
|
79
|
+
* is asked differently**, and it has to be: the picker holds what is ticked, so
|
|
80
|
+
* a chip that removed itself without telling it left the value ticked in a list
|
|
81
|
+
* nobody could see was wrong. That dismissal is answered as the field being
|
|
82
|
+
* filtered by nothing, so it arrives as `helix-facet` like every other change
|
|
83
|
+
* and the chip goes because the filter went.
|
|
59
84
|
*
|
|
60
85
|
* @since 0.1.0
|
|
61
86
|
* @status experimental
|
|
@@ -66,12 +91,15 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
|
|
|
66
91
|
* @slot end - After the text, for a count or a control.
|
|
67
92
|
* @slot start - Before the text, for a search icon.
|
|
68
93
|
* @slot suggestions - What could be typed next, as `Option`s, and `Option.Group`s of them.
|
|
69
|
-
* @slot tags - What is already narrowing the results, as `Tag`s.
|
|
94
|
+
* @slot tags - What is already narrowing the results, as `FilterChip`s or `Tag`s.
|
|
70
95
|
*
|
|
71
96
|
* @event {Event} change - Relayed from the input once the reader has finished.
|
|
72
97
|
* @event {HelixBeforeChangeEvent} helix-before-change - Fired as the text changes, and cancelable.
|
|
73
98
|
* @event {HelixBeforeCloseEvent} helix-before-close - Fired before the suggestions go, and cancelable.
|
|
74
99
|
* @event {HelixBeforeOpenEvent} helix-before-open - Fired before the suggestions show, and cancelable.
|
|
100
|
+
* @event {HelixDismissEvent} helix-dismiss - A token asking to go, from its close control or from Backspace on an empty field. A `FilterChip` naming a declared field is answered as `helix-facet` instead. Cancelable: refusing it takes the decision and the token stays.
|
|
101
|
+
* @event {HelixFacetEvent} helix-facet - Fired when a filter changes, whether read out of the text, picked, or dismissed. Cancelable: refusing it leaves the text and the chip where they were.
|
|
102
|
+
* @event {HelixSelectEvent} helix-select - A suggestion was chosen: the option's own, heard here because it bubbles out of `suggestions`. Cancelable: refusing it takes the decision from any other listener, and this field never acts on a choice itself, since what it means is the page's.
|
|
75
103
|
* @event {Event} input - Announced once the text has moved.
|
|
76
104
|
* @event {Event} toggle - Says the panel moved, carrying `newState` and `oldState`.
|
|
77
105
|
*
|
|
@@ -81,6 +109,7 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
|
|
|
81
109
|
* @csspart field - The box holding everything.
|
|
82
110
|
* @csspart hint - Help under the field, while nothing is wrong.
|
|
83
111
|
* @csspart label - The question, over the field.
|
|
112
|
+
* @csspart lead - The box before the text, holding whatever is slotted into `start`.
|
|
84
113
|
* @csspart rest - How many tokens did not fit. A `<button>` with `expandable`, a `<span>` without it.
|
|
85
114
|
* @csspart surface - The floating panel the suggestions sit in.
|
|
86
115
|
* @csspart tags - The row the tokens sit in.
|
|
@@ -129,6 +158,24 @@ export declare class HelixHeadlessFilterInput extends HelixHeadlessFilterInput_b
|
|
|
129
158
|
* `aria-controls` all arrive with the first option and leave with the last.
|
|
130
159
|
*/
|
|
131
160
|
private suggesting;
|
|
161
|
+
/**
|
|
162
|
+
* Whether anything is slotted before the text.
|
|
163
|
+
*
|
|
164
|
+
* The leading box is drawn only where there is something to put in it, and it
|
|
165
|
+
* is a box rather than a bare slot because it carries the seam between what a
|
|
166
|
+
* consumer put at the head of the field and the field itself. `Menu.Item`
|
|
167
|
+
* reads its own leading slot the same way and for the same reason: the light
|
|
168
|
+
* DOM answer is not reactive, so a control added after the first render would
|
|
169
|
+
* never get the box.
|
|
170
|
+
*
|
|
171
|
+
* **Empty, the box is `hidden`, not flagged**, for `Checkbox`'s reason: this
|
|
172
|
+
* tier has no sheet, and `::part(lead)` cannot be qualified by an attribute,
|
|
173
|
+
* so a `data-` flag left a headless field an empty flex item it had no way to
|
|
174
|
+
* remove, and the first chip sat one gap further in than the rest.
|
|
175
|
+
*/
|
|
176
|
+
private leading;
|
|
177
|
+
/** Whether `leading` has had its first answer from the children. */
|
|
178
|
+
private leadRead;
|
|
132
179
|
/**
|
|
133
180
|
* Whether Escape has closed the panel over text that still has suggestions.
|
|
134
181
|
*
|
|
@@ -195,6 +242,64 @@ export declare class HelixHeadlessFilterInput extends HelixHeadlessFilterInput_b
|
|
|
195
242
|
private readonly handleInput;
|
|
196
243
|
/** Relayed, because `change` does not cross a shadow boundary on its own. */
|
|
197
244
|
private readonly handleChange;
|
|
245
|
+
/**
|
|
246
|
+
* The fields this can be filtered by, wherever they were written.
|
|
247
|
+
*
|
|
248
|
+
* Descendants rather than children, because the usual place to write them is
|
|
249
|
+
* inside the `FilterMenu` slotted into `start`: the picker and the text are
|
|
250
|
+
* two ways at one set of declarations, and asking a consumer to write them
|
|
251
|
+
* twice would be asking which of the two surfaces is the real one.
|
|
252
|
+
*/
|
|
253
|
+
get fields(): HelixHeadlessFilterField[];
|
|
254
|
+
/**
|
|
255
|
+
* The picker holding a field, where the consumer wrote one.
|
|
256
|
+
*
|
|
257
|
+
* The picker keeps what is ticked, so anything that changes a filter has to
|
|
258
|
+
* go through it or the two surfaces start disagreeing: a filter typed here
|
|
259
|
+
* that the menu never heard about leaves the value unticked in a list the
|
|
260
|
+
* reader is about to open.
|
|
261
|
+
*/
|
|
262
|
+
private menuFor;
|
|
263
|
+
/**
|
|
264
|
+
* Says a field is now filtered by these values, through the picker where
|
|
265
|
+
* there is one, and reports whether it stood.
|
|
266
|
+
*
|
|
267
|
+
* Announced by the menu rather than relayed by this element, so the ticks and
|
|
268
|
+
* the event never come from two places. A consumer who wrote `FilterField`s
|
|
269
|
+
* without a picker gets it from here, which is the only case where nothing
|
|
270
|
+
* else could have said it.
|
|
271
|
+
*/
|
|
272
|
+
private announceFacet;
|
|
273
|
+
/**
|
|
274
|
+
* A chip asking to go, answered as the filter going.
|
|
275
|
+
*
|
|
276
|
+
* **Taken rather than passed on, and only for a chip naming a declared
|
|
277
|
+
* field.** Left alone, a dismissed chip removed itself and told the picker
|
|
278
|
+
* nothing, so the menu went on showing the value ticked and the reader's next
|
|
279
|
+
* press on it read as *unticking*: they pressed `Refunded` and nothing came
|
|
280
|
+
* back. Turning the request into `apply(field, [])` puts it down the one road
|
|
281
|
+
* every other filter takes, so the chip goes because the filter went. A
|
|
282
|
+
* consumer refusing that `helix-facet` keeps both, which is the answer a
|
|
283
|
+
* refusal should give.
|
|
284
|
+
*
|
|
285
|
+
* Everything else in the row is untouched: a plain `Tag`, or a chip for
|
|
286
|
+
* something no `FilterField` declares, is the consumer's to answer.
|
|
287
|
+
*/
|
|
288
|
+
private readonly handleDismiss;
|
|
289
|
+
/**
|
|
290
|
+
* Reads what has been typed, announces whatever of it was a filter, and takes
|
|
291
|
+
* that much of the text away.
|
|
292
|
+
*
|
|
293
|
+
* **Nothing at all without declarations**, which is what keeps this from
|
|
294
|
+
* changing under a consumer who never asked for it: a field with no
|
|
295
|
+
* `FilterField` in it has no vocabulary to read against, so Enter stays the
|
|
296
|
+
* form's and every keystroke is still just text.
|
|
297
|
+
*
|
|
298
|
+
* Refusing a `helix-facet` leaves that term's text where the reader wrote it,
|
|
299
|
+
* so a consumer who declines one is not left with a box that has silently
|
|
300
|
+
* emptied itself of a filter they did not take.
|
|
301
|
+
*/
|
|
302
|
+
private readFilters;
|
|
198
303
|
/**
|
|
199
304
|
* Empties the text and gives the caret back.
|
|
200
305
|
*
|
|
@@ -248,6 +353,8 @@ export declare class HelixHeadlessFilterInput extends HelixHeadlessFilterInput_b
|
|
|
248
353
|
* this element and must not close what is being pressed.
|
|
249
354
|
*/
|
|
250
355
|
private readonly handleFocusOut;
|
|
356
|
+
/** The leading box again, whenever what is in front of the text changes. */
|
|
357
|
+
private readonly handleLeading;
|
|
251
358
|
/** The row again, whenever what is in it changes. */
|
|
252
359
|
private readonly handleTokens;
|
|
253
360
|
/**
|
|
@@ -261,6 +368,8 @@ export declare class HelixHeadlessFilterInput extends HelixHeadlessFilterInput_b
|
|
|
261
368
|
* something it was never given.
|
|
262
369
|
*/
|
|
263
370
|
protected removeLastToken(): void;
|
|
371
|
+
connectedCallback(): void;
|
|
372
|
+
disconnectedCallback(): void;
|
|
264
373
|
protected willUpdate(changed: PropertyValues): void;
|
|
265
374
|
protected firstUpdated(changed: PropertyValues): void;
|
|
266
375
|
protected updated(changed: PropertyValues): void;
|
|
@@ -4,10 +4,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, customElement, drawIcon, HelixFormElement, holdCaret, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, restoreValue, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, customElement, drawIcon, HelixFacetEvent, HelixFormElement, holdCaret, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, restoreValue, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { x } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
|
+
import { readFilterText } from '../grammar.js';
|
|
11
12
|
const asksToGo = (node) => typeof node?.dismiss === 'function';
|
|
12
13
|
const isOption = (node) => 'active' in node && 'value' in node;
|
|
13
14
|
let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinControllerOpen(MixinTokens(MixinControllerValue(MixinField(HelixFormElement)))) {
|
|
@@ -23,6 +24,8 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
23
24
|
this.over = 0;
|
|
24
25
|
this.activeAt = -1;
|
|
25
26
|
this.suggesting = false;
|
|
27
|
+
this.leading = false;
|
|
28
|
+
this.leadRead = false;
|
|
26
29
|
this.dismissed = false;
|
|
27
30
|
this.panel = new PopoverController(this, {
|
|
28
31
|
anchor: () => this.renderRoot?.querySelector('[part="field"]'),
|
|
@@ -47,6 +50,20 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
47
50
|
event.stopPropagation();
|
|
48
51
|
this.dispatchEvent(new Event('change', { bubbles: true, composed: false }));
|
|
49
52
|
};
|
|
53
|
+
this.handleDismiss = (event) => {
|
|
54
|
+
const chip = event.composedPath()[0];
|
|
55
|
+
if (!(chip instanceof HTMLElement) || chip.slot !== 'tags') {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const field = chip.getAttribute('name') ?? '';
|
|
59
|
+
const menu = field === '' ? null : this.menuFor(field);
|
|
60
|
+
if (!menu) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
event.stopPropagation();
|
|
65
|
+
menu.apply(field, []);
|
|
66
|
+
};
|
|
50
67
|
this.handleClear = () => {
|
|
51
68
|
this.requestValue('');
|
|
52
69
|
const control = this.control;
|
|
@@ -59,6 +76,12 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
59
76
|
if (this.tokenKeydown(event, this.value)) {
|
|
60
77
|
return;
|
|
61
78
|
}
|
|
79
|
+
if (event.key === 'Enter' && !(this.open && this.suggestions[this.activeAt])) {
|
|
80
|
+
if (this.readFilters() > 0) {
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
62
85
|
if (!this.suggesting) {
|
|
63
86
|
return;
|
|
64
87
|
}
|
|
@@ -119,6 +142,9 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
119
142
|
this.activeAt = -1;
|
|
120
143
|
this.requestOpen(false);
|
|
121
144
|
};
|
|
145
|
+
this.handleLeading = (event) => {
|
|
146
|
+
this.leading = event.target.assignedNodes({ flatten: true }).length > 0;
|
|
147
|
+
};
|
|
122
148
|
this.handleTokens = () => {
|
|
123
149
|
this.over = this.hideOverflow(this.tokens);
|
|
124
150
|
};
|
|
@@ -164,6 +190,56 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
164
190
|
setSelectionRange(start, end, direction) {
|
|
165
191
|
this.control?.setSelectionRange(start, end, direction);
|
|
166
192
|
}
|
|
193
|
+
get fields() {
|
|
194
|
+
return [
|
|
195
|
+
...this.querySelectorAll('helix-headless-filter-field, helix-filter-field'),
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
menuFor(field) {
|
|
199
|
+
for (const menu of this.querySelectorAll('helix-headless-filter-menu, helix-filter-menu')) {
|
|
200
|
+
if (menu.fields.some((declared) => (declared.name ?? '') === field)) {
|
|
201
|
+
return menu;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
announceFacet(facet) {
|
|
207
|
+
const menu = this.menuFor(facet.field);
|
|
208
|
+
return menu
|
|
209
|
+
? menu.apply(facet.field, facet.values, facet.operator)
|
|
210
|
+
: this.dispatchEvent(new HelixFacetEvent(facet));
|
|
211
|
+
}
|
|
212
|
+
readFilters() {
|
|
213
|
+
const fields = this.fields;
|
|
214
|
+
if (fields.length === 0) {
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
const reading = readFilterText(this.value, fields.map((field) => field.vocabulary));
|
|
218
|
+
if (reading.terms.length === 0) {
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
let taken = 0;
|
|
222
|
+
const refused = [];
|
|
223
|
+
for (const term of reading.terms) {
|
|
224
|
+
if (this.announceFacet(term.facet)) {
|
|
225
|
+
taken += 1;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
refused.push(...term.written);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (taken === 0) {
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
this.requestValue([...refused, reading.text].filter(Boolean).join(' '));
|
|
235
|
+
const control = this.control;
|
|
236
|
+
if (control) {
|
|
237
|
+
restoreValue(control, this.value);
|
|
238
|
+
}
|
|
239
|
+
this.dismissed = false;
|
|
240
|
+
this.settleOpen();
|
|
241
|
+
return taken;
|
|
242
|
+
}
|
|
167
243
|
settleOpen() {
|
|
168
244
|
const focused = this.matches(':focus-within') || this.contains(document.activeElement);
|
|
169
245
|
this.requestOpen(this.suggesting && !this.dismissed && !this.isDisabled && focused);
|
|
@@ -192,8 +268,20 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
192
268
|
last.dismiss();
|
|
193
269
|
}
|
|
194
270
|
}
|
|
271
|
+
connectedCallback() {
|
|
272
|
+
super.connectedCallback();
|
|
273
|
+
this.addEventListener('helix-dismiss', this.handleDismiss);
|
|
274
|
+
}
|
|
275
|
+
disconnectedCallback() {
|
|
276
|
+
super.disconnectedCallback();
|
|
277
|
+
this.removeEventListener('helix-dismiss', this.handleDismiss);
|
|
278
|
+
}
|
|
195
279
|
willUpdate(changed) {
|
|
196
280
|
super.willUpdate(changed);
|
|
281
|
+
if (!this.leadRead && this.childrenKnown) {
|
|
282
|
+
this.leadRead = true;
|
|
283
|
+
this.leading = this.knownChildren.some((child) => child.slot === 'start');
|
|
284
|
+
}
|
|
197
285
|
if (!this.hasUpdated) {
|
|
198
286
|
return;
|
|
199
287
|
}
|
|
@@ -206,6 +294,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
206
294
|
}
|
|
207
295
|
firstUpdated(changed) {
|
|
208
296
|
super.firstUpdated(changed);
|
|
297
|
+
this.finishHydration();
|
|
209
298
|
this.handleTokens();
|
|
210
299
|
this.refreshSuggesting();
|
|
211
300
|
}
|
|
@@ -242,7 +331,9 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
242
331
|
render() {
|
|
243
332
|
return html `${this.renderLabel()}
|
|
244
333
|
<div part="field" @focusin=${this.handleFocusIn} @focusout=${this.handleFocusOut}>
|
|
245
|
-
<
|
|
334
|
+
<div part="lead" ?hidden=${!this.leading}>
|
|
335
|
+
<slot name="start" @slotchange=${this.handleLeading}></slot>
|
|
336
|
+
</div>
|
|
246
337
|
${this.renderTokenRow(html `<slot name="tags" @slotchange=${this.handleTokens}></slot>`, this.over)}
|
|
247
338
|
<input
|
|
248
339
|
part="control"
|
|
@@ -303,6 +394,9 @@ __decorate([
|
|
|
303
394
|
__decorate([
|
|
304
395
|
state()
|
|
305
396
|
], HelixHeadlessFilterInput.prototype, "suggesting", void 0);
|
|
397
|
+
__decorate([
|
|
398
|
+
state()
|
|
399
|
+
], HelixHeadlessFilterInput.prototype, "leading", void 0);
|
|
306
400
|
HelixHeadlessFilterInput = __decorate([
|
|
307
401
|
customElement('helix-headless-filter-input')
|
|
308
402
|
], HelixHeadlessFilterInput);
|
|
@@ -107,7 +107,7 @@ let HelixHeadlessFormatDate = class HelixHeadlessFormatDate extends HelixElement
|
|
|
107
107
|
const now = new Date();
|
|
108
108
|
this.clock = now.getTime();
|
|
109
109
|
const { next } = elapsedTime(this.locale)(moment, now);
|
|
110
|
-
if (!Number.isFinite(next)) {
|
|
110
|
+
if (!Number.isFinite(next) || !this.isConnected) {
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
this.stopTicking = subscribe(next, () => this.tick());
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from '@fusion.dev/helix-core';
|
|
|
12
12
|
export * from './accordion/index.js';
|
|
13
13
|
export * from './announcement/index.js';
|
|
14
14
|
export * from './app-header/index.js';
|
|
15
|
+
export * from './area-chart/index.js';
|
|
15
16
|
export * from './avatar/index.js';
|
|
16
17
|
export * from './badge/index.js';
|
|
17
18
|
export * from './bar-chart/index.js';
|
|
@@ -35,6 +36,7 @@ export * from './copy-button/index.js';
|
|
|
35
36
|
export * from './data-table/index.js';
|
|
36
37
|
export * from './date-input/index.js';
|
|
37
38
|
export * from './date-picker/index.js';
|
|
39
|
+
export * from './description-list/index.js';
|
|
38
40
|
export * from './dialog/index.js';
|
|
39
41
|
export * from './divider/index.js';
|
|
40
42
|
export * from './donut-chart/index.js';
|
|
@@ -61,6 +63,7 @@ export * from './meter/index.js';
|
|
|
61
63
|
export * from './navigation/index.js';
|
|
62
64
|
export * from './notifications/index.js';
|
|
63
65
|
export * from './number-input/index.js';
|
|
66
|
+
export * from './object-inspector/index.js';
|
|
64
67
|
export * from './option/index.js';
|
|
65
68
|
export * from './outline/index.js';
|
|
66
69
|
export * from './pagination/index.js';
|
|
@@ -71,9 +74,11 @@ export * from './qr-code/index.js';
|
|
|
71
74
|
export * from './quote/index.js';
|
|
72
75
|
export * from './radio/index.js';
|
|
73
76
|
export * from './rating/index.js';
|
|
77
|
+
export * from './scatter-chart/index.js';
|
|
74
78
|
export * from './scroll-area/index.js';
|
|
75
79
|
export * from './select/index.js';
|
|
76
80
|
export * from './skeleton/index.js';
|
|
81
|
+
export * from './skip-link/index.js';
|
|
77
82
|
export * from './slider/index.js';
|
|
78
83
|
export * from './sparkline/index.js';
|
|
79
84
|
export * from './speed-dial/index.js';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from '@fusion.dev/helix-core';
|
|
|
3
3
|
export * from './accordion/index.js';
|
|
4
4
|
export * from './announcement/index.js';
|
|
5
5
|
export * from './app-header/index.js';
|
|
6
|
+
export * from './area-chart/index.js';
|
|
6
7
|
export * from './avatar/index.js';
|
|
7
8
|
export * from './badge/index.js';
|
|
8
9
|
export * from './bar-chart/index.js';
|
|
@@ -26,6 +27,7 @@ export * from './copy-button/index.js';
|
|
|
26
27
|
export * from './data-table/index.js';
|
|
27
28
|
export * from './date-input/index.js';
|
|
28
29
|
export * from './date-picker/index.js';
|
|
30
|
+
export * from './description-list/index.js';
|
|
29
31
|
export * from './dialog/index.js';
|
|
30
32
|
export * from './divider/index.js';
|
|
31
33
|
export * from './donut-chart/index.js';
|
|
@@ -52,6 +54,7 @@ export * from './meter/index.js';
|
|
|
52
54
|
export * from './navigation/index.js';
|
|
53
55
|
export * from './notifications/index.js';
|
|
54
56
|
export * from './number-input/index.js';
|
|
57
|
+
export * from './object-inspector/index.js';
|
|
55
58
|
export * from './option/index.js';
|
|
56
59
|
export * from './outline/index.js';
|
|
57
60
|
export * from './pagination/index.js';
|
|
@@ -62,9 +65,11 @@ export * from './qr-code/index.js';
|
|
|
62
65
|
export * from './quote/index.js';
|
|
63
66
|
export * from './radio/index.js';
|
|
64
67
|
export * from './rating/index.js';
|
|
68
|
+
export * from './scatter-chart/index.js';
|
|
65
69
|
export * from './scroll-area/index.js';
|
|
66
70
|
export * from './select/index.js';
|
|
67
71
|
export * from './skeleton/index.js';
|
|
72
|
+
export * from './skip-link/index.js';
|
|
68
73
|
export * from './slider/index.js';
|
|
69
74
|
export * from './sparkline/index.js';
|
|
70
75
|
export * from './speed-dial/index.js';
|
|
@@ -21,6 +21,11 @@ declare const HelixHeadlessLineChart_base: typeof HelixElement & (new (...args:
|
|
|
21
21
|
* **One scale across every series**, the same as the bar chart, because two
|
|
22
22
|
* series drawn to different scales are two charts sharing a picture.
|
|
23
23
|
*
|
|
24
|
+
* **Not the chart for a total made of parts.** `area` washes each series from
|
|
25
|
+
* the floor on its own, so they overlap; `Area Chart` stacks them, so the top
|
|
26
|
+
* edge is the total and each band is a share. A line chart answers how each
|
|
27
|
+
* series moved, an area chart how the whole is made up.
|
|
28
|
+
*
|
|
24
29
|
* **The labels are HTML beside the drawing rather than `<text>` inside it**, for
|
|
25
30
|
* the reason `BarChart` records: an `<svg>` that fills its box scales its type
|
|
26
31
|
* with everything else, the same `font-size` arriving at 10px in a narrow chart
|
|
@@ -57,10 +62,12 @@ declare const HelixHeadlessLineChart_base: typeof HelixElement & (new (...args:
|
|
|
57
62
|
*/
|
|
58
63
|
export declare class HelixHeadlessLineChart extends HelixHeadlessLineChart_base {
|
|
59
64
|
/**
|
|
60
|
-
* Whether a wash is drawn under each line.
|
|
65
|
+
* Whether a wash is drawn under each line, each from the floor on its own.
|
|
66
|
+
* The washes overlap rather than add up: for parts that make a total, use
|
|
67
|
+
* `Area Chart`, which stacks them.
|
|
61
68
|
*
|
|
62
69
|
* Absent is lines alone, which is what suits more than one series: washes
|
|
63
|
-
*
|
|
70
|
+
* overlap and the lower ones are read through the upper. One series over a
|
|
64
71
|
* ground it should feel like a quantity against is the case for this.
|
|
65
72
|
*/
|
|
66
73
|
area: boolean;
|
|
@@ -114,11 +114,10 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
114
114
|
* Whether anything survived the filter, kept as state rather than counted at
|
|
115
115
|
* render time.
|
|
116
116
|
*
|
|
117
|
-
*
|
|
118
|
-
* hiding
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* schedules the one further render that shows it.
|
|
117
|
+
* The options are the consumer's elements rather than this component's
|
|
118
|
+
* markup, so the hiding is done to them in `willUpdate` and the render reads
|
|
119
|
+
* the answer it left here. Counted during render instead, a filter matching
|
|
120
|
+
* nothing left an empty box with no message in it.
|
|
122
121
|
*/
|
|
123
122
|
private hasMatches;
|
|
124
123
|
/**
|
|
@@ -127,8 +126,8 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
127
126
|
* Emptiness is a fact about light DOM children, and the server has none to
|
|
128
127
|
* count: without this the list shipped "Nothing to show" over markup that was
|
|
129
128
|
* about to have options in it, which is the one thing an empty state must
|
|
130
|
-
* never do. `place`
|
|
131
|
-
*
|
|
129
|
+
* never do. `place` does not run on the server, so this is false exactly
|
|
130
|
+
* when the answer is unknown.
|
|
132
131
|
*/
|
|
133
132
|
private counted;
|
|
134
133
|
protected readonly cascade: DisabledCascadeController;
|
|
@@ -231,6 +230,8 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
|
|
|
231
230
|
reveal(): void;
|
|
232
231
|
/** Hides what the filter excludes, and writes the rest onto the options. */
|
|
233
232
|
private place;
|
|
233
|
+
/** Scrolls the active option into view, which needs the rendered list to scroll. */
|
|
234
|
+
private followActive;
|
|
234
235
|
/** Reports the answer and the verdict on it. */
|
|
235
236
|
private sync;
|
|
236
237
|
connectedCallback(): void;
|
|
@@ -274,7 +274,9 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
274
274
|
this.hasMatches = anyShown;
|
|
275
275
|
}
|
|
276
276
|
this.counted = true;
|
|
277
|
-
|
|
277
|
+
}
|
|
278
|
+
followActive() {
|
|
279
|
+
const active = this.shown.find((option) => option.value === this.activeValue);
|
|
278
280
|
if (active) {
|
|
279
281
|
const items = this.renderRoot.querySelector('[part="items"]');
|
|
280
282
|
scrollItemIntoView(active, { padding: 4, ...(items ? { scroller: items } : {}) });
|
|
@@ -300,6 +302,9 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
300
302
|
super.willUpdate(changed);
|
|
301
303
|
hostAria(this.internals, { label: this.label, role: 'listbox' });
|
|
302
304
|
this.internals.ariaMultiSelectable = this.multiple ? 'true' : null;
|
|
305
|
+
if (this.children !== undefined && !this.hydrating) {
|
|
306
|
+
this.place();
|
|
307
|
+
}
|
|
303
308
|
}
|
|
304
309
|
get currentValue() {
|
|
305
310
|
return this.activeValue || (this.value.split(',')[0] ?? '');
|
|
@@ -314,7 +319,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
|
|
|
314
319
|
}
|
|
315
320
|
updated(changed) {
|
|
316
321
|
super.updated(changed);
|
|
317
|
-
this.
|
|
322
|
+
this.followActive();
|
|
318
323
|
this.sync();
|
|
319
324
|
this.linkHost();
|
|
320
325
|
this.linkActive();
|
|
@@ -12,8 +12,9 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
|
12
12
|
*
|
|
13
13
|
* **The role follows the shape.** A row that can be ticked is
|
|
14
14
|
* `menuitemcheckbox` and carries `aria-checked`, because a reader is told "one
|
|
15
|
-
* of these is on" rather than "here is a button"
|
|
16
|
-
* `
|
|
15
|
+
* of these is on" rather than "here is a button"; `exclusive` makes it
|
|
16
|
+
* `menuitemradio`, for a set where only one can be. A row that opens a page
|
|
17
|
+
* keeps `menuitem` and says `aria-haspopup`, since the page it opens is a menu.
|
|
17
18
|
*
|
|
18
19
|
* Whether it is on the page showing is set by the menu as a property, never as
|
|
19
20
|
* an attribute: an attribute written onto a consumer's element after it
|
|
@@ -64,6 +65,19 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
64
65
|
* about who may name a file on somebody's disk rather than a gap here.
|
|
65
66
|
*/
|
|
66
67
|
download: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Whether only one row of the set it belongs to can be on.
|
|
70
|
+
*
|
|
71
|
+
* Announcement and nothing else, the way `checkable` is: this element applies
|
|
72
|
+
* no choice and polices no group, so what it does is tell a reader which kind
|
|
73
|
+
* of set they are in. `menuitemcheckbox` says every row here is independent,
|
|
74
|
+
* which for a run of comparisons, or the values of a filter that compares
|
|
75
|
+
* against one, is the opposite of true.
|
|
76
|
+
*
|
|
77
|
+
* Meaningless without `checkable`, since a row that cannot be ticked is not
|
|
78
|
+
* one of a set of anything.
|
|
79
|
+
*/
|
|
80
|
+
exclusive: boolean;
|
|
67
81
|
/**
|
|
68
82
|
* Where the row goes, which makes it a real link.
|
|
69
83
|
*
|
|
@@ -98,13 +112,14 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
98
112
|
*/
|
|
99
113
|
rel: string;
|
|
100
114
|
/**
|
|
101
|
-
* Whether this row's submenu
|
|
115
|
+
* Whether the page showing is this row's submenu, or one below it.
|
|
102
116
|
*
|
|
103
117
|
* Separate from `onPage`, and it has to be. A row that opened a page is not
|
|
104
118
|
* itself on that page, so both are false for it, and rendering nothing on
|
|
105
119
|
* that basis takes its `slot[name="submenu"]` with it: the rows inside have
|
|
106
|
-
* nowhere to be assigned and are never laid out.
|
|
107
|
-
* page has to keep
|
|
120
|
+
* nowhere to be assigned and are never laid out. Every row on the way down to
|
|
121
|
+
* the open page has to keep its slot, not only the one directly above it.
|
|
122
|
+
* Three levels deep, the topmost row holds the slot the middle one lives in.
|
|
108
123
|
*/
|
|
109
124
|
showingSubmenu: boolean;
|
|
110
125
|
/**
|
|
@@ -131,7 +146,7 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
131
146
|
*/
|
|
132
147
|
get hasSubmenu(): boolean;
|
|
133
148
|
/**
|
|
134
|
-
* Whether anything is slotted into `submenu`.
|
|
149
|
+
* Whether anything is slotted into `submenu`.
|
|
135
150
|
*
|
|
136
151
|
* State rather than a getter over `children`. A getter is read during
|
|
137
152
|
* `render()` with nothing to re-read it, so a submenu arriving after the
|
|
@@ -144,7 +159,40 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
144
159
|
* once at first render. See the note there.
|
|
145
160
|
*/
|
|
146
161
|
private submenu;
|
|
162
|
+
/**
|
|
163
|
+
* Whether anything is slotted before the label.
|
|
164
|
+
*
|
|
165
|
+
* The leading box is drawn only where there is something to put in it, the
|
|
166
|
+
* same way the trailing mark is. What keeps the labels on one edge is the
|
|
167
|
+
* track the menu reserves for the page, not a box on every row, so a row with
|
|
168
|
+
* nothing here takes no box and still lines up with its neighbors.
|
|
169
|
+
*
|
|
170
|
+
* Read from the slot rather than from the light DOM, because the light DOM
|
|
171
|
+
* answer is not reactive: a consumer who adds an icon to one row after first
|
|
172
|
+
* render changes nothing this element is told about, and `slotchange` is
|
|
173
|
+
* exactly the thing that is.
|
|
174
|
+
*/
|
|
175
|
+
private start;
|
|
176
|
+
/**
|
|
177
|
+
* Both answers, from the children, before each render.
|
|
178
|
+
*
|
|
179
|
+
* The slots exist only once a render has made them: read through them, the
|
|
180
|
+
* first render drew no caret and no leading box and a second render had to
|
|
181
|
+
* follow it. `slotchange` still reads them, for a child that arrives later.
|
|
182
|
+
* Not on the server, where `children` is undefined, nor while hydrating what
|
|
183
|
+
* one sent.
|
|
184
|
+
*/
|
|
185
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
147
186
|
protected firstUpdated(changed: PropertyValues): void;
|
|
187
|
+
/**
|
|
188
|
+
* Re-reads whether anything is before the label.
|
|
189
|
+
*
|
|
190
|
+
* The slot itself is always rendered and it is the box around it that comes
|
|
191
|
+
* and goes, which is what makes this safe where `readSubmenu` needed the note
|
|
192
|
+
* it carries: there is one `slot[name="start"]` in one template branch, so it
|
|
193
|
+
* is never rebuilt and never fires on its way out.
|
|
194
|
+
*/
|
|
195
|
+
private readonly readStart;
|
|
148
196
|
/**
|
|
149
197
|
* Re-reads whether anything is in the submenu, from whichever slot is current.
|
|
150
198
|
*
|