@fusion.dev/helix-core 0.103.2 → 0.104.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 +3668 -1787
- package/dist/controllers/arrival-controller/arrival-controller.d.ts +43 -0
- package/dist/controllers/arrival-controller/arrival-controller.js +35 -0
- package/dist/controllers/cue-controller/cue-controller.d.ts +8 -0
- package/dist/controllers/cue-controller/cue-controller.js +4 -1
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +4 -0
- package/dist/controllers/remote-playback-controller/remote-playback-controller.d.ts +15 -0
- package/dist/controllers/remote-playback-controller/remote-playback-controller.js +19 -3
- package/dist/events/events.d.ts +253 -6
- package/dist/events/events.js +144 -0
- package/dist/helix-form-element/helix-form-element.d.ts +9 -0
- package/dist/helix-form-element/helix-form-element.js +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +7 -1
- package/dist/mixins/mixin-collection/mixin-collection.d.ts +6 -0
- package/dist/mixins/mixin-collection/mixin-collection.js +5 -1
- package/dist/mixins/mixin-controller-open/mixin-controller-open.js +3 -3
- package/dist/mixins/mixin-controller-value/mixin-controller-value.d.ts +1 -1
- package/dist/mixins/mixin-controller-value/mixin-controller-value.js +2 -1
- package/dist/mixins/mixin-field/mixin-field.d.ts +11 -0
- package/dist/mixins/mixin-field/mixin-field.js +25 -3
- package/dist/mixins/mixin-picker-field/mixin-picker-field.d.ts +8 -0
- package/dist/mixins/mixin-picker-field/mixin-picker-field.js +22 -2
- package/dist/mixins/mixin-tokens/mixin-tokens.js +0 -1
- package/dist/mixins/mixin-viewport/mixin-viewport.d.ts +97 -0
- package/dist/mixins/mixin-viewport/mixin-viewport.js +215 -0
- package/dist/utils/fill-template/fill-template.d.ts +13 -0
- package/dist/utils/fill-template/fill-template.js +1 -0
- package/dist/utils/key-claimed/key-claimed.d.ts +18 -0
- package/dist/utils/key-claimed/key-claimed.js +14 -0
- package/dist/utils/report-values/report-values.d.ts +21 -0
- package/dist/utils/report-values/report-values.js +11 -0
- package/dist/utils/slotted-words/slotted-words.d.ts +11 -0
- package/dist/utils/slotted-words/slotted-words.js +5 -0
- package/dist/utils/tell-disclosure/tell-disclosure.d.ts +3 -1
- package/dist/utils/tell-disclosure/tell-disclosure.js +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,215 @@
|
|
|
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 { html, nothing } from 'lit';
|
|
8
|
+
import { property, state } from 'lit/decorators.js';
|
|
9
|
+
import { FullscreenController } from '../../controllers/fullscreen-controller/fullscreen-controller.js';
|
|
10
|
+
import { IdleController } from '../../controllers/idle-controller/idle-controller.js';
|
|
11
|
+
import { inlineStep } from '../../utils/inline-step/inline-step.js';
|
|
12
|
+
import { isRtl } from '../../utils/is-rtl/is-rtl.js';
|
|
13
|
+
export const MixinViewport = (Base) => {
|
|
14
|
+
class Viewport extends Base {
|
|
15
|
+
constructor() {
|
|
16
|
+
super(...arguments);
|
|
17
|
+
this.fullscreenLabel = 'Fill the screen';
|
|
18
|
+
this.idleDelay = 0;
|
|
19
|
+
this.panDownLabel = 'Pan down';
|
|
20
|
+
this.panLeftLabel = 'Pan left';
|
|
21
|
+
this.panRightLabel = 'Pan right';
|
|
22
|
+
this.panStep = 0.25;
|
|
23
|
+
this.panUpLabel = 'Pan up';
|
|
24
|
+
this.zoomInLabel = 'Zoom in';
|
|
25
|
+
this.resting = false;
|
|
26
|
+
this.filled = false;
|
|
27
|
+
this.fullscreen = new FullscreenController(this, (full) => {
|
|
28
|
+
this.filled = full;
|
|
29
|
+
});
|
|
30
|
+
this.idle = new IdleController(this, () => this.idleDelay, (resting) => {
|
|
31
|
+
this.resting = resting;
|
|
32
|
+
});
|
|
33
|
+
this.zoomOutLabel = 'Zoom out';
|
|
34
|
+
this.handleKeydown = (event) => {
|
|
35
|
+
const target = event.target;
|
|
36
|
+
const own = target?.closest('[part="region"], [part~="controls"]');
|
|
37
|
+
if (!own || !this.renderRoot.contains(own)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (target !== own && !own.matches('[part~="controls"]')) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const along = inlineStep(this, event.key);
|
|
44
|
+
if (along === 1) {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
this.pan('end');
|
|
47
|
+
}
|
|
48
|
+
else if (along === -1) {
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
this.pan('start');
|
|
51
|
+
}
|
|
52
|
+
else if (event.key === 'ArrowUp') {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
this.pan('up');
|
|
55
|
+
}
|
|
56
|
+
else if (event.key === 'ArrowDown') {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
this.pan('down');
|
|
59
|
+
}
|
|
60
|
+
else if (event.key === '+' || event.key === '=') {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
this.zoom(1);
|
|
63
|
+
}
|
|
64
|
+
else if (event.key === '-' || event.key === '_') {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
this.zoom(-1);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
get restingControlsLabel() {
|
|
71
|
+
return 'View controls';
|
|
72
|
+
}
|
|
73
|
+
panEvent(_detail) {
|
|
74
|
+
throw new Error(`<${this.localName}> composes MixinViewport and must implement panEvent(), the surface naming no event of its own.`);
|
|
75
|
+
}
|
|
76
|
+
zoomEvent(_detail) {
|
|
77
|
+
throw new Error(`<${this.localName}> composes MixinViewport and must implement zoomEvent(), the surface naming no event of its own.`);
|
|
78
|
+
}
|
|
79
|
+
pan(to) {
|
|
80
|
+
this.dispatchEvent(this.panEvent({ by: this.panStep, to }));
|
|
81
|
+
}
|
|
82
|
+
zoom(by) {
|
|
83
|
+
this.dispatchEvent(this.zoomEvent({ by }));
|
|
84
|
+
}
|
|
85
|
+
toggleFullscreen() {
|
|
86
|
+
void this.fullscreen.toggle();
|
|
87
|
+
}
|
|
88
|
+
get restingControls() {
|
|
89
|
+
return 'pan zoom fullscreen';
|
|
90
|
+
}
|
|
91
|
+
draws(group) {
|
|
92
|
+
return (this.controls ?? this.restingControls).split(/\s+/).includes(group);
|
|
93
|
+
}
|
|
94
|
+
sideOf(way) {
|
|
95
|
+
if (way !== 'start' && way !== 'end') {
|
|
96
|
+
return way;
|
|
97
|
+
}
|
|
98
|
+
return (way === 'start') !== this.rightToLeft ? 'left' : 'right';
|
|
99
|
+
}
|
|
100
|
+
get rightToLeft() {
|
|
101
|
+
const element = this;
|
|
102
|
+
return !element.hydrating && isRtl(this);
|
|
103
|
+
}
|
|
104
|
+
named(way) {
|
|
105
|
+
const side = this.sideOf(way);
|
|
106
|
+
return side === 'up'
|
|
107
|
+
? this.panUpLabel
|
|
108
|
+
: side === 'down'
|
|
109
|
+
? this.panDownLabel
|
|
110
|
+
: side === 'left'
|
|
111
|
+
? this.panLeftLabel
|
|
112
|
+
: this.panRightLabel;
|
|
113
|
+
}
|
|
114
|
+
glyph(way) {
|
|
115
|
+
return { down: '↓', left: '←', right: '→', up: '↑' }[this.sideOf(way)] ?? '';
|
|
116
|
+
}
|
|
117
|
+
firstUpdated(changed) {
|
|
118
|
+
super.firstUpdated(changed);
|
|
119
|
+
this.idle.stir();
|
|
120
|
+
const element = this;
|
|
121
|
+
if (element.serverRendered && isRtl(this)) {
|
|
122
|
+
this.requestUpdate();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
get controlsElement() {
|
|
126
|
+
return this.renderRoot?.querySelector('[part="controls"]') ?? null;
|
|
127
|
+
}
|
|
128
|
+
renderControls() {
|
|
129
|
+
const pan = this.draws('pan');
|
|
130
|
+
const zoom = this.draws('zoom');
|
|
131
|
+
const fullscreen = this.draws('fullscreen');
|
|
132
|
+
if (!pan && !zoom && !fullscreen) {
|
|
133
|
+
return nothing;
|
|
134
|
+
}
|
|
135
|
+
return html `<div part="controls" role="group" aria-label=${this.controlsLabel ?? this.restingControlsLabel}>
|
|
136
|
+
${pan
|
|
137
|
+
? html `<button part="pan-up" aria-label=${this.named('up')} type="button" @click=${() => this.pan('up')}>
|
|
138
|
+
${this.glyph('up')}
|
|
139
|
+
</button>
|
|
140
|
+
<button part="pan-start" aria-label=${this.named('start')} type="button" @click=${() => this.pan('start')}>
|
|
141
|
+
${this.glyph('start')}
|
|
142
|
+
</button>
|
|
143
|
+
<button part="pan-end" aria-label=${this.named('end')} type="button" @click=${() => this.pan('end')}>
|
|
144
|
+
${this.glyph('end')}
|
|
145
|
+
</button>
|
|
146
|
+
<button part="pan-down" aria-label=${this.named('down')} type="button" @click=${() => this.pan('down')}>
|
|
147
|
+
${this.glyph('down')}
|
|
148
|
+
</button>`
|
|
149
|
+
: nothing}
|
|
150
|
+
${zoom
|
|
151
|
+
? html `<button part="zoom-in" aria-label=${this.zoomInLabel} type="button" @click=${() => this.zoom(1)}>
|
|
152
|
+
+
|
|
153
|
+
</button>
|
|
154
|
+
<button part="zoom-out" aria-label=${this.zoomOutLabel} type="button" @click=${() => this.zoom(-1)}>
|
|
155
|
+
−
|
|
156
|
+
</button>`
|
|
157
|
+
: nothing}
|
|
158
|
+
${fullscreen
|
|
159
|
+
? html `<button
|
|
160
|
+
part="fullscreen"
|
|
161
|
+
aria-label=${this.fullscreenLabel}
|
|
162
|
+
aria-pressed=${this.filled ? 'true' : 'false'}
|
|
163
|
+
type="button"
|
|
164
|
+
@click=${this.toggleFullscreen}
|
|
165
|
+
>
|
|
166
|
+
⤢
|
|
167
|
+
</button>`
|
|
168
|
+
: nothing}
|
|
169
|
+
</div>`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
__decorate([
|
|
173
|
+
property({ reflect: true })
|
|
174
|
+
], Viewport.prototype, "controls", void 0);
|
|
175
|
+
__decorate([
|
|
176
|
+
property({ attribute: 'controls-label' })
|
|
177
|
+
], Viewport.prototype, "controlsLabel", void 0);
|
|
178
|
+
__decorate([
|
|
179
|
+
property({ attribute: 'controls-placement', reflect: true })
|
|
180
|
+
], Viewport.prototype, "controlsPlacement", void 0);
|
|
181
|
+
__decorate([
|
|
182
|
+
property({ attribute: 'fullscreen-label' })
|
|
183
|
+
], Viewport.prototype, "fullscreenLabel", void 0);
|
|
184
|
+
__decorate([
|
|
185
|
+
property({ attribute: 'idle-delay', type: Number })
|
|
186
|
+
], Viewport.prototype, "idleDelay", void 0);
|
|
187
|
+
__decorate([
|
|
188
|
+
property({ attribute: 'pan-down-label' })
|
|
189
|
+
], Viewport.prototype, "panDownLabel", void 0);
|
|
190
|
+
__decorate([
|
|
191
|
+
property({ attribute: 'pan-left-label' })
|
|
192
|
+
], Viewport.prototype, "panLeftLabel", void 0);
|
|
193
|
+
__decorate([
|
|
194
|
+
property({ attribute: 'pan-right-label' })
|
|
195
|
+
], Viewport.prototype, "panRightLabel", void 0);
|
|
196
|
+
__decorate([
|
|
197
|
+
property({ attribute: 'pan-step', type: Number })
|
|
198
|
+
], Viewport.prototype, "panStep", void 0);
|
|
199
|
+
__decorate([
|
|
200
|
+
property({ attribute: 'pan-up-label' })
|
|
201
|
+
], Viewport.prototype, "panUpLabel", void 0);
|
|
202
|
+
__decorate([
|
|
203
|
+
property({ attribute: 'zoom-in-label' })
|
|
204
|
+
], Viewport.prototype, "zoomInLabel", void 0);
|
|
205
|
+
__decorate([
|
|
206
|
+
state()
|
|
207
|
+
], Viewport.prototype, "resting", void 0);
|
|
208
|
+
__decorate([
|
|
209
|
+
state()
|
|
210
|
+
], Viewport.prototype, "filled", void 0);
|
|
211
|
+
__decorate([
|
|
212
|
+
property({ attribute: 'zoom-out-label' })
|
|
213
|
+
], Viewport.prototype, "zoomOutLabel", void 0);
|
|
214
|
+
return Viewport;
|
|
215
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A label with its `{placeholders}` filled in.
|
|
3
|
+
*
|
|
4
|
+
* **The one way every translatable sentence here is written**: a property holds
|
|
5
|
+
* the whole sentence, and the numbers and names go in where it says. A
|
|
6
|
+
* sentence built from pieces in code puts the pieces in English order, and
|
|
7
|
+
* another language may want them the other way round.
|
|
8
|
+
*
|
|
9
|
+
* Every occurrence is filled, so a sentence can name the same thing twice. A
|
|
10
|
+
* placeholder with no value is left as written rather than emptied, which
|
|
11
|
+
* shows up on the page as the mistake it is.
|
|
12
|
+
*/
|
|
13
|
+
export declare const fillTemplate: (template: string, values: Record<string, string | number>) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const fillTemplate = (template, values) => Object.entries(values).reduce((out, [key, value]) => out.replaceAll(`{${key}}`, () => String(value)), template);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a press was already the business of whatever it landed on.
|
|
3
|
+
*
|
|
4
|
+
* **For a container that binds keys over content it did not write**: a toolbar,
|
|
5
|
+
* a viewer, a drilldown. Some of what they hold uses the same keys for itself,
|
|
6
|
+
* and moving the container regardless makes an input inside it uneditable:
|
|
7
|
+
* ArrowRight moves somewhere else, and `preventDefault` means the caret never
|
|
8
|
+
* moves at all.
|
|
9
|
+
*
|
|
10
|
+
* `composedPath()[0]` rather than `event.target`, which is retargeted to the
|
|
11
|
+
* control's host at the shadow boundary and cannot tell a text field inside a
|
|
12
|
+
* component from the component itself.
|
|
13
|
+
*
|
|
14
|
+
* `defaultPrevented` first, because a control that has already answered the
|
|
15
|
+
* press has said so in the way the platform provides, and a foreign control
|
|
16
|
+
* this package has never heard of has no other way to say it.
|
|
17
|
+
*/
|
|
18
|
+
export declare const keyClaimed: (event: KeyboardEvent) => boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const CARETLESS = new Set(['button', 'checkbox', 'color', 'file', 'image', 'reset', 'submit']);
|
|
2
|
+
export const keyClaimed = (event) => {
|
|
3
|
+
if (event.defaultPrevented) {
|
|
4
|
+
return true;
|
|
5
|
+
}
|
|
6
|
+
const inner = event.composedPath()[0];
|
|
7
|
+
if (!(inner instanceof HTMLElement)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
if (inner.isContentEditable || inner.localName === 'textarea' || inner.localName === 'select') {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
return inner.localName === 'input' && !CARETLESS.has(inner.type);
|
|
14
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports a control's answer to its form: one entry for each value, the way a
|
|
3
|
+
* native `<select multiple>` and a row of checkboxes send theirs.
|
|
4
|
+
*
|
|
5
|
+
* **Several answers are several entries, not one joined string.** A server
|
|
6
|
+
* reading `tags=a,b` has to know to split it on a convention this library
|
|
7
|
+
* chose, and one reading `tags=a&tags=b` reads it the way it reads every
|
|
8
|
+
* other multi-valued control on the platform. Listbox, Select and Combobox
|
|
9
|
+
* sent the first and Date Picker the second, so what a server received for the
|
|
10
|
+
* same answer depended on which control asked for it.
|
|
11
|
+
*
|
|
12
|
+
* **The joined string is the state**, which is what the browser hands back to
|
|
13
|
+
* `formStateRestoreCallback` and what `value` holds, so a restored control
|
|
14
|
+
* reads the shape it wrote.
|
|
15
|
+
*
|
|
16
|
+
* **Nothing without a `name`.** A plain string is skipped by the form when its
|
|
17
|
+
* control has no name, but `FormData` carries its own names and its entries
|
|
18
|
+
* are appended whatever the element is called, so an unnamed control would
|
|
19
|
+
* submit empty-named values.
|
|
20
|
+
*/
|
|
21
|
+
export declare const reportValues: (internals: ElementInternals, name: string, values: readonly string[]) => void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const reportValues = (internals, name, values) => {
|
|
2
|
+
if (name === '' || values.length === 0) {
|
|
3
|
+
internals.setFormValue(null);
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
const data = new FormData();
|
|
7
|
+
for (const value of values) {
|
|
8
|
+
data.append(name, value);
|
|
9
|
+
}
|
|
10
|
+
internals.setFormValue(data, values.join(','));
|
|
11
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The words a host's children say in the named slots, `''` being the default
|
|
3
|
+
* one, in the order they are written.
|
|
4
|
+
*
|
|
5
|
+
* For a component that says what the page put in it, which is the page's own
|
|
6
|
+
* text and not anything this library wrote: an empty state's heading and
|
|
7
|
+
* message, a banner's message. Text nodes count toward the default slot, as
|
|
8
|
+
* they are assigned to it. Nothing on a server, whose element has no children
|
|
9
|
+
* to read.
|
|
10
|
+
*/
|
|
11
|
+
export declare const slottedWords: (host: Element, slots: readonly string[]) => string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const slottedWords = (host, slots) => [...(host.childNodes ?? [])]
|
|
2
|
+
.filter((node) => (node instanceof Element ? slots.includes(node.slot) : slots.includes('')))
|
|
3
|
+
.map((node) => node.textContent?.replace(/\s+/g, ' ').trim() ?? '')
|
|
4
|
+
.filter(Boolean)
|
|
5
|
+
.join(' ');
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
* `<button slot="trigger">` has no shadow root to keep the state out of the
|
|
44
44
|
* markup and no property to take it, and a consumer cannot mirror it themselves
|
|
45
45
|
* because the open state belongs to the component. Saying so once is the same
|
|
46
|
-
* answer `Tooltip` gives about the link it will not write for them
|
|
46
|
+
* answer `Tooltip` gives about the link it will not write for them, and not at
|
|
47
|
+
* all to a trigger that already carries `aria-expanded`, which is a consumer
|
|
48
|
+
* mirroring the state from the component's `toggle` event.
|
|
47
49
|
*/
|
|
48
50
|
export declare const tellDisclosure: (trigger: Element | null | undefined, open: boolean) => void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix-core",
|
|
3
3
|
"description": "Helix element primitives: base classes, mixins, decorators, utilities.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.104.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@floating-ui/dom": "^1.8.0",
|
|
37
|
-
"@fusion.dev/helix-icons": "0.
|
|
37
|
+
"@fusion.dev/helix-icons": "0.104.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@fusion.dev/helix-cem": "0.
|
|
41
|
-
"@fusion.dev/helix-testing": "0.
|
|
40
|
+
"@fusion.dev/helix-cem": "0.104.0",
|
|
41
|
+
"@fusion.dev/helix-testing": "0.104.0",
|
|
42
42
|
"@lit-labs/ssr": "^4.1.0",
|
|
43
43
|
"lit": "^3.2.1"
|
|
44
44
|
},
|