@fusion.dev/helix-core 0.0.0-dev

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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/custom-elements.json +8005 -0
  3. package/dist/controllers/aria-forward-controller/aria-forward-controller.d.ts +67 -0
  4. package/dist/controllers/aria-forward-controller/aria-forward-controller.js +146 -0
  5. package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +79 -0
  6. package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +140 -0
  7. package/dist/controllers/command-controller/command-controller.d.ts +83 -0
  8. package/dist/controllers/command-controller/command-controller.js +93 -0
  9. package/dist/controllers/copy-controller/copy-controller.d.ts +58 -0
  10. package/dist/controllers/copy-controller/copy-controller.js +128 -0
  11. package/dist/controllers/disabled-cascade-controller/disabled-cascade-controller.d.ts +76 -0
  12. package/dist/controllers/disabled-cascade-controller/disabled-cascade-controller.js +67 -0
  13. package/dist/controllers/dismiss-controller/dismiss-controller.d.ts +58 -0
  14. package/dist/controllers/dismiss-controller/dismiss-controller.js +165 -0
  15. package/dist/controllers/drilldown-controller/drilldown-controller.d.ts +127 -0
  16. package/dist/controllers/drilldown-controller/drilldown-controller.js +171 -0
  17. package/dist/controllers/floating-controller/floating-controller.d.ts +123 -0
  18. package/dist/controllers/floating-controller/floating-controller.js +227 -0
  19. package/dist/controllers/focus-trap-controller/focus-trap-controller.d.ts +26 -0
  20. package/dist/controllers/focus-trap-controller/focus-trap-controller.js +93 -0
  21. package/dist/controllers/indicator-controller/indicator-controller.d.ts +97 -0
  22. package/dist/controllers/indicator-controller/indicator-controller.js +164 -0
  23. package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.d.ts +113 -0
  24. package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +117 -0
  25. package/dist/controllers/popover-controller/popover-controller.d.ts +132 -0
  26. package/dist/controllers/popover-controller/popover-controller.js +150 -0
  27. package/dist/controllers/source-controller/source-controller.d.ts +55 -0
  28. package/dist/controllers/source-controller/source-controller.js +94 -0
  29. package/dist/controllers/token-color-controller/token-color-controller.d.ts +63 -0
  30. package/dist/controllers/token-color-controller/token-color-controller.js +105 -0
  31. package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +127 -0
  32. package/dist/controllers/virtualize-controller/virtualize-controller.js +216 -0
  33. package/dist/events/element-events.d.ts +4 -0
  34. package/dist/events/element-events.js +34 -0
  35. package/dist/events/events.d.ts +743 -0
  36. package/dist/events/events.js +799 -0
  37. package/dist/helix-element/helix-element.d.ts +201 -0
  38. package/dist/helix-element/helix-element.js +695 -0
  39. package/dist/helix-form-element/helix-form-element.d.ts +152 -0
  40. package/dist/helix-form-element/helix-form-element.js +194 -0
  41. package/dist/index.d.ts +54 -0
  42. package/dist/index.js +54 -0
  43. package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +137 -0
  44. package/dist/mixins/mixin-chart-source/mixin-chart-source.js +140 -0
  45. package/dist/mixins/mixin-collection/index.d.ts +1 -0
  46. package/dist/mixins/mixin-collection/index.js +1 -0
  47. package/dist/mixins/mixin-collection/mixin-collection.d.ts +58 -0
  48. package/dist/mixins/mixin-collection/mixin-collection.js +154 -0
  49. package/dist/mixins/mixin-controller-active/mixin-controller-active.d.ts +53 -0
  50. package/dist/mixins/mixin-controller-active/mixin-controller-active.js +155 -0
  51. package/dist/mixins/mixin-controller-checked/mixin-controller-checked.d.ts +54 -0
  52. package/dist/mixins/mixin-controller-checked/mixin-controller-checked.js +157 -0
  53. package/dist/mixins/mixin-controller-open/mixin-controller-open.d.ts +49 -0
  54. package/dist/mixins/mixin-controller-open/mixin-controller-open.js +176 -0
  55. package/dist/mixins/mixin-controller-value/mixin-controller-value.d.ts +73 -0
  56. package/dist/mixins/mixin-controller-value/mixin-controller-value.js +173 -0
  57. package/dist/mixins/mixin-dismissible/mixin-dismissible.d.ts +41 -0
  58. package/dist/mixins/mixin-dismissible/mixin-dismissible.js +79 -0
  59. package/dist/mixins/mixin-field/mixin-field.d.ts +145 -0
  60. package/dist/mixins/mixin-field/mixin-field.js +423 -0
  61. package/dist/utils/anchor-element/anchor-element.d.ts +23 -0
  62. package/dist/utils/anchor-element/anchor-element.js +39 -0
  63. package/dist/utils/capture-pointer/capture-pointer.d.ts +22 -0
  64. package/dist/utils/capture-pointer/capture-pointer.js +30 -0
  65. package/dist/utils/closest-instance/closest-instance.d.ts +25 -0
  66. package/dist/utils/closest-instance/closest-instance.js +34 -0
  67. package/dist/utils/create-sheet/create-sheet.d.ts +16 -0
  68. package/dist/utils/create-sheet/create-sheet.js +16 -0
  69. package/dist/utils/custom-element/custom-element.d.ts +26 -0
  70. package/dist/utils/custom-element/custom-element.js +114 -0
  71. package/dist/utils/draw-icon/draw-icon.d.ts +38 -0
  72. package/dist/utils/draw-icon/draw-icon.js +39 -0
  73. package/dist/utils/draw-tooltip/draw-tooltip.d.ts +56 -0
  74. package/dist/utils/draw-tooltip/draw-tooltip.js +89 -0
  75. package/dist/utils/elapsed-time/elapsed-time.d.ts +10 -0
  76. package/dist/utils/elapsed-time/elapsed-time.js +117 -0
  77. package/dist/utils/focusable-elements/focusable-elements.d.ts +40 -0
  78. package/dist/utils/focusable-elements/focusable-elements.js +88 -0
  79. package/dist/utils/format-numbers/format-numbers.d.ts +1 -0
  80. package/dist/utils/format-numbers/format-numbers.js +29 -0
  81. package/dist/utils/host-aria/host-aria.d.ts +38 -0
  82. package/dist/utils/host-aria/host-aria.js +41 -0
  83. package/dist/utils/inherit-disabled/inherit-disabled.d.ts +33 -0
  84. package/dist/utils/inherit-disabled/inherit-disabled.js +55 -0
  85. package/dist/utils/line-path/line-path.d.ts +23 -0
  86. package/dist/utils/line-path/line-path.js +107 -0
  87. package/dist/utils/on-unless-false/on-unless-false.d.ts +28 -0
  88. package/dist/utils/on-unless-false/on-unless-false.js +32 -0
  89. package/dist/utils/read-series/read-series.d.ts +78 -0
  90. package/dist/utils/read-series/read-series.js +187 -0
  91. package/dist/utils/relative-time/relative-time.d.ts +24 -0
  92. package/dist/utils/relative-time/relative-time.js +84 -0
  93. package/dist/utils/request-change/request-change.d.ts +48 -0
  94. package/dist/utils/request-change/request-change.js +7 -0
  95. package/dist/utils/resolve-active-item/resolve-active-item.d.ts +28 -0
  96. package/dist/utils/resolve-active-item/resolve-active-item.js +21 -0
  97. package/dist/utils/restore-value/restore-value.d.ts +32 -0
  98. package/dist/utils/restore-value/restore-value.js +84 -0
  99. package/dist/utils/scale/scale.d.ts +23 -0
  100. package/dist/utils/scale/scale.js +32 -0
  101. package/dist/utils/scale/ticks.d.ts +27 -0
  102. package/dist/utils/scale/ticks.js +52 -0
  103. package/dist/utils/scroll-item-into-view/scroll-item-into-view.d.ts +10 -0
  104. package/dist/utils/scroll-item-into-view/scroll-item-into-view.js +98 -0
  105. package/dist/utils/slot-filled/slot-filled.d.ts +26 -0
  106. package/dist/utils/slot-filled/slot-filled.js +26 -0
  107. package/dist/utils/tell-disclosure/tell-disclosure.d.ts +51 -0
  108. package/dist/utils/tell-disclosure/tell-disclosure.js +76 -0
  109. package/dist/utils/ticker/ticker.d.ts +8 -0
  110. package/dist/utils/ticker/ticker.js +55 -0
  111. package/dist/utils/tone-marks/tone-marks.d.ts +52 -0
  112. package/dist/utils/tone-marks/tone-marks.js +36 -0
  113. package/dist/utils/value-as/value-as.d.ts +64 -0
  114. package/dist/utils/value-as/value-as.js +105 -0
  115. package/package.json +58 -0
