@fusion.dev/helix-headless 0.23.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 +48376 -26638
- 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 +31 -6
- package/dist/menu/item/menu-item.js +18 -5
- package/dist/menu/root/menu-root.js +7 -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
|
@@ -5,7 +5,7 @@ registerElementEvents({
|
|
|
5
5
|
'helix-headless-app-header': ['helix-scrolled'],
|
|
6
6
|
'helix-headless-avatar': ['error', 'load'],
|
|
7
7
|
'helix-headless-carousel': ['helix-select'],
|
|
8
|
-
'helix-headless-chat': ['helix-before-pin'],
|
|
8
|
+
'helix-headless-chat': ['helix-before-pin', 'helix-history', 'helix-send'],
|
|
9
9
|
'helix-headless-chat-composer': ['helix-send'],
|
|
10
10
|
'helix-headless-chat-message': ['helix-send'],
|
|
11
11
|
'helix-headless-checkbox': ['change', 'helix-before-toggle', 'input'],
|
|
@@ -38,7 +38,13 @@ registerElementEvents({
|
|
|
38
38
|
],
|
|
39
39
|
'helix-headless-command': ['helix-before-close', 'helix-before-open', 'helix-select', 'toggle'],
|
|
40
40
|
'helix-headless-copy-button': ['helix-copy'],
|
|
41
|
-
'helix-headless-data-table': [
|
|
41
|
+
'helix-headless-data-table': [
|
|
42
|
+
'helix-filter',
|
|
43
|
+
'helix-navigate',
|
|
44
|
+
'helix-reorder',
|
|
45
|
+
'helix-select',
|
|
46
|
+
'helix-sort',
|
|
47
|
+
],
|
|
42
48
|
'helix-headless-date-input': [
|
|
43
49
|
'change',
|
|
44
50
|
'helix-before-change',
|
|
@@ -53,14 +59,19 @@ registerElementEvents({
|
|
|
53
59
|
'helix-headless-drilldown': ['helix-select'],
|
|
54
60
|
'helix-headless-drilldown-item': ['helix-select'],
|
|
55
61
|
'helix-headless-file-input': ['change', 'helix-before-change', 'input'],
|
|
62
|
+
'helix-headless-filter-chip': ['helix-dismiss'],
|
|
56
63
|
'helix-headless-filter-input': [
|
|
57
64
|
'change',
|
|
58
65
|
'helix-before-change',
|
|
59
66
|
'helix-before-close',
|
|
60
67
|
'helix-before-open',
|
|
68
|
+
'helix-dismiss',
|
|
69
|
+
'helix-facet',
|
|
70
|
+
'helix-select',
|
|
61
71
|
'input',
|
|
62
72
|
'toggle',
|
|
63
73
|
],
|
|
74
|
+
'helix-headless-filter-menu': ['helix-facet'],
|
|
64
75
|
'helix-headless-heatmap': ['helix-resize'],
|
|
65
76
|
'helix-headless-image': ['error', 'load'],
|
|
66
77
|
'helix-headless-input': ['change', 'helix-before-change', 'input'],
|
|
@@ -70,8 +81,10 @@ registerElementEvents({
|
|
|
70
81
|
'helix-headless-menu': ['helix-before-close', 'helix-before-open', 'helix-select', 'toggle'],
|
|
71
82
|
'helix-headless-menu-item': ['helix-select'],
|
|
72
83
|
'helix-headless-notification': ['helix-dismiss', 'helix-read'],
|
|
73
|
-
'helix-headless-
|
|
84
|
+
'helix-headless-notification-group': ['helix-before-close', 'helix-before-open'],
|
|
85
|
+
'helix-headless-notifications': ['helix-dismiss', 'helix-read'],
|
|
74
86
|
'helix-headless-number-input': ['change', 'helix-before-change', 'input'],
|
|
87
|
+
'helix-headless-object-inspector': ['helix-copy'],
|
|
75
88
|
'helix-headless-option': ['helix-select'],
|
|
76
89
|
'helix-headless-outline': ['helix-before-activate'],
|
|
77
90
|
'helix-headless-pagination': ['helix-navigate'],
|
|
@@ -79,7 +92,7 @@ registerElementEvents({
|
|
|
79
92
|
'helix-headless-pin-input': ['change', 'helix-before-change', 'helix-complete', 'input'],
|
|
80
93
|
'helix-headless-popover': ['helix-before-close', 'helix-before-open', 'toggle'],
|
|
81
94
|
'helix-headless-radio': ['helix-select'],
|
|
82
|
-
'helix-headless-radio-group': ['change', 'helix-before-change', 'input'],
|
|
95
|
+
'helix-headless-radio-group': ['change', 'helix-before-change', 'helix-select', 'input'],
|
|
83
96
|
'helix-headless-rating': ['change', 'helix-before-change', 'input'],
|
|
84
97
|
'helix-headless-select': [
|
|
85
98
|
'change',
|
|
@@ -90,13 +103,19 @@ registerElementEvents({
|
|
|
90
103
|
'toggle',
|
|
91
104
|
],
|
|
92
105
|
'helix-headless-slider': ['change', 'helix-before-change', 'input'],
|
|
93
|
-
'helix-headless-speed-dial': [
|
|
106
|
+
'helix-headless-speed-dial': [
|
|
107
|
+
'helix-before-close',
|
|
108
|
+
'helix-before-open',
|
|
109
|
+
'helix-select',
|
|
110
|
+
'toggle',
|
|
111
|
+
],
|
|
94
112
|
'helix-headless-speed-dial-item': ['helix-select'],
|
|
95
113
|
'helix-headless-splitter': ['helix-before-collapse', 'helix-before-resize'],
|
|
96
114
|
'helix-headless-step': ['helix-select'],
|
|
97
|
-
'helix-headless-steps': ['helix-activate', 'helix-before-activate'],
|
|
115
|
+
'helix-headless-steps': ['helix-activate', 'helix-before-activate', 'helix-select'],
|
|
98
116
|
'helix-headless-switch': ['change', 'helix-before-toggle', 'input'],
|
|
99
117
|
'helix-headless-tab': ['helix-select'],
|
|
118
|
+
'helix-headless-table-column': ['helix-before-resize'],
|
|
100
119
|
'helix-headless-tabs': ['helix-activate', 'helix-before-activate', 'helix-select'],
|
|
101
120
|
'helix-headless-tag': ['helix-dismiss'],
|
|
102
121
|
'helix-headless-textarea': ['change', 'helix-before-change', 'input'],
|
|
@@ -109,6 +128,7 @@ registerElementEvents({
|
|
|
109
128
|
'toggle',
|
|
110
129
|
],
|
|
111
130
|
'helix-headless-time-picker': ['change', 'helix-before-change', 'input'],
|
|
131
|
+
'helix-headless-toast': ['helix-dismiss'],
|
|
112
132
|
'helix-headless-toast-item': ['helix-dismiss'],
|
|
113
133
|
'helix-headless-toggle': ['helix-select'],
|
|
114
134
|
'helix-headless-toggle-group': ['change', 'helix-before-change', 'helix-select', 'input'],
|
|
@@ -117,6 +137,7 @@ registerElementEvents({
|
|
|
117
137
|
'helix-before-activate',
|
|
118
138
|
'helix-before-close',
|
|
119
139
|
'helix-complete',
|
|
140
|
+
'helix-tour-ask',
|
|
120
141
|
'toggle',
|
|
121
142
|
],
|
|
122
143
|
'helix-headless-tour-stop': ['helix-tour-ask'],
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
declare const HelixHeadlessFilterChip_base: (new (...args: any[]) => import("@fusion.dev/helix-core").DismissibleElement) & typeof HelixElement;
|
|
4
|
+
/**
|
|
5
|
+
* @summary One filter in force: a field, how it is compared, and to what.
|
|
6
|
+
*
|
|
7
|
+
* @description
|
|
8
|
+
* A chip is the readable form of the thing `FilterField` declares. It carries
|
|
9
|
+
* the three parts a filter has and a control that asks for it to go.
|
|
10
|
+
*
|
|
11
|
+
* **The operator is drawn only when it is worth drawing**, which is the whole
|
|
12
|
+
* reason the common case reads as plainly as it does:
|
|
13
|
+
*
|
|
14
|
+
* ```
|
|
15
|
+
* Disposition refunded, chargeback the field's usual comparison, unwritten
|
|
16
|
+
* Disposition is not refunded a comparison somebody chose, so it shows
|
|
17
|
+
* Created after 1 Aug a scalar, which has no usual comparison
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* A reader ticking two values in a menu gets the first, with no jargon on it. A
|
|
21
|
+
* reader who reaches for a comparison gets the third, which explains itself the
|
|
22
|
+
* first time they see one. The rule is one line: **omit `operator-label` for the
|
|
23
|
+
* field's default operator and pass it for every other.** Whoever builds the
|
|
24
|
+
* chip holds the field, and the field knows which its default is.
|
|
25
|
+
*
|
|
26
|
+
* **It shows a filter and never applies one.** Dismissing is a request, the way
|
|
27
|
+
* it is on `Tag` and `Announcement`: the chip is the consumer's element in a
|
|
28
|
+
* consumer's list, and taking it out from under them would put their model and
|
|
29
|
+
* the document out of step. `preventDefault()` on the `helix-dismiss` is a
|
|
30
|
+
* refusal, and that is also how a consumer takes control of the interaction for
|
|
31
|
+
* one press: `requestChange` documents the whole pattern, and its first line is
|
|
32
|
+
* that control is an action rather than a mode.
|
|
33
|
+
*
|
|
34
|
+
* **Changing the comparison belongs to whatever is slotted into `operator`.** A
|
|
35
|
+
* menu trigger there makes the segment a control; the text fallback makes it a
|
|
36
|
+
* label. This element does not learn either, for the reason `FilterOperator`
|
|
37
|
+
* gives: what the words mean is the domain's.
|
|
38
|
+
*
|
|
39
|
+
* @since 0.23.0
|
|
40
|
+
* @status experimental
|
|
41
|
+
* @category forms
|
|
42
|
+
* @aliases FilterToken, FacetChip
|
|
43
|
+
* @documentation https://helix.fusion.dev/components/filter-input
|
|
44
|
+
*
|
|
45
|
+
* @slot - What the field is compared to, which is usually text and may be
|
|
46
|
+
* anything a consumer wants read there.
|
|
47
|
+
* @slot field - Replaces the field's name, for a chip that wants a mark beside
|
|
48
|
+
* it or a shortened form.
|
|
49
|
+
* @slot operator - Replaces the comparison, and is where a control that changes
|
|
50
|
+
* it goes.
|
|
51
|
+
*
|
|
52
|
+
* @event {HelixDismissEvent} helix-dismiss - Fired when the reader asks to remove the filter. Cancelable: refusing it takes the decision and nothing moves.
|
|
53
|
+
*
|
|
54
|
+
* @csspart dismiss - The control that asks to remove the filter.
|
|
55
|
+
* @csspart dismiss-mark - The glyph on the dismiss control, where none is slotted.
|
|
56
|
+
* @csspart field - The name of what is being filtered.
|
|
57
|
+
* @csspart operator - How it is compared, drawn only where there is one to draw.
|
|
58
|
+
* @csspart values - What it is compared to.
|
|
59
|
+
*/
|
|
60
|
+
export declare class HelixHeadlessFilterChip extends HelixHeadlessFilterChip_base {
|
|
61
|
+
dismissLabel: string;
|
|
62
|
+
/**
|
|
63
|
+
* The field this filter is on, as a reader sees it.
|
|
64
|
+
*
|
|
65
|
+
* The slot's fallback rather than the slot's replacement: a consumer who wants
|
|
66
|
+
* a mark beside the name slots `field` and this goes unused.
|
|
67
|
+
*/
|
|
68
|
+
label: string;
|
|
69
|
+
/**
|
|
70
|
+
* The field this filter is on, as the consumer's model calls it.
|
|
71
|
+
*
|
|
72
|
+
* Never shown. Reflected, because a consumer removing one filter from a list
|
|
73
|
+
* of them selects on this, and so undefaulted for the reason `operator` gives.
|
|
74
|
+
*
|
|
75
|
+
* **A chip built in script should be given the attribute, not the property**,
|
|
76
|
+
* if the next thing to happen might select on it. Reflection is written on
|
|
77
|
+
* this element's next update rather than during the assignment, so a second
|
|
78
|
+
* `helix-facet` for the same field arriving in the same frame selects nothing,
|
|
79
|
+
* builds a second chip, and leaves the row holding two of one filter. The
|
|
80
|
+
* lookup then finds whichever came first, so dismissing one chip takes the
|
|
81
|
+
* other away. `setAttribute('name', field)` is true immediately and costs
|
|
82
|
+
* nothing; the property is for markup and for frameworks that write both.
|
|
83
|
+
*/
|
|
84
|
+
name?: string;
|
|
85
|
+
/**
|
|
86
|
+
* What the consumer's own model calls the comparison in force.
|
|
87
|
+
*
|
|
88
|
+
* Never shown, and it is what a consumer matches on when the chip comes back
|
|
89
|
+
* to them. `label` translates; this does not.
|
|
90
|
+
*
|
|
91
|
+
* Reflected and so undefaulted: a reflected property with a default writes an
|
|
92
|
+
* attribute on upgrade that the server never wrote, and the two markups differ
|
|
93
|
+
* before a reader has touched anything.
|
|
94
|
+
*/
|
|
95
|
+
operator?: string;
|
|
96
|
+
/**
|
|
97
|
+
* The comparison, as a reader sees it, and empty for the field's usual one.
|
|
98
|
+
*
|
|
99
|
+
* **Empty is the whole design and not an oversight.** A chip drawn for the
|
|
100
|
+
* comparison a field is normally filtered by says the field and the values and
|
|
101
|
+
* nothing between them, because a word every chip carries is a word nobody
|
|
102
|
+
* reads. The moment somebody picks a different comparison it appears, which
|
|
103
|
+
* makes an unusual filter look unusual.
|
|
104
|
+
*/
|
|
105
|
+
operatorLabel: string;
|
|
106
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
107
|
+
protected render(): TemplateResult;
|
|
108
|
+
}
|
|
109
|
+
declare global {
|
|
110
|
+
interface HTMLElementTagNameMap {
|
|
111
|
+
'helix-headless-filter-chip': HelixHeadlessFilterChip;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { customElement, HelixElement, MixinDismissible } from '@fusion.dev/helix-core';
|
|
8
|
+
import { x } from '@fusion.dev/helix-icons';
|
|
9
|
+
import { html, nothing } from 'lit';
|
|
10
|
+
import { property } from 'lit/decorators.js';
|
|
11
|
+
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
|
+
let HelixHeadlessFilterChip = class HelixHeadlessFilterChip extends MixinDismissible(HelixElement) {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(...arguments);
|
|
15
|
+
this.dismissLabel = 'Remove filter';
|
|
16
|
+
this.label = '';
|
|
17
|
+
this.operatorLabel = '';
|
|
18
|
+
}
|
|
19
|
+
firstUpdated(changed) {
|
|
20
|
+
super.firstUpdated(changed);
|
|
21
|
+
this.finishHydration();
|
|
22
|
+
}
|
|
23
|
+
render() {
|
|
24
|
+
const operator = this.operatorLabel !== '' || this.knownChildren.some((child) => child.slot === 'operator');
|
|
25
|
+
const values = !this.childrenKnown ||
|
|
26
|
+
[...this.childNodes].some((node) => node instanceof Element ? node.slot === '' : (node.textContent ?? '').trim() !== '');
|
|
27
|
+
return html `<span part="field"><slot name="field">${this.label}</slot></span>
|
|
28
|
+
${operator
|
|
29
|
+
? html `<span part="operator"><slot name="operator">${this.operatorLabel}</slot></span>`
|
|
30
|
+
: nothing}
|
|
31
|
+
${values ? html `<span part="values"><slot></slot></span>` : nothing}
|
|
32
|
+
${this.dismissible
|
|
33
|
+
? html `<button
|
|
34
|
+
part="dismiss"
|
|
35
|
+
type="button"
|
|
36
|
+
aria-label=${this.dismissLabel}
|
|
37
|
+
@click=${() => this.dismiss()}
|
|
38
|
+
>
|
|
39
|
+
<svg part="dismiss-mark" fill="currentColor" viewBox=${x.viewBox} aria-hidden="true">
|
|
40
|
+
${unsafeSVG(x.content)}
|
|
41
|
+
</svg>
|
|
42
|
+
</button>`
|
|
43
|
+
: nothing}`;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
__decorate([
|
|
47
|
+
property()
|
|
48
|
+
], HelixHeadlessFilterChip.prototype, "label", void 0);
|
|
49
|
+
__decorate([
|
|
50
|
+
property({ reflect: true })
|
|
51
|
+
], HelixHeadlessFilterChip.prototype, "name", void 0);
|
|
52
|
+
__decorate([
|
|
53
|
+
property({ reflect: true })
|
|
54
|
+
], HelixHeadlessFilterChip.prototype, "operator", void 0);
|
|
55
|
+
__decorate([
|
|
56
|
+
property({ attribute: 'operator-label' })
|
|
57
|
+
], HelixHeadlessFilterChip.prototype, "operatorLabel", void 0);
|
|
58
|
+
HelixHeadlessFilterChip = __decorate([
|
|
59
|
+
customElement('helix-headless-filter-chip')
|
|
60
|
+
], HelixHeadlessFilterChip);
|
|
61
|
+
export { HelixHeadlessFilterChip };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
+
import { nothing, type PropertyValues } from 'lit';
|
|
3
|
+
import type { HelixHeadlessOption } from '../../option/index.js';
|
|
4
|
+
import { type FilterGrammarField } from '../grammar.js';
|
|
5
|
+
import type { HelixHeadlessFilterOperator } from '../operator/filter-operator.js';
|
|
6
|
+
/**
|
|
7
|
+
* @summary One thing a set can be filtered by, and how.
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* **A declaration, not a control.** It renders nothing and is never seen. A
|
|
11
|
+
* consumer writes one per field, with the comparisons that field supports and
|
|
12
|
+
* the values it supports them on, and the filter input reads them to build its
|
|
13
|
+
* menu:
|
|
14
|
+
*
|
|
15
|
+
* ```html
|
|
16
|
+
* <helix-headless-filter-field name="disposition" label="Disposition">
|
|
17
|
+
* <helix-headless-filter-operator name="any" label="is any of" default>
|
|
18
|
+
* </helix-headless-filter-operator>
|
|
19
|
+
* <helix-headless-filter-operator name="none" label="is none of">
|
|
20
|
+
* </helix-headless-filter-operator>
|
|
21
|
+
* <helix-headless-option value="refunded">Refunded</helix-headless-option>
|
|
22
|
+
* </helix-headless-filter-field>
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* **Operators are per field and never global.** `is after` belongs to a date and
|
|
26
|
+
* `has all of` to a set, and a field that supports one comparison shows no
|
|
27
|
+
* chooser at all. A global list would offer every field every word and leave the
|
|
28
|
+
* component deciding which of them made sense, which is the domain question it
|
|
29
|
+
* declines to answer.
|
|
30
|
+
*
|
|
31
|
+
* **A field that declares none gets two, and they are the ones the punctuation
|
|
32
|
+
* can say.** `is any of` and `is none of` are what `field:a,b` and `-field:a,b`
|
|
33
|
+
* mean, so declaring them was ceremony for the case every product has; the
|
|
34
|
+
* example above is what a consumer writes when the words matter, not the price
|
|
35
|
+
* of admission. Declaring one comparison replaces both rather than joining
|
|
36
|
+
* them — see `vocabulary`, which is where that is decided once for the menu and
|
|
37
|
+
* the grammar together.
|
|
38
|
+
*
|
|
39
|
+
* **Values are `Option`s, which is the element a consumer already has.** They
|
|
40
|
+
* carry a value, a label and a disabled state, and a picker built on `Menu` or
|
|
41
|
+
* `Listbox` can render them without this element inventing a second way to say
|
|
42
|
+
* the same thing. A field whose values cannot be enumerated declares none, and
|
|
43
|
+
* what fills a chip then is whatever the consumer types.
|
|
44
|
+
*
|
|
45
|
+
* @since 0.23.0
|
|
46
|
+
* @status experimental
|
|
47
|
+
* @category forms
|
|
48
|
+
* @aliases Facet, Dimension
|
|
49
|
+
* @documentation https://helix.fusion.dev/components/filter-input
|
|
50
|
+
*
|
|
51
|
+
* @slot - The comparisons this field supports, as `FilterOperator`s, and the
|
|
52
|
+
* values it supports them on, as `Option`s. Neither is rendered: they are read.
|
|
53
|
+
*/
|
|
54
|
+
export declare class HelixHeadlessFilterField extends HelixElement {
|
|
55
|
+
/**
|
|
56
|
+
* What a reader is shown, in the menu and on every chip built from this field.
|
|
57
|
+
*/
|
|
58
|
+
label: string;
|
|
59
|
+
/**
|
|
60
|
+
* What the consumer's own model calls this field.
|
|
61
|
+
*
|
|
62
|
+
* Never shown, and what arrives in the filter this helps build.
|
|
63
|
+
*
|
|
64
|
+
* Reflected and so undefaulted: a reflected property with a default writes an
|
|
65
|
+
* attribute on upgrade that the server never wrote.
|
|
66
|
+
*/
|
|
67
|
+
name?: string;
|
|
68
|
+
/**
|
|
69
|
+
* The comparisons declared under this field, in the order they were written.
|
|
70
|
+
*
|
|
71
|
+
* Read from the light DOM rather than from a slot, because there is no slot:
|
|
72
|
+
* this element renders nothing, so nothing is ever assigned. Order is the
|
|
73
|
+
* consumer's, which is the order a chooser should offer them in.
|
|
74
|
+
*
|
|
75
|
+
* **As declared, so empty where nothing was.** The defaults are applied by
|
|
76
|
+
* `vocabulary` and not here: this getter answers what the consumer wrote, and
|
|
77
|
+
* a caller that wants what the field *supports* wants the other one.
|
|
78
|
+
*/
|
|
79
|
+
get operators(): HelixHeadlessFilterOperator[];
|
|
80
|
+
/**
|
|
81
|
+
* The comparison a chip leaves unwritten, or nothing where the consumer named
|
|
82
|
+
* none.
|
|
83
|
+
*
|
|
84
|
+
* **The first one marked, and a second is reported rather than resolved.** Two
|
|
85
|
+
* defaults on one field is a consumer saying two different things, and picking
|
|
86
|
+
* one of them quietly would show a chip whose operator is missing and whose
|
|
87
|
+
* meaning is not the one they wrote. A field with no default has none: every
|
|
88
|
+
* chip built from it then spells its operator out, which is correct for a
|
|
89
|
+
* field whose comparisons are all equally surprising.
|
|
90
|
+
*/
|
|
91
|
+
get defaultOperator(): HelixHeadlessFilterOperator | null;
|
|
92
|
+
/**
|
|
93
|
+
* The values declared under this field, in the order they were written.
|
|
94
|
+
*
|
|
95
|
+
* Empty is meaningful rather than missing: a field whose values cannot be
|
|
96
|
+
* listed, a free text search or an open ended number, declares none and the
|
|
97
|
+
* picker offers typing instead of choosing.
|
|
98
|
+
*/
|
|
99
|
+
get values(): HelixHeadlessOption[];
|
|
100
|
+
/**
|
|
101
|
+
* This field flattened into what the menu renders and the grammar reads.
|
|
102
|
+
*
|
|
103
|
+
* **The one place the defaults are applied.** A field that declares no
|
|
104
|
+
* comparisons supports `is any of` and `is none of`, which is what the typed
|
|
105
|
+
* grammar can say and so what the picker offers; declaring one replaces both,
|
|
106
|
+
* because a set half inherited and half written is one nobody can read off the
|
|
107
|
+
* markup. Two things need this answer and neither should be resolving it
|
|
108
|
+
* itself.
|
|
109
|
+
*
|
|
110
|
+
* Data rather than elements, so the grammar stays a module with no DOM in it
|
|
111
|
+
* and the menu renders rows from the same shape whether the consumer wrote the
|
|
112
|
+
* operators or not.
|
|
113
|
+
*/
|
|
114
|
+
get vocabulary(): FilterGrammarField;
|
|
115
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
116
|
+
/**
|
|
117
|
+
* Nothing, which is what makes this a declaration.
|
|
118
|
+
*
|
|
119
|
+
* No `<slot>`, so none of the children a consumer wrote is painted. See
|
|
120
|
+
* `FilterOperator`'s note, which records the mechanism.
|
|
121
|
+
*/
|
|
122
|
+
protected render(): typeof nothing;
|
|
123
|
+
}
|
|
124
|
+
declare global {
|
|
125
|
+
interface HTMLElementTagNameMap {
|
|
126
|
+
'helix-headless-filter-field': HelixHeadlessFilterField;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { customElement, HelixElement } from '@fusion.dev/helix-core';
|
|
8
|
+
import { nothing } from 'lit';
|
|
9
|
+
import { property } from 'lit/decorators.js';
|
|
10
|
+
import { DEFAULT_FILTER_OPERATORS } from '../grammar.js';
|
|
11
|
+
let HelixHeadlessFilterField = class HelixHeadlessFilterField extends HelixElement {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
this.label = '';
|
|
15
|
+
}
|
|
16
|
+
get operators() {
|
|
17
|
+
return [
|
|
18
|
+
...this.querySelectorAll(':scope > helix-headless-filter-operator, :scope > helix-filter-operator'),
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
get defaultOperator() {
|
|
22
|
+
const marked = this.operators.filter((operator) => operator.default);
|
|
23
|
+
if (marked.length > 1) {
|
|
24
|
+
console.warn(`\`${this.name || 'A filter field'}\` names ${marked.length} default operators, and a chip can only leave one unwritten. Mark one.`);
|
|
25
|
+
}
|
|
26
|
+
return marked[0] ?? null;
|
|
27
|
+
}
|
|
28
|
+
get values() {
|
|
29
|
+
return [
|
|
30
|
+
...this.querySelectorAll(':scope > helix-headless-option, :scope > helix-option'),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
get vocabulary() {
|
|
34
|
+
const declared = this.operators;
|
|
35
|
+
const unwritten = this.defaultOperator;
|
|
36
|
+
return {
|
|
37
|
+
label: this.label,
|
|
38
|
+
name: this.name ?? '',
|
|
39
|
+
operators: declared.length > 0
|
|
40
|
+
? declared.map((operator) => ({
|
|
41
|
+
arity: operator.arity ?? 'many',
|
|
42
|
+
default: operator === unwritten,
|
|
43
|
+
label: operator.label,
|
|
44
|
+
name: operator.name ?? '',
|
|
45
|
+
}))
|
|
46
|
+
: DEFAULT_FILTER_OPERATORS,
|
|
47
|
+
values: this.values.map((option) => ({
|
|
48
|
+
label: option.textContent?.trim() || option.value,
|
|
49
|
+
value: option.value,
|
|
50
|
+
})),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
willUpdate(changed) {
|
|
54
|
+
super.willUpdate(changed);
|
|
55
|
+
this.internals.role = 'none';
|
|
56
|
+
}
|
|
57
|
+
render() {
|
|
58
|
+
return nothing;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
__decorate([
|
|
62
|
+
property()
|
|
63
|
+
], HelixHeadlessFilterField.prototype, "label", void 0);
|
|
64
|
+
__decorate([
|
|
65
|
+
property({ reflect: true })
|
|
66
|
+
], HelixHeadlessFilterField.prototype, "name", void 0);
|
|
67
|
+
HelixHeadlessFilterField = __decorate([
|
|
68
|
+
customElement('helix-headless-filter-field')
|
|
69
|
+
], HelixHeadlessFilterField);
|
|
70
|
+
export { HelixHeadlessFilterField };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a typed filter against what a consumer declared.
|
|
3
|
+
*
|
|
4
|
+
* **Helix owns the punctuation, the consumer owns the words.** `field:a,b` is a
|
|
5
|
+
* filter in every product that has ever had one, and a library that made every
|
|
6
|
+
* consumer write that parse again was charging them for a decision nobody
|
|
7
|
+
* disagrees about. So the shape is here: a key, a colon, a comma separated list,
|
|
8
|
+
* and a leading `-` for the negation. What a key or a value *means* is still
|
|
9
|
+
* never asked, which is the line `FilterField` draws and this does not cross:
|
|
10
|
+
* nothing here compares anything, and what comes out is the same `helix-facet`
|
|
11
|
+
* the picker announces, for the consumer to answer however they answer it.
|
|
12
|
+
*
|
|
13
|
+
* **It reads only what was declared.** A key is a field's `name` or its `label`;
|
|
14
|
+
* a value is an `Option`'s `value` or its text. Anything else is left alone and
|
|
15
|
+
* comes back as text, which is what makes a search box that is also a filter
|
|
16
|
+
* possible: a reader types `refund tuesday disposition:settled` and two thirds
|
|
17
|
+
* of that is still a search.
|
|
18
|
+
*
|
|
19
|
+
* **Where the consumer's vocabulary starts, this stops.** The negation is bound
|
|
20
|
+
* to the operator named `none` and to nothing else, so a field that declares its
|
|
21
|
+
* own comparisons and no `none` has no negation to type: `-created:today` is
|
|
22
|
+
* text, and the picker is where that filter gets built. Guessing which of a
|
|
23
|
+
* consumer's operators a `-` meant is the domain question, and this is the one
|
|
24
|
+
* place it would be tempting to answer.
|
|
25
|
+
*
|
|
26
|
+
* Its own module rather than sitting in the element, because it is string
|
|
27
|
+
* handling with no DOM in it: this is the half worth testing directly.
|
|
28
|
+
*/
|
|
29
|
+
import type { FacetDetail } from '@fusion.dev/helix-core';
|
|
30
|
+
/** One comparison a field supports, as the grammar needs it. */
|
|
31
|
+
export interface FilterGrammarOperator {
|
|
32
|
+
/**
|
|
33
|
+
* How many values it takes: `none` is complete on its own, `one` is a scalar,
|
|
34
|
+
* `many` is a set. Undeclared reads as `many`, which `FilterOperator` records.
|
|
35
|
+
*/
|
|
36
|
+
arity: 'many' | 'none' | 'one';
|
|
37
|
+
/** Whether this is the one a chip leaves unwritten. */
|
|
38
|
+
default: boolean;
|
|
39
|
+
/** What a reader is shown. */
|
|
40
|
+
label: string;
|
|
41
|
+
/** What the consumer's model calls it. */
|
|
42
|
+
name: string;
|
|
43
|
+
}
|
|
44
|
+
/** One value a field can be compared to, as the grammar needs it. */
|
|
45
|
+
export interface FilterGrammarValue {
|
|
46
|
+
/** What a reader is shown, and one of the two spellings it can be typed as. */
|
|
47
|
+
label: string;
|
|
48
|
+
/** What the consumer's model calls it. */
|
|
49
|
+
value: string;
|
|
50
|
+
}
|
|
51
|
+
/** One field and everything declared under it, as the grammar needs it. */
|
|
52
|
+
export interface FilterGrammarField {
|
|
53
|
+
/** What a reader is shown. */
|
|
54
|
+
label: string;
|
|
55
|
+
/** What the consumer's model calls it. */
|
|
56
|
+
name: string;
|
|
57
|
+
/** The comparisons it supports, the field's own or the defaults. */
|
|
58
|
+
operators: readonly FilterGrammarOperator[];
|
|
59
|
+
/** The values it can be compared to, or none where they cannot be listed. */
|
|
60
|
+
values: readonly FilterGrammarValue[];
|
|
61
|
+
}
|
|
62
|
+
/** One filter read out of the text. */
|
|
63
|
+
export interface FilterGrammarTerm {
|
|
64
|
+
/** What the picker would have announced for this, unchanged. */
|
|
65
|
+
facet: FacetDetail;
|
|
66
|
+
/** The pieces of text it was read from, so a caller can take exactly them. */
|
|
67
|
+
written: readonly string[];
|
|
68
|
+
}
|
|
69
|
+
/** What a line of text turned out to say. */
|
|
70
|
+
export interface FilterGrammarReading {
|
|
71
|
+
/** What it said, one per field. */
|
|
72
|
+
terms: readonly FilterGrammarTerm[];
|
|
73
|
+
/** What it did not, in the order it was written. */
|
|
74
|
+
text: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The comparisons a field has when it declares none.
|
|
78
|
+
*
|
|
79
|
+
* **Two, because two is what the punctuation can say.** `field:a,b` and
|
|
80
|
+
* `-field:a,b` are the whole default grammar, so a third default would be one
|
|
81
|
+
* the picker offered and nobody could type. A field wanting `is after` declares
|
|
82
|
+
* it, and declaring anything replaces these rather than joining them: a set half
|
|
83
|
+
* inherited from a library and half written by hand is one nobody can read off
|
|
84
|
+
* the markup.
|
|
85
|
+
*/
|
|
86
|
+
export declare const DEFAULT_FILTER_OPERATORS: readonly FilterGrammarOperator[];
|
|
87
|
+
/**
|
|
88
|
+
* Reads a line of text as filters and whatever was not one.
|
|
89
|
+
*
|
|
90
|
+
* **One comparison per field, and a second is left as text.** `helix-facet`
|
|
91
|
+
* carries a field's whole set, so `disposition:a -disposition:b` has no event
|
|
92
|
+
* shape to arrive in and the picker cannot build it either. Announcing the
|
|
93
|
+
* second over the first would drop a filter the reader wrote and spelled
|
|
94
|
+
* correctly; leaving it in the box shows them it was not taken.
|
|
95
|
+
*/
|
|
96
|
+
export declare function readFilterText(text: string, fields: readonly FilterGrammarField[]): FilterGrammarReading;
|