@fusion.dev/helix-core 0.2.0 → 0.4.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 +4 -4
- package/dist/controllers/aria-forward-controller/aria-forward-controller.js +0 -105
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +0 -71
- package/dist/controllers/command-controller/command-controller.js +0 -62
- package/dist/controllers/copy-controller/copy-controller.js +0 -63
- package/dist/controllers/disabled-cascade-controller/disabled-cascade-controller.js +0 -40
- package/dist/controllers/dismiss-controller/dismiss-controller.js +0 -124
- package/dist/controllers/drilldown-controller/drilldown-controller.js +0 -87
- package/dist/controllers/floating-controller/floating-controller.js +0 -152
- package/dist/controllers/focus-trap-controller/focus-trap-controller.js +0 -35
- package/dist/controllers/indicator-controller/indicator-controller.js +0 -82
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +0 -46
- package/dist/controllers/popover-controller/popover-controller.js +0 -72
- package/dist/controllers/source-controller/source-controller.js +0 -38
- package/dist/controllers/token-color-controller/token-color-controller.js +0 -51
- package/dist/controllers/virtualize-controller/virtualize-controller.js +0 -128
- package/dist/events/element-events.js +0 -22
- package/dist/events/events.js +0 -386
- package/dist/helix-element/helix-element.d.ts +10 -17
- package/dist/helix-element/helix-element.js +0 -505
- package/dist/helix-form-element/helix-form-element.js +0 -140
- package/dist/mixins/mixin-chart-source/mixin-chart-source.js +0 -77
- package/dist/mixins/mixin-collection/mixin-collection.js +0 -83
- package/dist/mixins/mixin-controller-active/mixin-controller-active.js +0 -82
- package/dist/mixins/mixin-controller-checked/mixin-controller-checked.js +0 -78
- package/dist/mixins/mixin-controller-open/mixin-controller-open.js +0 -93
- package/dist/mixins/mixin-controller-value/mixin-controller-value.js +0 -87
- package/dist/mixins/mixin-dismissible/mixin-dismissible.js +0 -38
- package/dist/mixins/mixin-field/mixin-field.js +0 -277
- package/dist/utils/anchor-element/anchor-element.js +0 -23
- package/dist/utils/capture-pointer/capture-pointer.js +0 -18
- package/dist/utils/closest-instance/closest-instance.js +0 -20
- package/dist/utils/create-sheet/create-sheet.js +0 -14
- package/dist/utils/custom-element/custom-element.js +0 -83
- package/dist/utils/draw-icon/draw-icon.js +0 -28
- package/dist/utils/draw-tooltip/draw-tooltip.js +1 -50
- package/dist/utils/elapsed-time/elapsed-time.js +0 -51
- package/dist/utils/focusable-elements/focusable-elements.js +1 -59
- package/dist/utils/format-numbers/format-numbers.js +0 -16
- package/dist/utils/host-aria/host-aria.js +0 -26
- package/dist/utils/inherit-disabled/inherit-disabled.js +0 -36
- package/dist/utils/line-path/line-path.js +0 -36
- package/dist/utils/on-unless-false/on-unless-false.js +0 -23
- package/dist/utils/read-series/read-series.js +0 -71
- package/dist/utils/relative-time/relative-time.js +0 -43
- package/dist/utils/resolve-active-item/resolve-active-item.js +0 -20
- package/dist/utils/restore-value/restore-value.js +0 -54
- package/dist/utils/scale/scale.js +0 -19
- package/dist/utils/scale/ticks.js +0 -36
- package/dist/utils/scroll-item-into-view/scroll-item-into-view.js +0 -53
- package/dist/utils/slot-filled/slot-filled.js +0 -23
- package/dist/utils/tell-disclosure/tell-disclosure.js +0 -53
- package/dist/utils/ticker/ticker.js +0 -22
- package/dist/utils/tone-marks/tone-marks.js +0 -24
- package/dist/utils/value-as/value-as.js +0 -56
- package/package.json +5 -5
|
@@ -6,161 +6,27 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { property, state } from 'lit/decorators.js';
|
|
8
8
|
import { HelixElement } from '../helix-element/helix-element.js';
|
|
9
|
-
/**
|
|
10
|
-
* @summary Base for every element that participates in a form.
|
|
11
|
-
*
|
|
12
|
-
* @description
|
|
13
|
-
* This has to be a class rather than a mixin or controller, and that is not a
|
|
14
|
-
* style preference: the browser reads `formAssociated` off the constructor
|
|
15
|
-
* during element upgrade, which cannot be delegated to a composed object.
|
|
16
|
-
*
|
|
17
|
-
* `internals` comes from `HelixElement`, which attaches it for every component.
|
|
18
|
-
* `attachInternals()` throws on a second call, so there is exactly one owner.
|
|
19
|
-
*
|
|
20
|
-
* Deliberately small. It carries what every form participant needs and nothing
|
|
21
|
-
* more; `value`, validity, and the reset and restore callbacks land when the
|
|
22
|
-
* first input does, rather than being designed against components that do not
|
|
23
|
-
* exist yet.
|
|
24
|
-
*
|
|
25
|
-
* @since 0.1.0
|
|
26
|
-
* @status experimental
|
|
27
|
-
* @documentation https://helix.fusion.dev/core/form-element
|
|
28
|
-
*/
|
|
29
9
|
export class HelixFormElement extends HelixElement {
|
|
30
10
|
constructor() {
|
|
31
11
|
super(...arguments);
|
|
32
|
-
/**
|
|
33
|
-
* Whether the control is non-interactive.
|
|
34
|
-
*
|
|
35
|
-
* Reflected, because the styled tier selects on it, and mirrored to
|
|
36
|
-
* `aria-disabled` rather than removing the element from the accessibility
|
|
37
|
-
* tree, so a screen reader can still announce that the control exists.
|
|
38
|
-
*
|
|
39
|
-
* The mirror itself is in `willUpdate` below, here rather than in each
|
|
40
|
-
* control, because here is where `disabled` is.
|
|
41
|
-
*/
|
|
42
12
|
this.disabled = false;
|
|
43
|
-
/**
|
|
44
|
-
* Whether an ancestor `<fieldset disabled>` has switched this control off.
|
|
45
|
-
*
|
|
46
|
-
* The platform tells a form-associated element through `formDisabledCallback`
|
|
47
|
-
* and nothing else does, so without this a control inside a disabled fieldset
|
|
48
|
-
* stays fully typeable and announces itself as enabled.
|
|
49
|
-
*
|
|
50
|
-
* The submission half is already the platform's: measured, the fieldset's
|
|
51
|
-
* controls are absent from the form's entry list and the host matches
|
|
52
|
-
* `:disabled`, so the value never goes anywhere. What is missing without this
|
|
53
|
-
* is everything a reader can see or touch.
|
|
54
|
-
*
|
|
55
|
-
* State rather than the `disabled` property. Writing that would reflect an
|
|
56
|
-
* attribute the consumer's server markup does not have, which React reports
|
|
57
|
-
* on hydration and does not patch up, and it would also silently overwrite
|
|
58
|
-
* what the consumer set.
|
|
59
|
-
*/
|
|
60
13
|
this.formDisabled = false;
|
|
61
|
-
/**
|
|
62
|
-
* Name submitted with the form.
|
|
63
|
-
*
|
|
64
|
-
* **Reflected, and it is the exception to "reflect only what CSS selects
|
|
65
|
-
* on".** The platform builds a form's entry list from the `name` *content
|
|
66
|
-
* attribute*, not from the property, so a control named through the property
|
|
67
|
-
* submitted under no name at all and was silently dropped. Measured: two
|
|
68
|
-
* inputs in one form, one named by attribute and one by property, and the
|
|
69
|
-
* form carried a single entry.
|
|
70
|
-
*
|
|
71
|
-
* Safe for a React consumer, which is what the rule is protecting: React
|
|
72
|
-
* renders a custom element's props as attributes on the server, so the name
|
|
73
|
-
* is already in the markup being hydrated and reflecting it writes what is
|
|
74
|
-
* there. It is only a change to the markup when a consumer assigns it
|
|
75
|
-
* imperatively later, and nothing is hydrating by then.
|
|
76
|
-
*
|
|
77
|
-
* Here rather than on each control.
|
|
78
|
-
*/
|
|
79
14
|
this.name = '';
|
|
80
15
|
}
|
|
81
|
-
/** Opts the element into form ownership, labels, and submission. */
|
|
82
16
|
static { this.formAssociated = true; }
|
|
83
|
-
/**
|
|
84
|
-
* Whether the control is off, by its own attribute or by an ancestor.
|
|
85
|
-
*
|
|
86
|
-
* What a component should bind to rather than `disabled`, which is only the
|
|
87
|
-
* half of it the consumer wrote.
|
|
88
|
-
*
|
|
89
|
-
* @internal Read by the component itself and by nothing outside it: every
|
|
90
|
-
* call in the library is `this.isDisabled`. Public, it prints a row on
|
|
91
|
-
* twenty-four families in both tiers, forty-eight in all, on every form
|
|
92
|
-
* control's reference page.
|
|
93
|
-
*
|
|
94
|
-
* **A consumer loses nothing, which was measured rather than assumed.**
|
|
95
|
-
* `element.matches(':disabled')` answers the same question, is the platform's
|
|
96
|
-
* own, and works on a native control too. Checked in a browser across the
|
|
97
|
-
* three cases: a control disabled by its own attribute, one inside a
|
|
98
|
-
* `<fieldset>` before and after that fieldset is disabled, and one that is
|
|
99
|
-
* neither. `:disabled` agreed with `isDisabled` in every one, including the
|
|
100
|
-
* inheritance case that is the only reason this getter exists.
|
|
101
|
-
*
|
|
102
|
-
* Documented away rather than made `protected`, because a subclass in
|
|
103
|
-
* `helix/styled` binds to it and that is exactly what it is for.
|
|
104
|
-
*/
|
|
105
17
|
get isDisabled() {
|
|
106
18
|
return this.disabled || this.formDisabled;
|
|
107
19
|
}
|
|
108
|
-
/**
|
|
109
|
-
* The platform's notice that an ancestor fieldset changed.
|
|
110
|
-
*
|
|
111
|
-
* **The argument is the effective state, and taking it whole is what latched
|
|
112
|
-
* every control in the library.** The platform calls this for the element's
|
|
113
|
-
* own `disabled` as well as for a fieldset, and our own arrives here at the
|
|
114
|
-
* worst possible moment: reflecting `disabled` to the attribute happens
|
|
115
|
-
* inside Lit's `update()`, the UA fires this synchronously from that write,
|
|
116
|
-
* and a reactive write during an update is a change-in-update Lit does not
|
|
117
|
-
* re-render for. So `formDisabled` ran exactly one update behind forever.
|
|
118
|
-
*
|
|
119
|
-
* Measured on Slider, logging what `render` saw:
|
|
120
|
-
*
|
|
121
|
-
* ```
|
|
122
|
-
* set true disabled true formDisabled false isDisabled true
|
|
123
|
-
* set false disabled false formDisabled true isDisabled true <- stale
|
|
124
|
-
* (no third render)
|
|
125
|
-
* ```
|
|
126
|
-
*
|
|
127
|
-
* The host came out right every time, `disabled` false, `:disabled` not
|
|
128
|
-
* matching, and the inner control kept the attribute, so a control disabled
|
|
129
|
-
* once could never be re-enabled. It only shows when something re-enables,
|
|
130
|
-
* which is the same shape of bug `inheritDisabled` was written for.
|
|
131
|
-
*
|
|
132
|
-
* Our own half is already in `this.disabled` and `isDisabled` is an or, so
|
|
133
|
-
* this records only what the fieldset adds. That also removes the write
|
|
134
|
-
* entirely on the own-disabled path rather than moving it.
|
|
135
|
-
*
|
|
136
|
-
* The one case this cannot see is a control that is disabled itself *and*
|
|
137
|
-
* inside a disabled fieldset, whose own `disabled` is then removed: the
|
|
138
|
-
* effective state does not change, so the platform does not call again, and
|
|
139
|
-
* the fieldset's hold is not recorded. There is no callback to learn it from
|
|
140
|
-
* and reading `:disabled` here would answer about the attribute we are in the
|
|
141
|
-
* middle of removing.
|
|
142
|
-
*/
|
|
143
20
|
formDisabledCallback(disabled) {
|
|
144
21
|
this.formDisabled = disabled && !this.disabled;
|
|
145
22
|
}
|
|
146
23
|
willUpdate(changed) {
|
|
147
24
|
super.willUpdate(changed);
|
|
148
|
-
// Off for either reason, and said once for every control in the library.
|
|
149
25
|
this.internals.ariaDisabled = this.isDisabled ? 'true' : null;
|
|
150
26
|
}
|
|
151
|
-
/**
|
|
152
|
-
* The form this control belongs to, if any.
|
|
153
|
-
*
|
|
154
|
-
* @reference https://html.spec.whatwg.org/multipage/custom-elements.html#form-associated-custom-elements form-associated custom elements
|
|
155
|
-
*/
|
|
156
27
|
get form() {
|
|
157
28
|
return this.internals.form;
|
|
158
29
|
}
|
|
159
|
-
/**
|
|
160
|
-
* Labels associated with this control.
|
|
161
|
-
*
|
|
162
|
-
* @reference https://html.spec.whatwg.org/multipage/custom-elements.html#form-associated-custom-elements form-associated custom elements
|
|
163
|
-
*/
|
|
164
30
|
get labels() {
|
|
165
31
|
return this.internals.labels;
|
|
166
32
|
}
|
|
@@ -173,12 +39,6 @@ __decorate([
|
|
|
173
39
|
], HelixFormElement.prototype, "formDisabled", void 0);
|
|
174
40
|
__decorate([
|
|
175
41
|
property({
|
|
176
|
-
/* Empty means absent, which plain reflection cannot say. `name = ''` is the
|
|
177
|
-
default and Lit reflects it as `name=""`, so every button, switch and
|
|
178
|
-
calendar in the library would ship an empty name attribute the consumer
|
|
179
|
-
never wrote. Returning `null` removes the attribute instead. Same trap
|
|
180
|
-
`hostAria` exists for: a name of no characters is not the absence of
|
|
181
|
-
one. */
|
|
182
42
|
converter: {
|
|
183
43
|
fromAttribute: (value) => value ?? '',
|
|
184
44
|
toAttribute: (value) => (value === '' ? null : value),
|
|
@@ -8,111 +8,34 @@ import { property } from 'lit/decorators.js';
|
|
|
8
8
|
import { ChartPointerController } from '../../controllers/chart-pointer-controller/chart-pointer-controller.js';
|
|
9
9
|
import { SourceController } from '../../controllers/source-controller/source-controller.js';
|
|
10
10
|
import { readRows, readSeries } from '../../utils/read-series/read-series.js';
|
|
11
|
-
/**
|
|
12
|
-
* How many colors the series palette has, and so where it starts over.
|
|
13
|
-
*
|
|
14
|
-
* Here rather than in each chart, because a number two files have to agree on
|
|
15
|
-
* is a number that will stop agreeing: when the palette went from eight to
|
|
16
|
-
* twenty-four, one of five copies was missed and drew every ninth series in the
|
|
17
|
-
* first color while the legend beside it named the ninth.
|
|
18
|
-
*/
|
|
19
11
|
export const PALETTE = 24;
|
|
20
|
-
/** The palette slot for the nth series, counting from one and wrapping. */
|
|
21
12
|
export const seriesIndex = (depth) => (depth % PALETTE) + 1;
|
|
22
|
-
/**
|
|
23
|
-
* The half of a chart that is about finding its numbers rather than drawing.
|
|
24
|
-
*
|
|
25
|
-
* **A mixin rather than a controller**, by the rule the tier notes give: it
|
|
26
|
-
* introduces `for` and `label`, which a consumer writes. `SourceController` is
|
|
27
|
-
* the other half and stays a controller, because resolving an id and watching
|
|
28
|
-
* for changes is behavior nobody calls.
|
|
29
|
-
*
|
|
30
|
-
* **`Legend` deliberately does not take this.** It reads a table and has no
|
|
31
|
-
* drawing to title, so `label` would be API it never uses, and a mixin applied
|
|
32
|
-
* for four fifths of a fit is how a shared thing starts drifting.
|
|
33
|
-
*/
|
|
34
13
|
export const MixinChartSource = (Base) => {
|
|
35
14
|
class ChartSourceElement extends Base {
|
|
36
15
|
constructor() {
|
|
37
16
|
super(...arguments);
|
|
38
|
-
/** Resolves `for` and re-renders when the table behind it changes. */
|
|
39
17
|
this.source = new SourceController(this);
|
|
40
|
-
/**
|
|
41
|
-
* Which reading a pointer is nearest, for a tooltip to sit on.
|
|
42
|
-
*
|
|
43
|
-
* Public for the same reason `series` and `named` are: a protected member
|
|
44
|
-
* cannot be cast onto across unrelated classes, so it would be invisible to
|
|
45
|
-
* every chart that needs it. Reading where a pointer is resting is a fair
|
|
46
|
-
* thing to ask a chart anyway.
|
|
47
|
-
*/
|
|
48
18
|
this.pointer = new ChartPointerController(this);
|
|
49
|
-
/**
|
|
50
|
-
* The id of the table to read.
|
|
51
|
-
*
|
|
52
|
-
* Resolved through `getRootNode()`, so a chart inside another component's
|
|
53
|
-
* shadow root names a table in that root rather than in the document. A
|
|
54
|
-
* `<table>` or anything built from grid roles, including `helix-table`.
|
|
55
|
-
*
|
|
56
|
-
* **In React this arrives as a property and not as an attribute**, the way
|
|
57
|
-
* `Tooltip.for` does, so it does not reflect and nothing styles on it.
|
|
58
|
-
*/
|
|
59
19
|
this.for = '';
|
|
60
|
-
/**
|
|
61
|
-
* The drawing's name, for a pointer resting on it.
|
|
62
|
-
*
|
|
63
|
-
* Absent falls back to the table's own caption, which is usually the right
|
|
64
|
-
* words and is already written.
|
|
65
|
-
*/
|
|
66
20
|
this.label = '';
|
|
67
21
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Re-resolves when the chart is pointed at a different table.
|
|
70
|
-
*
|
|
71
|
-
* `SourceController` watches whatever it resolved on connect, and nothing
|
|
72
|
-
* tells it the id changed. Without this, pointing a chart at a second table
|
|
73
|
-
* leaves it drawing the first, silently.
|
|
74
|
-
*/
|
|
75
22
|
willUpdate(changed) {
|
|
76
23
|
super.willUpdate(changed);
|
|
77
24
|
if (changed.has('for')) {
|
|
78
25
|
this.source.watch();
|
|
79
26
|
}
|
|
80
27
|
}
|
|
81
|
-
/** The columns of the table, one series each, or none if there is no table. */
|
|
82
28
|
get series() {
|
|
83
29
|
const element = this.source.element;
|
|
84
30
|
return element ? readSeries(element) : [];
|
|
85
31
|
}
|
|
86
|
-
/**
|
|
87
|
-
* What to call the drawing, the consumer's word or the table's own.
|
|
88
|
-
*
|
|
89
|
-
* The fallback is the reason this is shared rather than copied: a caption is
|
|
90
|
-
* already written, already the right words, and already the thing a reader
|
|
91
|
-
* of the table sees. A chart that made the consumer say it twice would be
|
|
92
|
-
* asking for the second one to drift.
|
|
93
|
-
*/
|
|
94
32
|
get named() {
|
|
95
33
|
return this.label || this.source.element?.querySelector('caption')?.textContent?.trim() || '';
|
|
96
34
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Every row of the table, with each cell's text as well as its number.
|
|
99
|
-
*
|
|
100
|
-
* This is where a tooltip's content comes from, and it is deliberately not
|
|
101
|
-
* `series`. A chart draws numbers; a reader resting a pointer on one wants
|
|
102
|
-
* what the table says, which is `$128k` and not `128000`, and often wants
|
|
103
|
-
* the column beside it that nothing plotted at all.
|
|
104
|
-
*
|
|
105
|
-
* **Not `rows`.** It is the source table's rows, and `rows` on a chart is a
|
|
106
|
-
* name a chart wants for itself: `helix-heatmap` takes `rows="7"` to mean a
|
|
107
|
-
* calendar week, and declaring that against a base class whose `rows` is a
|
|
108
|
-
* `Row[]` getter is a type error at best. Name the member for what it holds
|
|
109
|
-
* rather than for the shortest word available.
|
|
110
|
-
*/
|
|
111
35
|
get sourceRows() {
|
|
112
36
|
const element = this.source.element;
|
|
113
37
|
return element ? readRows(element) : [];
|
|
114
38
|
}
|
|
115
|
-
/** How many readings there are, which is what the pointer maps onto. */
|
|
116
39
|
get readings() {
|
|
117
40
|
return this.sourceRows.length;
|
|
118
41
|
}
|
|
@@ -6,74 +6,16 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { html, nothing } from 'lit';
|
|
8
8
|
import { property, state } from 'lit/decorators.js';
|
|
9
|
-
/**
|
|
10
|
-
* Grafts the two states a collection has that are not its content.
|
|
11
|
-
*
|
|
12
|
-
* **Empty and loading are different answers and a reader can tell them apart.**
|
|
13
|
-
* "No results" under a spinner is a lie for as long as the request is in
|
|
14
|
-
* flight, and a blank box is not an answer at all: a reader cannot know whether
|
|
15
|
-
* a list is empty, still arriving, or broken.
|
|
16
|
-
*
|
|
17
|
-
* **`aria-busy` on the collection, not on the message.** A reader with a screen
|
|
18
|
-
* reader is told the region is changing and then told once, politely, what it
|
|
19
|
-
* settled on: a live region that fires on every intermediate state is a reader
|
|
20
|
-
* being read a progress bar.
|
|
21
|
-
*
|
|
22
|
-
* **The status replaces the rows rather than sitting above them.** A list that
|
|
23
|
-
* shows "Loading" over the last request's results is a list that lies about
|
|
24
|
-
* which question it is answering, and this is the point in a component's life
|
|
25
|
-
* when a consumer is most likely to be looking at stale content.
|
|
26
|
-
*
|
|
27
|
-
* **`empty` and `loading` are documented on each of the five families that
|
|
28
|
-
* apply this, rather than here.** See `MixinField` for why a class-level tag on
|
|
29
|
-
* a mixin reaches no manifest.
|
|
30
|
-
*/
|
|
31
9
|
export const MixinCollection = (Base) => {
|
|
32
10
|
class Collection extends Base {
|
|
33
11
|
constructor() {
|
|
34
12
|
super(...arguments);
|
|
35
|
-
/**
|
|
36
|
-
* What is said where there are none, and none are coming.
|
|
37
|
-
*/
|
|
38
13
|
this.emptyText = 'Nothing to show';
|
|
39
|
-
/**
|
|
40
|
-
* Whether the rows are still on their way.
|
|
41
|
-
*
|
|
42
|
-
* Reflected, because a stylesheet needs it: the rows that are already there
|
|
43
|
-
* are dimmed rather than removed while a new set arrives, and only CSS can
|
|
44
|
-
* say that without the component knowing what a row looks like.
|
|
45
|
-
*/
|
|
46
14
|
this.loading = false;
|
|
47
|
-
/**
|
|
48
|
-
* What is said while they are on their way.
|
|
49
|
-
*/
|
|
50
15
|
this.loadingText = 'Loading';
|
|
51
|
-
/**
|
|
52
|
-
* Whether the rows have been looked at yet.
|
|
53
|
-
*/
|
|
54
16
|
this.slotCounted = false;
|
|
55
|
-
/**
|
|
56
|
-
* How many there were, the last time anyone looked.
|
|
57
|
-
*
|
|
58
|
-
* **Never name this `rows`.** TypeScript's `private` is a compile-time
|
|
59
|
-
* claim and the runtime field is whatever it is named, so a subclass
|
|
60
|
-
* declaring `@property() rows` for the data a consumer hands it would share
|
|
61
|
-
* one field with this counter: the slot count would overwrite the
|
|
62
|
-
* consumer's array on every `updated`, and `hasRows` would read the array's
|
|
63
|
-
* length as a slot count. Nothing would throw. `helix-data-table` takes
|
|
64
|
-
* exactly that property.
|
|
65
|
-
*/
|
|
66
17
|
this.slotRows = 0;
|
|
67
18
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Counts what a rows slot was given.
|
|
70
|
-
*
|
|
71
|
-
* Emptiness is a fact about light DOM children and the server has none to
|
|
72
|
-
* count. `slotchange` never fires there, so `counted` is false exactly when
|
|
73
|
-
* the answer is unknown, and an unknown answer is not "empty": a list that
|
|
74
|
-
* shipped "Nothing to show" over markup about to have rows in it is the one
|
|
75
|
-
* thing an empty state must never do.
|
|
76
|
-
*/
|
|
77
19
|
countRows(event) {
|
|
78
20
|
this.slotCounted = true;
|
|
79
21
|
this.slotRows = event.target.assignedElements().length;
|
|
@@ -81,24 +23,6 @@ export const MixinCollection = (Base) => {
|
|
|
81
23
|
get hasRows() {
|
|
82
24
|
return this.slotCounted ? this.slotRows > 0 : true;
|
|
83
25
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Counts the rows slot after every render.
|
|
86
|
-
*
|
|
87
|
-
* `slotchange` alone is not enough, and the gap is exactly the case this
|
|
88
|
-
* mixin exists for: the event fires when an assignment *changes*, and a
|
|
89
|
-
* collection that arrives with nothing in it has no change to report. So a
|
|
90
|
-
* list that was empty from the start never learned it was empty and said
|
|
91
|
-
* nothing at all, which is the blank box a reader cannot tell from a broken
|
|
92
|
-
* one. Measured on Table, whose rows slot starts empty.
|
|
93
|
-
*
|
|
94
|
-
* The default slot, by name, because that is where every collection in this
|
|
95
|
-
* library puts its rows: Listbox its options, Tree its items, Table its
|
|
96
|
-
* rows, Menu and Drilldown their pages. A named slot is a header or a
|
|
97
|
-
* footer, and neither is content.
|
|
98
|
-
*
|
|
99
|
-
* `updated` never runs on the server, which is what keeps `counted` false
|
|
100
|
-
* exactly when the answer is unknown.
|
|
101
|
-
*/
|
|
102
26
|
updated(changed) {
|
|
103
27
|
super.updated?.(changed);
|
|
104
28
|
const slot = this.renderRoot?.querySelector?.('slot:not([name])');
|
|
@@ -111,13 +35,6 @@ export const MixinCollection = (Base) => {
|
|
|
111
35
|
this.slotRows = rows;
|
|
112
36
|
}
|
|
113
37
|
}
|
|
114
|
-
/**
|
|
115
|
-
* The status line, or nothing where there is content to show.
|
|
116
|
-
*
|
|
117
|
-
* Loading wins over empty. A collection that has not answered yet is not
|
|
118
|
-
* empty, and saying so is the most common version of this fault: the reader
|
|
119
|
-
* is told there is nothing a moment before there is something.
|
|
120
|
-
*/
|
|
121
38
|
renderStatus(hasContent) {
|
|
122
39
|
if (this.loading) {
|
|
123
40
|
return html `<p part="loading" role="status">${this.loadingText}</p>`;
|
|
@@ -8,57 +8,13 @@ import { property } from 'lit/decorators.js';
|
|
|
8
8
|
import { HelixActivateEvent, HelixBeforeActivateEvent } from '../../events/events.js';
|
|
9
9
|
import { requestChange } from '../../utils/request-change/request-change.js';
|
|
10
10
|
import { resolveActiveItem, } from '../../utils/resolve-active-item/resolve-active-item.js';
|
|
11
|
-
/**
|
|
12
|
-
* Grafts "which one of these is the current one" onto a container.
|
|
13
|
-
*
|
|
14
|
-
* For a set where one member is current and the set is not a form control:
|
|
15
|
-
* tabs, a tree, a nav bar, a carousel. Where it *is* a form control the value
|
|
16
|
-
* is submitted and belongs to `MixinControllerValue`, which is why these are
|
|
17
|
-
* two mixins and not one with a flag.
|
|
18
|
-
*
|
|
19
|
-
* **Announcing before applying is the part worth sharing.** Writing the current
|
|
20
|
-
* value straight from the item's event leaves a consumer no way to refuse a
|
|
21
|
-
* change, and whether a selection can be refused must not depend on which
|
|
22
|
-
* component a reader is looking at.
|
|
23
|
-
*
|
|
24
|
-
* **The fallback is the other half.** `items.find(match) ?? items[0]` is not
|
|
25
|
-
* the obvious line it looks like: it is what makes a set whose `active` names
|
|
26
|
-
* nothing still show something, which is every set before its consumer has set
|
|
27
|
-
* one and every set whose current item has just been removed. `resolveActive`
|
|
28
|
-
* also skips disabled items in that fallback, or a set opening on a disabled
|
|
29
|
-
* first item shows it as current.
|
|
30
|
-
*/
|
|
31
11
|
export const MixinControllerActive = (Base) => {
|
|
32
|
-
// JSDoc is duplicated from the interface deliberately. The interface is what
|
|
33
|
-
// TypeScript consumers see; this class is what the manifest analyzer reads
|
|
34
|
-
// when resolving inherited members into dependent packages.
|
|
35
12
|
class ActiveElement extends Base {
|
|
36
13
|
constructor() {
|
|
37
14
|
super(...arguments);
|
|
38
|
-
/**
|
|
39
|
-
* Initial item for uncontrolled use.
|
|
40
|
-
*
|
|
41
|
-
* Ignored once `active` is written.
|
|
42
|
-
*/
|
|
43
15
|
this.defaultActive = '';
|
|
44
|
-
/**
|
|
45
|
-
* The state, whoever set it, and `undefined` until somebody has.
|
|
46
|
-
*
|
|
47
|
-
* One field and not two. Writing always applies, the way a native control
|
|
48
|
-
* does when a script assigns to it.
|
|
49
|
-
*/
|
|
50
16
|
this.activeState = undefined;
|
|
51
17
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Which item is current, by its `value`.
|
|
54
|
-
*
|
|
55
|
-
* Not `option.active`, which is the option the keyboard is on: that is a
|
|
56
|
-
* different question from which one is chosen.
|
|
57
|
-
*
|
|
58
|
-
* Writing always applies, so `<helix-tabs active="one">` starts on that
|
|
59
|
-
* tab, and `default-active` is how markup says where to start without
|
|
60
|
-
* naming the current one.
|
|
61
|
-
*/
|
|
62
18
|
get active() {
|
|
63
19
|
return this.activeState ?? this.defaultActive;
|
|
64
20
|
}
|
|
@@ -67,16 +23,6 @@ export const MixinControllerActive = (Base) => {
|
|
|
67
23
|
this.activeState = next;
|
|
68
24
|
this.requestUpdate('active', previous);
|
|
69
25
|
}
|
|
70
|
-
/**
|
|
71
|
-
* Requests a new current item, and reports whether the request stood.
|
|
72
|
-
*
|
|
73
|
-
* Announces before it acts, so `preventDefault()` on the `helix-before-activate` is
|
|
74
|
-
* a refusal and nothing moves. Unlike a native control, nothing has moved
|
|
75
|
-
* by the time this runs, so refusing is simply not writing: there is
|
|
76
|
-
* nothing to put back. Nobody refusing means the set updates itself,
|
|
77
|
-
* whether or not a consumer declared `active`, a declared property being a
|
|
78
|
-
* seed and a correction.
|
|
79
|
-
*/
|
|
80
26
|
requestActive(next) {
|
|
81
27
|
if (next === this.active) {
|
|
82
28
|
return true;
|
|
@@ -96,37 +42,9 @@ export const MixinControllerActive = (Base) => {
|
|
|
96
42
|
}
|
|
97
43
|
return applied;
|
|
98
44
|
}
|
|
99
|
-
/**
|
|
100
|
-
* Says the current child moved, once it has.
|
|
101
|
-
*
|
|
102
|
-
* `helix-activate` rather than a native word: nothing on the platform means
|
|
103
|
-
* "a set chose a different member", and borrowing `change` would be a lie
|
|
104
|
-
* about what moved.
|
|
105
|
-
*
|
|
106
|
-
* A consumer who only wants to know which child is current cannot use
|
|
107
|
-
* `helix-before-activate` for it, that one firing *before* `apply`, so
|
|
108
|
-
* `currentTarget.active` is the previous value inside the handler and
|
|
109
|
-
* `detail.value` is a proposal another listener may refuse. `helix-select`
|
|
110
|
-
* is no substitute either: it fires when an item is *chosen* and not when
|
|
111
|
-
* `active` is assigned.
|
|
112
|
-
*
|
|
113
|
-
* **After `apply`, which is the whole reason it is a second event**, so the
|
|
114
|
-
* state is on the element by the time a handler reads it. Not cancelable:
|
|
115
|
-
* the decision was taken by the event before this one, and two refusable
|
|
116
|
-
* events for one transition are two answers to one question.
|
|
117
|
-
*/
|
|
118
45
|
announceActivated(value) {
|
|
119
46
|
this.dispatchEvent(new HelixActivateEvent({ value }));
|
|
120
47
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Which item is current, given what there is to choose from.
|
|
123
|
-
*
|
|
124
|
-
* The first enabled one when `active` names nothing, which is a set before
|
|
125
|
-
* its consumer has chosen and a set whose current item has just been
|
|
126
|
-
* removed. Disabled items are skipped in the fallback but not in the match:
|
|
127
|
-
* a consumer who names a disabled item meant it, and quietly moving to
|
|
128
|
-
* another would be the component overruling them.
|
|
129
|
-
*/
|
|
130
48
|
resolveActive(items) {
|
|
131
49
|
return resolveActiveItem(this.active, items);
|
|
132
50
|
}
|
|
@@ -7,47 +7,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { property } from 'lit/decorators.js';
|
|
8
8
|
import { HelixBeforeToggleEvent } from '../../events/events.js';
|
|
9
9
|
import { requestChange } from '../../utils/request-change/request-change.js';
|
|
10
|
-
/**
|
|
11
|
-
* Grafts checked state onto a base class.
|
|
12
|
-
*
|
|
13
|
-
* `checked` is a seed and a correction rather than a claim: the element applies
|
|
14
|
-
* its own changes from wherever markup started it, `defaultChecked` is what a
|
|
15
|
-
* reset returns to, and a consumer takes the decision by refusing the
|
|
16
|
-
* `helix-before-toggle`.
|
|
17
|
-
*
|
|
18
|
-
* Records `:state(checked)` whenever the element is checked. Styled tiers
|
|
19
|
-
* select on that rather than on `[checked]`, which is absent until somebody
|
|
20
|
-
* writes it.
|
|
21
|
-
*/
|
|
22
10
|
export const MixinControllerChecked = (Base) => {
|
|
23
|
-
// JSDoc is duplicated from MixinControllerCheckedInterface deliberately. The
|
|
24
|
-
// interface is what TypeScript consumers see; this class is what the manifest
|
|
25
|
-
// analyzer reads when resolving inherited members into dependent packages.
|
|
26
11
|
class CheckedElement extends Base {
|
|
27
12
|
constructor() {
|
|
28
13
|
super(...arguments);
|
|
29
|
-
/**
|
|
30
|
-
* Initial checked state for uncontrolled use, where nothing declared one.
|
|
31
|
-
*/
|
|
32
14
|
this.defaultChecked = false;
|
|
33
|
-
/**
|
|
34
|
-
* The state, whoever set it, and `undefined` until somebody has.
|
|
35
|
-
*
|
|
36
|
-
* One field and not two. Writing always applies, the way a native control
|
|
37
|
-
* does when a script assigns to it.
|
|
38
|
-
*/
|
|
39
15
|
this.checkedState = undefined;
|
|
40
16
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Whether the control is checked.
|
|
43
|
-
*
|
|
44
|
-
* One property rather than two. Writing always applies;
|
|
45
|
-
* `<helix-checkbox checked>` is the same statement in markup.
|
|
46
|
-
*
|
|
47
|
-
* Not reflected: the attribute is the consumer's input and stays as they
|
|
48
|
-
* wrote it, and `:state(checked)` carries the resolved state for
|
|
49
|
-
* stylesheets.
|
|
50
|
-
*/
|
|
51
17
|
get checked() {
|
|
52
18
|
return this.checkedState ?? this.defaultChecked;
|
|
53
19
|
}
|
|
@@ -56,33 +22,10 @@ export const MixinControllerChecked = (Base) => {
|
|
|
56
22
|
this.checkedState = next;
|
|
57
23
|
this.requestUpdate('checked', previous);
|
|
58
24
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Records the resolved state as the custom state `checked`.
|
|
61
|
-
*
|
|
62
|
-
* **A custom state and never `data-checked`.** An attribute written here
|
|
63
|
-
* means `<helix-checkbox default-checked>` comes back from a consumer's
|
|
64
|
-
* server without it and gains it the moment the definition lands, which
|
|
65
|
-
* React lists as a hydration mismatch and does not patch up.
|
|
66
|
-
* `HelixElement.toggleState` says why the host is the wrong place to keep a
|
|
67
|
-
* component's own bookkeeping.
|
|
68
|
-
*
|
|
69
|
-
* In `willUpdate`, which is where the resolved state is first known and is
|
|
70
|
-
* what a subclass rendering from it expects.
|
|
71
|
-
*/
|
|
72
25
|
willUpdate(changed) {
|
|
73
26
|
super.willUpdate(changed);
|
|
74
27
|
this.toggleState('checked', this.checked);
|
|
75
28
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Requests a checked change, and reports whether it was applied.
|
|
78
|
-
*
|
|
79
|
-
* Announces before it acts, so `preventDefault()` on the
|
|
80
|
-
* `helix-before-toggle` is a refusal and nothing moves. Nobody refusing
|
|
81
|
-
* means the element updates itself, whether or not a consumer declared the
|
|
82
|
-
* property: a declared property is a seed and a correction, the way
|
|
83
|
-
* `<input value>` is, and a consumer who wants the change for themselves
|
|
84
|
-
* refuses it and assigns their own.
|
|
85
|
-
*/
|
|
86
29
|
requestChecked(next) {
|
|
87
30
|
if (this.checked === next) {
|
|
88
31
|
return true;
|
|
@@ -98,37 +41,16 @@ export const MixinControllerChecked = (Base) => {
|
|
|
98
41
|
next,
|
|
99
42
|
});
|
|
100
43
|
if (applied) {
|
|
101
|
-
/* `input` here and `change` relayed from the inner control, which is
|
|
102
|
-
the pair a native checkbox sends. Announced after `apply`, so a
|
|
103
|
-
handler reading `currentTarget.checked` reads the new state. */
|
|
104
44
|
this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
|
105
45
|
}
|
|
106
46
|
return applied;
|
|
107
47
|
}
|
|
108
|
-
/** Requests the opposite of the current state. */
|
|
109
48
|
toggleChecked() {
|
|
110
49
|
return this.requestChecked(!this.checked);
|
|
111
50
|
}
|
|
112
|
-
/**
|
|
113
|
-
* Restores the control when the browser brings a form back.
|
|
114
|
-
*
|
|
115
|
-
* The mixin owns `checked`, so the mixin owns putting it back: written on
|
|
116
|
-
* one consumer and not the other, a reader who navigates away and comes
|
|
117
|
-
* back to a restored form finds that control flipped off.
|
|
118
|
-
*
|
|
119
|
-
* Through the public accessor rather than the private field, so a restore
|
|
120
|
-
* takes the same path a script assignment does.
|
|
121
|
-
*/
|
|
122
51
|
formStateRestoreCallback(state) {
|
|
123
52
|
this.checked = state !== null;
|
|
124
53
|
}
|
|
125
|
-
/**
|
|
126
|
-
* Drops the recorded state, so the control reads `defaultChecked` again.
|
|
127
|
-
*
|
|
128
|
-
* A form reset has to return a control to what its markup declared, and no
|
|
129
|
-
* assignment to `checked` can do that: any assignment records a state. The
|
|
130
|
-
* recorded state lives here, so this is where it is dropped.
|
|
131
|
-
*/
|
|
132
54
|
resetChecked() {
|
|
133
55
|
const previous = this.checked;
|
|
134
56
|
this.checkedState = undefined;
|