@@ -0,0 +1,157 @@
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 { property } from 'lit/decorators.js';
8
+ import { HelixBeforeToggleEvent } from '../../events/events.js';
9
+ import { requestChange } from '../../utils/request-change/request-change.js';
10
+ /**
11
+ * Grafts controlled and uncontrolled checked state onto a base class.
12
+ *
13
+ * Which mode it is in is read once, at the first update, from whether anything
14
+ * had set `checked` by then: markup, a hydrating server's attributes and a
15
+ * framework's initial properties have all arrived, and nothing else has
16
+ * happened yet. Set, and the consumer owns it and the element only announces.
17
+ * Unset, and the element owns it and `defaultChecked` says where it starts.
18
+ *
19
+ * Records `:state(checked)` whenever the element is checked, in either mode.
20
+ * Styled tiers select on that rather than on `[checked]`, which is absent for
21
+ * uncontrolled consumers.
22
+ */
23
+ export const MixinControllerChecked = (Base) => {
24
+ // JSDoc is duplicated from MixinControllerCheckedInterface deliberately. The
25
+ // interface is what TypeScript consumers see; this class is what the manifest
26
+ // analyzer reads when resolving inherited members into dependent packages.
27
+ class CheckedElement extends Base {
28
+ constructor() {
29
+ super(...arguments);
30
+ /**
31
+ * Initial checked state for uncontrolled use, where nothing declared one.
32
+ */
33
+ this.defaultChecked = false;
34
+ /**
35
+ * The state, whoever set it, and `undefined` until somebody has.
36
+ *
37
+ * One field and not two. Writing always applies, the way a native control
38
+ * does when a script assigns to it. What the write does not do is change
39
+ * which mode the element is in.
40
+ */
41
+ this.checkedState = undefined;
42
+ }
43
+ /**
44
+ * Whether the control is checked.
45
+ *
46
+ * One property rather than two. Reading gives the real state whichever
47
+ * mode the control is in; writing hands ownership to the consumer, which
48
+ * is what the attribute in `<helix-checkbox checked>` does as well. The
49
+ * same shape all four of these mixins have, for the reason given on
50
+ * `MixinControllerOpen.open`.
51
+ *
52
+ * Not reflected: the attribute is the consumer's input and stays as they
53
+ * wrote it, and `:state(checked)` carries the resolved state for
54
+ * stylesheets.
55
+ *
56
+ *
57
+ */
58
+ get checked() {
59
+ return this.checkedState ?? this.defaultChecked;
60
+ }
61
+ set checked(next) {
62
+ const previous = this.checked;
63
+ this.checkedState = next;
64
+ this.requestUpdate('checked', previous);
65
+ }
66
+ /**
67
+ * Records the resolved state as the custom state `checked`.
68
+ *
69
+ * **A state and not `data-checked`, which is what this wrote until 27 Aug
70
+ * 2026.** The attribute went on in `willUpdate`, so `<helix-checkbox
71
+ * default-checked>` came back from a consumer's server without it and
72
+ * gained it the moment the definition landed, which React lists as a
73
+ * hydration mismatch and does not patch up. `HelixElement.toggleState` says
74
+ * why the host is the wrong place to keep a component's own bookkeeping.
75
+ *
76
+ * Still in `willUpdate`, which is where the resolved state is first known
77
+ * and is what a subclass rendering from it expects. The old note here said
78
+ * SSR calls `willUpdate` and not `connectedCallback`, so the attribute
79
+ * reached server output: true, and no longer a reason for anything, a
80
+ * custom state being a fact about a running element that a server has
81
+ * nothing to say about.
82
+ */
83
+ willUpdate(changed) {
84
+ super.willUpdate(changed);
85
+ this.toggleState('checked', this.checked);
86
+ }
87
+ /**
88
+ * Requests a checked change, and reports whether it was applied.
89
+ *
90
+ * Announces before it acts, so `preventDefault()` on the
91
+ * `helix-before-toggle` is a refusal and nothing moves. Nobody refusing means the element updates itself, whether or not a consumer
92
+ * declared the property: a declared property is a seed and a correction,
93
+ * the way `<input value>` is, and a consumer who wants the change for
94
+ * themselves refuses it and assigns their own. See 294.
95
+ */
96
+ requestChecked(next) {
97
+ if (this.checked === next) {
98
+ return true;
99
+ }
100
+ const applied = requestChange({
101
+ apply: (checked) => {
102
+ const previous = this.checked;
103
+ this.checkedState = checked;
104
+ this.requestUpdate('checked', previous);
105
+ },
106
+ event: (checked) => new HelixBeforeToggleEvent({ checked }),
107
+ host: this,
108
+ next,
109
+ });
110
+ if (applied) {
111
+ /* `input` here and `change` relayed from the inner control, which is
112
+ the pair a native checkbox sends. Announced after `apply`, so a
113
+ handler reading `currentTarget.checked` reads the new state. */
114
+ this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
115
+ }
116
+ return applied;
117
+ }
118
+ /** Requests the opposite of the current state. */
119
+ toggleChecked() {
120
+ return this.requestChecked(!this.checked);
121
+ }
122
+ /**
123
+ * Restores the control when the browser brings a form back.
124
+ *
125
+ * Hand-written on the checkbox and nowhere else, so `Switch`, the mixin's
126
+ * only other consumer, had no restore at all: a reader who navigated away
127
+ * and came back to a restored form found their switch flipped off. The
128
+ * mixin owns `checked`, so the mixin owns putting it back.
129
+ *
130
+ * Through `checked` rather than the private state, so a controlled consumer
131
+ * is asked the same way every other change asks.
132
+ */
133
+ formStateRestoreCallback(state) {
134
+ this.checked = state !== null;
135
+ }
136
+ /**
137
+ * Drops the uncontrolled state, so the control reads `defaultChecked`
138
+ * again.
139
+ *
140
+ * A form reset has to return a control to what its markup declared, and
141
+ * clearing `checked` alone only leaves controlled mode. The value the
142
+ * element has been keeping lives here, so this is where it is dropped.
143
+ */
144
+ resetChecked() {
145
+ const previous = this.checked;
146
+ this.checkedState = undefined;
147
+ this.requestUpdate('checked', previous);
148
+ }
149
+ }
150
+ __decorate([
151
+ property({ type: Boolean })
152
+ ], CheckedElement.prototype, "checked", null);
153
+ __decorate([
154
+ property({ attribute: 'default-checked', type: Boolean })
155
+ ], CheckedElement.prototype, "defaultChecked", void 0);
156
+ return CheckedElement;
157
+ };
@@ -0,0 +1,49 @@
1
+ import type { HelixElement } from '../../helix-element/helix-element.js';
2
+ type Constructor<T> = new (...args: any[]) => T;
3
+ /**
4
+ * The public surface `MixinControllerOpen` adds.
5
+ *
6
+ * Declared separately because TypeScript cannot emit declarations for an
7
+ * anonymous class that has private members (TS4094). Casting the mixin's return
8
+ * to this interface keeps the internals private and the build working.
9
+ */
10
+ export declare class MixinControllerOpenInterface {
11
+ /** Initial open state for uncontrolled use. Ignored once `open` is set. */
12
+ defaultOpen: boolean;
13
+ /**
14
+ * Whether the element is open.
15
+ *
16
+ * Reading gives the real state, in either mode. Writing hands ownership to
17
+ * the consumer: from then on the element announces changes and applies none
18
+ * of them, and waits to be told.
19
+ */
20
+ open: boolean;
21
+ /**
22
+ * Requests an open-state change, and reports whether it was applied.
23
+ *
24
+ * Announces before it acts: a listener calling `preventDefault()` on the
25
+ * `helix-before-open` or `helix-before-close` refuses the change, and nothing moves.
26
+ */
27
+ requestOpen(next: boolean): boolean;
28
+ /** Requests the opposite of the current state. */
29
+ toggleOpen(): boolean;
30
+ }
31
+ /**
32
+ * Grafts open state onto a base class, and says so twice.
33
+ *
34
+ * `helix-before-open` and `helix-before-close` announce the change while it is
35
+ * still a request, so `preventDefault()` refuses it and nothing moves. Native
36
+ * `toggle` announces it afterwards, once the state has actually gone, carrying
37
+ * `newState` and `oldState` the way the platform does on `<details>` and on a
38
+ * popover. The first is how a consumer takes the decision; the second is what
39
+ * they bind when they only want to know. See 294 and 296.
40
+ *
41
+ * `open` is a seed and a correction rather than a claim: the element applies
42
+ * its own changes from wherever markup started it, and `defaultOpen` is what a
43
+ * reset returns to.
44
+ *
45
+ * Records `:state(open)` whenever the element is open. Styled tiers select on
46
+ * that rather than on `[open]`, which is absent until somebody writes it.
47
+ */
48
+ export declare const MixinControllerOpen: <T extends Constructor<HelixElement>>(Base: T) => Constructor<MixinControllerOpenInterface> & T;
49
+ export {};
@@ -0,0 +1,176 @@
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 { property } from 'lit/decorators.js';
8
+ import { HelixBeforeCloseEvent, HelixBeforeOpenEvent } from '../../events/events.js';
9
+ import { requestChange } from '../../utils/request-change/request-change.js';
10
+ /**
11
+ * Grafts open state onto a base class, and says so twice.
12
+ *
13
+ * `helix-before-open` and `helix-before-close` announce the change while it is
14
+ * still a request, so `preventDefault()` refuses it and nothing moves. Native
15
+ * `toggle` announces it afterwards, once the state has actually gone, carrying
16
+ * `newState` and `oldState` the way the platform does on `<details>` and on a
17
+ * popover. The first is how a consumer takes the decision; the second is what
18
+ * they bind when they only want to know. See 294 and 296.
19
+ *
20
+ * `open` is a seed and a correction rather than a claim: the element applies
21
+ * its own changes from wherever markup started it, and `defaultOpen` is what a
22
+ * reset returns to.
23
+ *
24
+ * Records `:state(open)` whenever the element is open. Styled tiers select on
25
+ * that rather than on `[open]`, which is absent until somebody writes it.
26
+ */
27
+ export const MixinControllerOpen = (Base) => {
28
+ // JSDoc is duplicated from MixinControllerOpenInterface deliberately. The interface is
29
+ // what TypeScript consumers see; this class is what the manifest analyzer
30
+ // reads when resolving inherited members into dependent packages.
31
+ class OpenElement extends Base {
32
+ constructor() {
33
+ super(...arguments);
34
+ /**
35
+ * Initial open state for uncontrolled use, where nothing declared one.
36
+ */
37
+ this.defaultOpen = false;
38
+ /**
39
+ * The state, whoever set it, and `undefined` until somebody has.
40
+ *
41
+ * Prefixed, and it has to be. These mixins compose onto one prototype
42
+ * chain, `Select` takes this one and `MixinControllerValue` together, and
43
+ * TypeScript's `private` is a compile-time fiction: two mixins with a field
44
+ * called `state` share one property on the instance, and the second one
45
+ * silently overwrites the first. Measured, as a select whose `value` came
46
+ * back holding its `open` boolean.
47
+ *
48
+ * One field and not two. Writing always applies, which is what a native
49
+ * control does when a script assigns to it, and is the behavior an
50
+ * imperative `element.open = true` should have whatever mode the element is
51
+ * in. What the write does *not* do is change which mode that is.
52
+ */
53
+ this.openState = undefined;
54
+ }
55
+ /**
56
+ * Whether the element is open.
57
+ *
58
+ * One property, not two. Reading gives the real state whichever mode the
59
+ * element is in, so `dialog.open` answers the question anyone asking it
60
+ * meant. Writing hands ownership to the consumer, which is what the
61
+ * attribute in `<helix-dialog open>` does as well: from then on the element
62
+ * announces changes and applies none of them.
63
+ *
64
+ * It reads as an accessor pair rather than a field because those are two
65
+ * different questions sharing one name, and the alternative was a second
66
+ * property answering the first. That worked for a boolean, where `isOpen`
67
+ * reads well; there is no such word for a value or an active item, so all
68
+ * four of these mixins now have this shape instead of two shapes.
69
+ *
70
+ * **Not reflected, and that is required rather than tidy.** Reflection
71
+ * writes the resolved state back as an attribute, and writing the attribute
72
+ * is how a consumer says they own it. So an uncontrolled element that
73
+ * opened itself would put `open` on its own host and, from the next thing
74
+ * that read it, be indistinguishable from one the consumer controls:
75
+ * cloned, serialised for hydration, or diffed by a framework, it would come
76
+ * back holding whatever the last reader did rather than what the author
77
+ * wrote. `:state(open)` carries the resolved state for stylesheets, which
78
+ * is what the mirror has always been for.
79
+ */
80
+ get open() {
81
+ return this.openState ?? this.defaultOpen;
82
+ }
83
+ set open(next) {
84
+ const previous = this.open;
85
+ this.openState = next;
86
+ this.requestUpdate('open', previous);
87
+ }
88
+ /**
89
+ * Records the resolved state as the custom state `open`.
90
+ *
91
+ * **A state and not `data-open`, which is what this wrote until 27 Aug
92
+ * 2026.** An `<helix-accordion-item default-open>` was served without the
93
+ * attribute and gained it on upgrade, which is a hydration mismatch a React
94
+ * consumer reports. See `HelixElement.toggleState`, and see `Toast.Item`
95
+ * for the other answer to the same fault: inverting the hook so the write
96
+ * falls on the side that only happens after somebody acts. Inversion works
97
+ * where the resting state is the loud one and cannot help here, `checked`
98
+ * and `open` being true only because a consumer asked for them.
99
+ */
100
+ willUpdate(changed) {
101
+ super.willUpdate(changed);
102
+ this.toggleState('open', this.open);
103
+ }
104
+ /**
105
+ * Requests an open-state change, and reports whether it was applied.
106
+ *
107
+ * Announces before it acts, so `preventDefault()` on the `helix-before-open` or
108
+ * `helix-before-close` is a refusal and nothing moves. Nobody refusing means the
109
+ * element opens or closes itself, whether or not a consumer declared
110
+ * `open`: a declared property is a seed and a correction, and a consumer
111
+ * who wants the decision for themselves refuses it and assigns their own.
112
+ * See 294.
113
+ */
114
+ requestOpen(next) {
115
+ if (this.open === next) {
116
+ return true;
117
+ }
118
+ const applied = requestChange({
119
+ apply: (open) => {
120
+ const previous = this.open;
121
+ this.openState = open;
122
+ this.requestUpdate('open', previous);
123
+ },
124
+ event: (open) => (open ? new HelixBeforeOpenEvent() : new HelixBeforeCloseEvent()),
125
+ host: this,
126
+ next,
127
+ });
128
+ if (applied) {
129
+ this.announceToggle(next);
130
+ }
131
+ return applied;
132
+ }
133
+ /**
134
+ * Says the state moved, under the platform's own name and in its shape.
135
+ *
136
+ * **`<details>` fires `toggle` and a popover fires `toggle`, and sixteen
137
+ * families here fired neither.** Two of them render a real `<dialog>` and
138
+ * ten show a `popover="manual"` panel, so the browser was already firing
139
+ * something on an element inside a shadow root, where `toggle` neither
140
+ * bubbles nor composes and so reaches nobody. The rest, the disclosures,
141
+ * have no native element to fire anything at all: `AccordionItem` and
142
+ * `TreeItem` are `aria-expanded` and `hidden` written by hand, which is
143
+ * `<details>`'s job done manually.
144
+ *
145
+ * **From here rather than from `PopoverController`, which had it first.**
146
+ * The controller covers eleven families and the mixin covers sixteen, and
147
+ * the overlap is ten that would have announced twice. What `toggle` reports
148
+ * is the *state*, and the state is this mixin's; the controller is a way of
149
+ * putting a panel on the screen.
150
+ *
151
+ * **`newState` and `oldState`, because that is the platform's shape**, so a
152
+ * consumer arriving from `<details>` reads the same two words. Not
153
+ * cancelable: the before events are the request, and a second cancelable
154
+ * event for one transition is two answers to one question.
155
+ */
156
+ announceToggle(open) {
157
+ const event = new Event('toggle', { bubbles: false, cancelable: false, composed: false });
158
+ Object.assign(event, {
159
+ newState: open ? 'open' : 'closed',
160
+ oldState: open ? 'closed' : 'open',
161
+ });
162
+ this.dispatchEvent(event);
163
+ }
164
+ /** Requests the opposite of the current state. */
165
+ toggleOpen() {
166
+ return this.requestOpen(!this.open);
167
+ }
168
+ }
169
+ __decorate([
170
+ property({ type: Boolean })
171
+ ], OpenElement.prototype, "open", null);
172
+ __decorate([
173
+ property({ attribute: 'default-open', type: Boolean })
174
+ ], OpenElement.prototype, "defaultOpen", void 0);
175
+ return OpenElement;
176
+ };
@@ -0,0 +1,73 @@
1
+ import type { HelixFormElement } from '../../helix-form-element/helix-form-element.js';
2
+ type Constructor<T> = new (...args: any[]) => T;
3
+ /**
4
+ * The public surface `MixinControllerValue` adds.
5
+ *
6
+ * Declared separately because TypeScript cannot emit declarations for an
7
+ * anonymous class that has private members (TS4094).
8
+ */
9
+ export declare class MixinControllerValueInterface {
10
+ /**
11
+ * The value the markup declared, which is where a reset returns to.
12
+ *
13
+ * Separate from `value`, which is where the control is now, and gone from the
14
+ * reader's view the moment they change anything.
15
+ */
16
+ defaultValue: string;
17
+ /**
18
+ * What the control holds now, and what its form submits.
19
+ *
20
+ * Reading gives the real value, in either mode. Writing hands ownership to
21
+ * the consumer: from then on the control announces changes and applies none
22
+ * of them, and waits to be told.
23
+ */
24
+ value: string;
25
+ /**
26
+ * Whether every edit is announced, rather than every settled value.
27
+ *
28
+ * A control the reader types or drags in moves continuously, and the platform
29
+ * calls that `input`: `<input type="text">` fires one per keystroke and fires
30
+ * `change` once, on commit. Five controls here are that shape, and the rest
31
+ * change in discrete steps where a step *is* the change.
32
+ *
33
+ * It selects the event rather than the timing. `requestValue` is called
34
+ * whenever the value moves either way; this decides whether the control sends
35
+ * `input` alone or `input` and `change` together, so a consumer can read the
36
+ * name and know how often to expect it.
37
+ */
38
+ protected readonly announcesInput: boolean;
39
+ /** Requests a new value, and reports whether the request stood. */
40
+ requestValue(next: string): boolean;
41
+ /** Returns the control to the value its markup declared. */
42
+ formResetCallback(): void;
43
+ /** Restores the value when the browser brings a form back. */
44
+ formStateRestoreCallback(state: string | null): void;
45
+ }
46
+ /**
47
+ * Grafts `value`, `defaultValue`, and the two callbacks that answer a form.
48
+ *
49
+ * Every control with a value had written this, and by the third it was three
50
+ * copies of the same four members and the same seeding rule: `Input`, then
51
+ * `Toggle.Group`, then `Radio.Group`. The copies had not diverged, which is the
52
+ * argument for the mixin rather than against it, because the next one would
53
+ * have.
54
+ *
55
+ * **Seeded in `willUpdate`, not `connectedCallback`.** SSR never calls the
56
+ * latter, so a control with a `default-value` would serve empty and fill itself
57
+ * at hydration, which is a flash for a reader and a mismatch for React. Seeding
58
+ * once, before the first render, is what makes the server and the client agree.
59
+ *
60
+ * **What it does not own is reporting.** `setFormValue` stays with the
61
+ * component, because what an empty value means is the component's business: a
62
+ * text field submits its emptiness, since `""` is an answer and the field was
63
+ * on the form, while a group with nothing chosen submits nothing at all. One
64
+ * mixin cannot hold both without a flag, and a flag would be this rule written
65
+ * down twice.
66
+ *
67
+ * `Checkbox` deliberately does not take this. Its `value` is what it submits
68
+ * *when it is on* rather than what it holds, so it is configuration rather than
69
+ * state, and the state it does have is `checked`, which is
70
+ * `MixinControllerChecked`'s.
71
+ */
72
+ export declare const MixinControllerValue: <T extends Constructor<HelixFormElement>>(Base: T) => Constructor<MixinControllerValueInterface> & T;
73
+ export {};
@@ -0,0 +1,173 @@
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 { property } from 'lit/decorators.js';
8
+ import { HelixBeforeChangeEvent } from '../../events/events.js';
9
+ import { requestChange } from '../../utils/request-change/request-change.js';
10
+ /**
11
+ * Grafts `value`, `defaultValue`, and the two callbacks that answer a form.
12
+ *
13
+ * Every control with a value had written this, and by the third it was three
14
+ * copies of the same four members and the same seeding rule: `Input`, then
15
+ * `Toggle.Group`, then `Radio.Group`. The copies had not diverged, which is the
16
+ * argument for the mixin rather than against it, because the next one would
17
+ * have.
18
+ *
19
+ * **Seeded in `willUpdate`, not `connectedCallback`.** SSR never calls the
20
+ * latter, so a control with a `default-value` would serve empty and fill itself
21
+ * at hydration, which is a flash for a reader and a mismatch for React. Seeding
22
+ * once, before the first render, is what makes the server and the client agree.
23
+ *
24
+ * **What it does not own is reporting.** `setFormValue` stays with the
25
+ * component, because what an empty value means is the component's business: a
26
+ * text field submits its emptiness, since `""` is an answer and the field was
27
+ * on the form, while a group with nothing chosen submits nothing at all. One
28
+ * mixin cannot hold both without a flag, and a flag would be this rule written
29
+ * down twice.
30
+ *
31
+ * `Checkbox` deliberately does not take this. Its `value` is what it submits
32
+ * *when it is on* rather than what it holds, so it is configuration rather than
33
+ * state, and the state it does have is `checked`, which is
34
+ * `MixinControllerChecked`'s.
35
+ */
36
+ export const MixinControllerValue = (Base) => {
37
+ // JSDoc is duplicated from the interface deliberately. The interface is what
38
+ // TypeScript consumers see; this class is what the manifest analyzer reads
39
+ // when resolving inherited members into dependent packages.
40
+ class ValueElement extends Base {
41
+ constructor() {
42
+ super(...arguments);
43
+ /**
44
+ * The value the markup declared, which is where a reset returns to.
45
+ */
46
+ this.defaultValue = '';
47
+ /**
48
+ * The state, whoever set it, and `undefined` until somebody has.
49
+ *
50
+ * One field and not two. Writing always applies, the way a native control
51
+ * does when a script assigns to it.
52
+ */
53
+ this.valueState = undefined;
54
+ /**
55
+ * Whether a reader edits this continuously, which decides the native name.
56
+ *
57
+ * **It used to pick between `helix-input` and `helix-change`**, which was
58
+ * one mechanism under two names and said *phase* where it meant
59
+ * *granularity*. The request event is `helix-before-change` whatever the
60
+ * answer; this decides which native event announces the result, `input`
61
+ * while a reader is editing against `change` when they are done, which is
62
+ * the distinction the platform already draws. See 294.
63
+ *
64
+ * Discrete by default: only a control the reader edits continuously says
65
+ * otherwise.
66
+ */
67
+ this.announcesInput = false;
68
+ }
69
+ /**
70
+ * What the control holds now, and what its form submits.
71
+ *
72
+ * One property, not two. Reading gives the real value whichever mode the
73
+ * control is in, so `input.value` answers the question anyone asking it
74
+ * meant. Writing hands ownership to the consumer, which is what
75
+ * `<helix-input value="x">` says too, and matches what a controlled input
76
+ * means in React: the value is theirs and the control waits to be told.
77
+ * `default-value` is how markup seeds one it does not want to own.
78
+ *
79
+ * The same shape as the other three mixins, which is the point of them
80
+ * being four of a kind rather than four of three kinds.
81
+ *
82
+ * helix-input
83
+ */
84
+ get value() {
85
+ return this.valueState ?? this.defaultValue;
86
+ }
87
+ set value(next) {
88
+ const previous = this.value;
89
+ this.valueState = next;
90
+ this.requestUpdate('value', previous);
91
+ }
92
+ /**
93
+ * Announces the applied value under the platform's own name.
94
+ *
95
+ * **After `apply`, never before**, which is the whole reason this is
96
+ * separate from the request event: a native handler reads
97
+ * `event.target.value`, and it has to be the value that was just applied.
98
+ *
99
+ * **Both, for a discrete control, because that is what the platform does.**
100
+ * A native `<select>` sends `input` and then `change` from one choice: the
101
+ * value moved, and the reader is also done. A continuous control announces
102
+ * `input` here and relays `change` from its inner control when the reader
103
+ * is done, so the pair keeps the platform's timings rather than reporting a
104
+ * commit on every keystroke.
105
+ */
106
+ announceNatively() {
107
+ this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
108
+ if (!this.announcesInput) {
109
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: false }));
110
+ }
111
+ }
112
+ /**
113
+ * Requests a new value, and reports whether the request stood.
114
+ *
115
+ * Announces before it acts, so `preventDefault()` on the `helix-before-change` is
116
+ * a refusal and nothing moves. Nobody refusing means the control updates
117
+ * itself, whether or not a consumer declared `value`: a declared property
118
+ * is a seed and a correction, the way `<input value>` is, and a consumer
119
+ * who wants the change for themselves refuses it and assigns their own.
120
+ * See 294.
121
+ */
122
+ requestValue(next) {
123
+ if (this.value === next) {
124
+ return true;
125
+ }
126
+ const applied = requestChange({
127
+ apply: (value) => {
128
+ const previous = this.value;
129
+ this.valueState = value;
130
+ this.requestUpdate('value', previous);
131
+ },
132
+ event: (value) => new HelixBeforeChangeEvent({ value }),
133
+ host: this,
134
+ next,
135
+ });
136
+ if (applied) {
137
+ this.announceNatively();
138
+ }
139
+ return applied;
140
+ }
141
+ /**
142
+ * Returns the control to the value its markup declared.
143
+ *
144
+ * Both halves are dropped, the consumer's and the control's, because a
145
+ * reset means the form is as it was served. Clearing only the internal one
146
+ * would leave a control that had been written to stuck at that value.
147
+ */
148
+ formResetCallback() {
149
+ const previous = this.value;
150
+ this.valueState = undefined;
151
+ this.requestUpdate('value', previous);
152
+ }
153
+ /**
154
+ * Restores the value when the browser brings a form back.
155
+ *
156
+ * Falling back to `defaultValue` rather than to empty, because a restore
157
+ * with no state is a form the browser has nothing recorded for, which is
158
+ * the same situation as a fresh one.
159
+ */
160
+ formStateRestoreCallback(state) {
161
+ const previous = this.value;
162
+ this.valueState = state ?? undefined;
163
+ this.requestUpdate('value', previous);
164
+ }
165
+ }
166
+ __decorate([
167
+ property({ attribute: 'default-value' })
168
+ ], ValueElement.prototype, "defaultValue", void 0);
169
+ __decorate([
170
+ property()
171
+ ], ValueElement.prototype, "value", null);
172
+ return ValueElement;
173
+ };
@@ -0,0 +1,41 @@
1
+ import type { LitElement } from 'lit';
2
+ import { HelixDismissEvent } from '../../events/events.js';
3
+ type Constructor<T> = new (...args: any[]) => T;
4
+ /**
5
+ * The API a dismissible surface has, as TypeScript sees it.
6
+ */
7
+ export declare class DismissibleElement {
8
+ /** Whether it offers a control to take it away. */
9
+ dismissible: boolean;
10
+ /** Accessible name for that control. */
11
+ dismissLabel: string;
12
+ /** Announces that it should go, and reports whether the request stood. */
13
+ dismiss(): boolean;
14
+ /** The event, built but not dispatched, for a caller that owns the timing. */
15
+ protected dismissEvent(): HelixDismissEvent;
16
+ }
17
+ /**
18
+ * Grafts `dismissible`, its label, and the event that says so.
19
+ *
20
+ * Three surfaces had written this: `Announcement`, `Tag` and `Toast.Item`, each
21
+ * with the same two properties, the same event, and the same expression for the
22
+ * label. They had not diverged, which is the argument for extracting rather than
23
+ * against it, because the fourth would have.
24
+ *
25
+ * **The label reads the default slot, not the whole element**, and that is a bug
26
+ * fix rather than a move. Every copy said `this.textContent`, which is every
27
+ * light-DOM descendant including the ones assigned elsewhere. On `Announcement`
28
+ * and `Tag` that is the same thing, because they have one slot. On `Toast.Item`
29
+ * it swept in the actions: a message reading "Could not reach the server" with a
30
+ * `Retry` button announced itself as "Could not reach the server Retry", and the
31
+ * consumer removing it from their list got that string to match on.
32
+ *
33
+ * **`dismissEvent()` is separate from `dismiss()` for the one that needs it.**
34
+ * `Announcement` and `Tag` dispatch and are done: they own no state, so a
35
+ * refusal is the whole of the consumer's answer. `Toast.Item` holds an `open`
36
+ * it may or may not own, so its event has to go through the controlled-change
37
+ * path with an `apply` beside it. Giving it the event rather than the method
38
+ * lets it keep the contract and skip the default.
39
+ */
40
+ export declare const MixinDismissible: <T extends Constructor<LitElement>>(Base: T) => Constructor<DismissibleElement> & T;
41
+ export {};