@fusion.dev/helix-headless 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +48376 -26638
- package/dist/announcement/root/announcement-root.d.ts +19 -5
- package/dist/area-chart/index.d.ts +1 -0
- package/dist/area-chart/index.js +1 -0
- package/dist/area-chart/root/area-chart-root.d.ts +182 -0
- package/dist/area-chart/root/area-chart-root.js +205 -0
- package/dist/carousel/root/carousel-root.d.ts +1 -0
- package/dist/carousel/root/carousel-root.js +18 -2
- package/dist/chat/index.d.ts +1 -1
- package/dist/chat/message/chat-message.d.ts +7 -0
- package/dist/chat/message/chat-message.js +2 -1
- package/dist/chat/root/chat-root.d.ts +195 -4
- package/dist/chat/root/chat-root.js +316 -23
- package/dist/checkbox-group/root/checkbox-group-root.d.ts +2 -0
- package/dist/checkbox-group/root/checkbox-group-root.js +12 -2
- package/dist/code-block/index.d.ts +1 -0
- package/dist/code-block/index.js +1 -0
- package/dist/code-block/patch.d.ts +62 -0
- package/dist/code-block/patch.js +93 -0
- package/dist/code-block/root/code-block-root.d.ts +82 -17
- package/dist/code-block/root/code-block-root.js +83 -6
- package/dist/color-picker/root/color-picker-root.d.ts +4 -3
- package/dist/color-picker/root/color-picker-root.js +4 -1
- package/dist/data-table/root/data-table-root.d.ts +195 -23
- package/dist/data-table/root/data-table-root.js +308 -27
- package/dist/description-list/index.d.ts +2 -0
- package/dist/description-list/index.js +2 -0
- package/dist/description-list/item/description-list-item.d.ts +39 -0
- package/dist/description-list/item/description-list-item.js +26 -0
- package/dist/description-list/root/description-list-root.d.ts +56 -0
- package/dist/description-list/root/description-list-root.js +21 -0
- package/dist/element-events.generated.js +27 -6
- package/dist/filter-input/chip/filter-chip.d.ts +114 -0
- package/dist/filter-input/chip/filter-chip.js +61 -0
- package/dist/filter-input/field/filter-field.d.ts +128 -0
- package/dist/filter-input/field/filter-field.js +70 -0
- package/dist/filter-input/grammar.d.ts +96 -0
- package/dist/filter-input/grammar.js +133 -0
- package/dist/filter-input/index.d.ts +5 -0
- package/dist/filter-input/index.js +5 -0
- package/dist/filter-input/menu/filter-menu.d.ts +153 -0
- package/dist/filter-input/menu/filter-menu.js +164 -0
- package/dist/filter-input/operator/filter-operator.d.ts +105 -0
- package/dist/filter-input/operator/filter-operator.js +39 -0
- package/dist/filter-input/root/filter-input-root.d.ts +118 -9
- package/dist/filter-input/root/filter-input-root.js +96 -2
- package/dist/format-date/root/format-date-root.js +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/line-chart/root/line-chart-root.d.ts +9 -2
- package/dist/listbox/root/listbox-root.d.ts +8 -7
- package/dist/listbox/root/listbox-root.js +7 -2
- package/dist/menu/item/menu-item.d.ts +31 -6
- package/dist/menu/item/menu-item.js +18 -5
- package/dist/menu/root/menu-root.js +7 -2
- package/dist/notifications/group/notification-group.d.ts +129 -0
- package/dist/notifications/group/notification-group.js +106 -0
- package/dist/notifications/index.d.ts +1 -0
- package/dist/notifications/index.js +1 -0
- package/dist/notifications/notification/notification.d.ts +39 -13
- package/dist/notifications/notification/notification.js +11 -8
- package/dist/notifications/root/notifications-root.d.ts +57 -1
- package/dist/notifications/root/notifications-root.js +35 -4
- package/dist/number-input/root/number-input-root.d.ts +21 -1
- package/dist/number-input/root/number-input-root.js +18 -5
- package/dist/object-inspector/index.d.ts +1 -0
- package/dist/object-inspector/index.js +1 -0
- package/dist/object-inspector/root/object-inspector-root.d.ts +183 -0
- package/dist/object-inspector/root/object-inspector-root.js +348 -0
- package/dist/outline/root/outline-root.d.ts +16 -1
- package/dist/outline/root/outline-root.js +27 -9
- package/dist/radio/group/radio-group.d.ts +1 -0
- package/dist/scatter-chart/index.d.ts +1 -0
- package/dist/scatter-chart/index.js +1 -0
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +144 -0
- package/dist/scatter-chart/root/scatter-chart-root.js +205 -0
- package/dist/skip-link/index.d.ts +1 -0
- package/dist/skip-link/index.js +1 -0
- package/dist/skip-link/root/skip-link-root.d.ts +81 -0
- package/dist/skip-link/root/skip-link-root.js +58 -0
- package/dist/speed-dial/root/speed-dial-root.d.ts +1 -0
- package/dist/speed-dial/root/speed-dial-root.js +6 -2
- package/dist/steps/root/steps-root.d.ts +1 -0
- package/dist/table/column/table-column.d.ts +79 -0
- package/dist/table/column/table-column.js +125 -5
- package/dist/table/root/table-root.d.ts +2 -0
- package/dist/table/root/table-root.js +12 -3
- package/dist/time-picker/root/time-picker-root.d.ts +9 -0
- package/dist/time-picker/root/time-picker-root.js +5 -0
- package/dist/toast/root/toast-root.d.ts +2 -0
- package/dist/tooltip/root/tooltip-root.js +1 -1
- package/dist/tour/root/tour-root.d.ts +1 -0
- package/dist/tree/item/tree-item.d.ts +7 -4
- package/dist/tree/item/tree-item.js +4 -1
- package/dist/video/root/video-root.d.ts +23 -1
- package/dist/video/root/video-root.js +42 -22
- package/dist/virtual-list/root/virtual-list-root.d.ts +10 -6
- package/dist/virtual-list/root/virtual-list-root.js +10 -4
- package/package.json +5 -5
- package/types/jsx.d.ts +4535 -346
- package/types/svelte.d.ts +11 -0
- package/types/vue.d.ts +737 -39
|
@@ -27,6 +27,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
27
27
|
this.step = 1;
|
|
28
28
|
this.scrubFrom = 0;
|
|
29
29
|
this.scrubValue = 0;
|
|
30
|
+
this.scrubStart = '';
|
|
30
31
|
this.handleScrubDown = (event) => {
|
|
31
32
|
if (this.isDisabled || this.readOnly) {
|
|
32
33
|
return;
|
|
@@ -35,6 +36,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
35
36
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
36
37
|
this.scrubFrom = event.clientX;
|
|
37
38
|
this.scrubValue = this.numeric ?? this.min ?? 0;
|
|
39
|
+
this.scrubStart = this.value;
|
|
38
40
|
event.preventDefault();
|
|
39
41
|
};
|
|
40
42
|
this.handleScrubMove = (event) => {
|
|
@@ -58,8 +60,12 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
58
60
|
};
|
|
59
61
|
this.handleScrubEnd = (event) => {
|
|
60
62
|
const grip = event.currentTarget;
|
|
61
|
-
if (grip.hasPointerCapture(event.pointerId)) {
|
|
62
|
-
|
|
63
|
+
if (!grip.hasPointerCapture(event.pointerId)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
grip.releasePointerCapture(event.pointerId);
|
|
67
|
+
if (this.value !== this.scrubStart) {
|
|
68
|
+
this.dispatchEvent(new Event('change', { bubbles: true, composed: false }));
|
|
63
69
|
}
|
|
64
70
|
};
|
|
65
71
|
this.handleInput = (event) => {
|
|
@@ -98,13 +104,13 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
98
104
|
this.min !== undefined &&
|
|
99
105
|
!this.isDisabled &&
|
|
100
106
|
!this.readOnly) {
|
|
101
|
-
this.
|
|
107
|
+
this.land(String(this.min));
|
|
102
108
|
}
|
|
103
109
|
else if (event.key === 'End' &&
|
|
104
110
|
this.max !== undefined &&
|
|
105
111
|
!this.isDisabled &&
|
|
106
112
|
!this.readOnly) {
|
|
107
|
-
this.
|
|
113
|
+
this.land(String(this.max));
|
|
108
114
|
}
|
|
109
115
|
else {
|
|
110
116
|
return;
|
|
@@ -142,11 +148,18 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
142
148
|
const step = Math.abs(this.step) || 1;
|
|
143
149
|
const from = this.numeric ?? this.min ?? 0;
|
|
144
150
|
const next = this.clamp(this.numeric === null ? from : from + steps * step);
|
|
145
|
-
this.
|
|
151
|
+
this.land(String(next));
|
|
152
|
+
}
|
|
153
|
+
land(next) {
|
|
154
|
+
const before = this.value;
|
|
155
|
+
const applied = this.requestValue(next);
|
|
146
156
|
const control = this.control;
|
|
147
157
|
if (control) {
|
|
148
158
|
restoreValue(control, this.value);
|
|
149
159
|
}
|
|
160
|
+
if (applied && this.value !== before) {
|
|
161
|
+
this.dispatchEvent(new Event('change', { bubbles: true, composed: false }));
|
|
162
|
+
}
|
|
150
163
|
}
|
|
151
164
|
stepUp(large = false) {
|
|
152
165
|
this.nudge(large ? (this.largeStep ?? 10) : 1);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './root/object-inspector-root.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './root/object-inspector-root.js';
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
import '../../copy-button/index.js';
|
|
4
|
+
/** One visible line of the tree: a value, where it sits, and whether it is open. */
|
|
5
|
+
interface Row {
|
|
6
|
+
branch: boolean;
|
|
7
|
+
depth: number;
|
|
8
|
+
/** The property name or array index this value is under; absent for the root. */
|
|
9
|
+
key?: string;
|
|
10
|
+
open: boolean;
|
|
11
|
+
path: string;
|
|
12
|
+
position: number;
|
|
13
|
+
siblings: number;
|
|
14
|
+
value: unknown;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* @summary A value read as a tree: collapse what you do not need, count what
|
|
18
|
+
* you collapsed, copy the part you do.
|
|
19
|
+
*
|
|
20
|
+
* @description
|
|
21
|
+
* What a developer-facing product shows constantly is a structured value: an
|
|
22
|
+
* API response, a config, a row's raw record. As text in a code block, a reader
|
|
23
|
+
* scrolls a wall of JSON and cannot put away the branch they do not care about;
|
|
24
|
+
* as a Tree, every node is a label and a string reads the same as a number. This
|
|
25
|
+
* reads the value and draws each entry with its type visible.
|
|
26
|
+
*
|
|
27
|
+
* **It reads and never edits.** An inspector that edits is a different
|
|
28
|
+
* component, with validation, undo and a model of what changed, and the reading
|
|
29
|
+
* one is what is wanted.
|
|
30
|
+
*
|
|
31
|
+
* **The data is text in the page or a property.** `for` names a
|
|
32
|
+
* `<script type="application/json">` by id, the channel `VirtualList` and the
|
|
33
|
+
* charts already read: it survives a server, which has attributes and no
|
|
34
|
+
* children, and React's hydration, which skips object properties. `value` is
|
|
35
|
+
* there for a page that already holds the object, and wins when both are set.
|
|
36
|
+
*
|
|
37
|
+
* **One tab stop, and the rows are reached with the arrow keys**, the tree
|
|
38
|
+
* pattern: Up and Down move, Right opens a branch or enters it, Left closes it or
|
|
39
|
+
* goes to its parent, Home and End go to the ends. The tree keeps focus and
|
|
40
|
+
* points at the active row with `aria-activedescendant`, which is what lets a
|
|
41
|
+
* windowed tree drop rows from the page without dropping focus with them. Each
|
|
42
|
+
* row carries its real level, position and set size, so a reader on a windowed
|
|
43
|
+
* row hears where it is in the value rather than in the slice drawn.
|
|
44
|
+
*
|
|
45
|
+
* **Copying takes the active row**, as JSON for a branch and as itself for a
|
|
46
|
+
* string, from the copy button or from Command or Control with C while the tree
|
|
47
|
+
* has focus and nothing is selected. The button sits beside the tree rather than in each row, where a
|
|
48
|
+
* button would be an interactive control inside a tree item, which assistive
|
|
49
|
+
* technology cannot reach reliably.
|
|
50
|
+
*
|
|
51
|
+
* **`windowed` draws only the rows in view**, for a value of thousands of
|
|
52
|
+
* entries, through the same controller `VirtualList` and `DataTable` use. The
|
|
53
|
+
* box has to have a height for that to mean anything, which the styled tier
|
|
54
|
+
* gives it and a headless consumer has to.
|
|
55
|
+
*
|
|
56
|
+
* @since 0.1.0
|
|
57
|
+
* @status experimental
|
|
58
|
+
* @category data
|
|
59
|
+
* @aliases JSON viewer, Object viewer, Inspector, Data explorer
|
|
60
|
+
* @documentation https://helix.fusion.dev/components/object-inspector
|
|
61
|
+
*
|
|
62
|
+
* @event {HelixCopyEvent} helix-copy - A value was put on the clipboard, from the copy button or the keyboard.
|
|
63
|
+
*
|
|
64
|
+
* @csspart after - The room the rows after the window would take, when windowed.
|
|
65
|
+
* @csspart before - The room the rows before the window would take, when windowed.
|
|
66
|
+
* @csspart boolean - A `true` or `false`.
|
|
67
|
+
* @csspart copy - The copy button, which copies the active row.
|
|
68
|
+
* @csspart copy-button - The inner button.
|
|
69
|
+
* @csspart copy-mark - The clipboard glyph, and the confirmation that replaces it.
|
|
70
|
+
* @csspart copy-tooltip - The bubble, forwarded from the tooltip inside.
|
|
71
|
+
* @csspart copy-tooltip-arrow - The point, resting against the trigger.
|
|
72
|
+
* @csspart count - How many entries a branch holds.
|
|
73
|
+
* @csspart frame - The tree and the copy button beside it.
|
|
74
|
+
* @csspart header - The bar carrying the label, where there is one.
|
|
75
|
+
* @csspart key - A property name or an array index.
|
|
76
|
+
* @csspart label - The label.
|
|
77
|
+
* @csspart null - A `null`.
|
|
78
|
+
* @csspart number - A number.
|
|
79
|
+
* @csspart row - One entry, a tree item.
|
|
80
|
+
* @csspart string - A string, in quotes.
|
|
81
|
+
* @csspart summary - The brackets standing for a branch's contents.
|
|
82
|
+
* @csspart toggle - The caret on a branch: down when open, across when closed.
|
|
83
|
+
* @csspart tree - The tree, the one thing focus lands on.
|
|
84
|
+
*
|
|
85
|
+
* @cssproperty --object-inspector-level - How deep a row is, from 0 at the root, for indenting it.
|
|
86
|
+
*/
|
|
87
|
+
export declare class HelixHeadlessObjectInspector extends HelixElement {
|
|
88
|
+
/**
|
|
89
|
+
* How many levels start open.
|
|
90
|
+
*
|
|
91
|
+
* One by default: the root is open and everything under it closed, so a
|
|
92
|
+
* reader sees the shape of the value before its contents. `0` starts with the
|
|
93
|
+
* root closed; a large number opens everything, which on a big value is the
|
|
94
|
+
* wall of text this exists to avoid.
|
|
95
|
+
*/
|
|
96
|
+
defaultDepth: number;
|
|
97
|
+
/**
|
|
98
|
+
* The id of a `<script type="application/json">` holding the value.
|
|
99
|
+
*
|
|
100
|
+
* Read once the document exists, and again whenever this changes. Text that
|
|
101
|
+
* does not parse leaves the inspector empty rather than throwing, since a
|
|
102
|
+
* page's markup should not take the page down over a comma.
|
|
103
|
+
*/
|
|
104
|
+
for: string;
|
|
105
|
+
/**
|
|
106
|
+
* Accessible name for the tree, and the text of the header where there is one.
|
|
107
|
+
*/
|
|
108
|
+
label: string;
|
|
109
|
+
/**
|
|
110
|
+
* Whether to leave the copy button off.
|
|
111
|
+
*
|
|
112
|
+
* Negative for the reason Code Block's is: a boolean attribute defaulting to
|
|
113
|
+
* true could never be turned off from markup.
|
|
114
|
+
*/
|
|
115
|
+
noCopy: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* The value itself, for a page that already holds it.
|
|
118
|
+
*
|
|
119
|
+
* Takes precedence over `for`. Anything JSON can say is drawn; anything else
|
|
120
|
+
* is shown as its string.
|
|
121
|
+
*/
|
|
122
|
+
value: unknown;
|
|
123
|
+
/**
|
|
124
|
+
* Whether to draw only the rows in view.
|
|
125
|
+
*
|
|
126
|
+
* For a value of thousands of entries. Reflected, because the box it scrolls
|
|
127
|
+
* needs a height, which is a stylesheet rule reading this attribute.
|
|
128
|
+
*/
|
|
129
|
+
windowed: boolean;
|
|
130
|
+
/** The value read from `for`, when there is no `value`. */
|
|
131
|
+
private read;
|
|
132
|
+
/** The `for` that `read` came from, so it is read again only when that changes. */
|
|
133
|
+
private loadedFor;
|
|
134
|
+
/** Which branches are open, by path. */
|
|
135
|
+
private opened;
|
|
136
|
+
/** The row the arrow keys are on, by its place among the visible rows. */
|
|
137
|
+
private active;
|
|
138
|
+
/** Set by a keyboard move, so the next render brings the active row into view. */
|
|
139
|
+
private revealing;
|
|
140
|
+
private readonly virtualize;
|
|
141
|
+
private get root();
|
|
142
|
+
/** Every visible row, in order, worked out once per change rather than per read. */
|
|
143
|
+
private rows;
|
|
144
|
+
private readonly copier;
|
|
145
|
+
/** Every visible row, in order, for the tree as it stands. */
|
|
146
|
+
private flatten;
|
|
147
|
+
/** Opens every branch shallower than `defaultDepth`. */
|
|
148
|
+
private seed;
|
|
149
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
150
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
151
|
+
protected updated(changed: PropertyValues): void;
|
|
152
|
+
private load;
|
|
153
|
+
/**
|
|
154
|
+
* Brings the active row into the box.
|
|
155
|
+
*
|
|
156
|
+
* **A windowed tree may not have drawn it, and a jump straight to where it
|
|
157
|
+
* should be does not land.** The controller places rows it has not measured at
|
|
158
|
+
* its estimate, so an offset computed from a real row's height points at a
|
|
159
|
+
* different row, and asking again from there asks the same wrong question: the
|
|
160
|
+
* first version of this looped until the page stopped. So each attempt steps
|
|
161
|
+
* from the rows that *are* drawn toward the one wanted, by how many rows lie
|
|
162
|
+
* between, which closes the gap every time whatever the estimate is, and gives
|
|
163
|
+
* up after a few rather than trusting that it always will.
|
|
164
|
+
*/
|
|
165
|
+
private reveal;
|
|
166
|
+
/** What copying takes: a branch as indented JSON, a string as itself. */
|
|
167
|
+
private copyable;
|
|
168
|
+
private toggle;
|
|
169
|
+
private move;
|
|
170
|
+
private readonly handleKeydown;
|
|
171
|
+
private handleRowClick;
|
|
172
|
+
private renderValue;
|
|
173
|
+
private renderRow;
|
|
174
|
+
/** The copy button, behind the seam Code Block's uses, so each tier renders its own. */
|
|
175
|
+
protected renderCopyButton(row: Row | undefined): TemplateResult;
|
|
176
|
+
protected render(): TemplateResult;
|
|
177
|
+
}
|
|
178
|
+
declare global {
|
|
179
|
+
interface HTMLElementTagNameMap {
|
|
180
|
+
'helix-headless-object-inspector': HelixHeadlessObjectInspector;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export {};
|
|
@@ -0,0 +1,348 @@
|
|
|
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 { CopyController, customElement, HelixElement, VirtualizeController, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { caretDown, caretRight } from '@fusion.dev/helix-icons';
|
|
9
|
+
import { html, nothing } from 'lit';
|
|
10
|
+
import { property, state } from 'lit/decorators.js';
|
|
11
|
+
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
|
+
import { html as staticHtml } from 'lit/static-html.js';
|
|
13
|
+
import '../../copy-button/index.js';
|
|
14
|
+
const branchy = (value) => value !== null && typeof value === 'object';
|
|
15
|
+
const entries = (value) => Array.isArray(value) ? value.map((item, at) => [String(at), item]) : Object.entries(value);
|
|
16
|
+
const child = (path, key) => `${path}/${key.replaceAll('~', '~0').replaceAll('/', '~1')}`;
|
|
17
|
+
let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends HelixElement {
|
|
18
|
+
constructor() {
|
|
19
|
+
super(...arguments);
|
|
20
|
+
this.defaultDepth = 1;
|
|
21
|
+
this.for = '';
|
|
22
|
+
this.label = '';
|
|
23
|
+
this.noCopy = false;
|
|
24
|
+
this.value = undefined;
|
|
25
|
+
this.windowed = false;
|
|
26
|
+
this.read = undefined;
|
|
27
|
+
this.opened = new Set();
|
|
28
|
+
this.active = 0;
|
|
29
|
+
this.revealing = false;
|
|
30
|
+
this.virtualize = new VirtualizeController(this, {
|
|
31
|
+
container: () => this.windowed ? this.renderRoot?.querySelector('[part="tree"]') : null,
|
|
32
|
+
count: () => this.rows.length,
|
|
33
|
+
estimate: () => 24,
|
|
34
|
+
measured: () => this.windowed,
|
|
35
|
+
rows: () => [...(this.renderRoot?.querySelectorAll('[part="row"]') ?? [])].map((element) => ({ element, index: Number(element.dataset.index) })),
|
|
36
|
+
});
|
|
37
|
+
this.rows = [];
|
|
38
|
+
this.copier = new CopyController(this);
|
|
39
|
+
this.handleKeydown = (event) => {
|
|
40
|
+
const rows = this.rows;
|
|
41
|
+
const row = rows[this.active];
|
|
42
|
+
if (!row) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'c') {
|
|
46
|
+
if (globalThis.getSelection?.()?.toString()) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
void this.copier.copy(this.copyable(row));
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
switch (event.key) {
|
|
54
|
+
case 'ArrowDown':
|
|
55
|
+
this.move(this.active + 1);
|
|
56
|
+
break;
|
|
57
|
+
case 'ArrowUp':
|
|
58
|
+
this.move(this.active - 1);
|
|
59
|
+
break;
|
|
60
|
+
case 'Home':
|
|
61
|
+
this.move(0);
|
|
62
|
+
break;
|
|
63
|
+
case 'End':
|
|
64
|
+
this.move(rows.length - 1);
|
|
65
|
+
break;
|
|
66
|
+
case 'ArrowRight':
|
|
67
|
+
if (row.branch && !row.open) {
|
|
68
|
+
this.toggle(row, true);
|
|
69
|
+
}
|
|
70
|
+
else if (row.open) {
|
|
71
|
+
this.move(this.active + 1);
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
case 'ArrowLeft':
|
|
75
|
+
if (row.open) {
|
|
76
|
+
this.toggle(row, false);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const parent = rows.findLastIndex((candidate, at) => at < this.active && candidate.depth === row.depth - 1);
|
|
80
|
+
if (parent >= 0) {
|
|
81
|
+
this.move(parent);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
case 'Enter':
|
|
86
|
+
case ' ':
|
|
87
|
+
if (row.branch) {
|
|
88
|
+
this.toggle(row);
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
default:
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
get root() {
|
|
98
|
+
return this.value === undefined ? this.read : this.value;
|
|
99
|
+
}
|
|
100
|
+
flatten() {
|
|
101
|
+
const rows = [];
|
|
102
|
+
if (this.root === undefined) {
|
|
103
|
+
return rows;
|
|
104
|
+
}
|
|
105
|
+
const visit = (value, path, depth, position, siblings, key) => {
|
|
106
|
+
const branch = branchy(value);
|
|
107
|
+
const open = branch && this.opened.has(path);
|
|
108
|
+
rows.push({
|
|
109
|
+
branch,
|
|
110
|
+
depth,
|
|
111
|
+
open,
|
|
112
|
+
path,
|
|
113
|
+
position,
|
|
114
|
+
siblings,
|
|
115
|
+
value,
|
|
116
|
+
...(key === undefined ? {} : { key }),
|
|
117
|
+
});
|
|
118
|
+
if (open) {
|
|
119
|
+
const children = entries(value);
|
|
120
|
+
children.forEach(([name, item], at) => {
|
|
121
|
+
visit(item, child(path, name), depth + 1, at + 1, children.length, name);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
visit(this.root, '', 0, 1, 1);
|
|
126
|
+
return rows;
|
|
127
|
+
}
|
|
128
|
+
seed() {
|
|
129
|
+
const opened = new Set();
|
|
130
|
+
const visit = (value, path, depth) => {
|
|
131
|
+
if (!branchy(value) || depth >= this.defaultDepth) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
opened.add(path);
|
|
135
|
+
for (const [name, item] of entries(value)) {
|
|
136
|
+
visit(item, child(path, name), depth + 1);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
visit(this.root, '', 0);
|
|
140
|
+
this.opened = opened;
|
|
141
|
+
this.active = 0;
|
|
142
|
+
}
|
|
143
|
+
willUpdate(changed) {
|
|
144
|
+
super.willUpdate(changed);
|
|
145
|
+
if (this.loadedFor !== this.for &&
|
|
146
|
+
typeof this.ownerDocument?.getElementById === 'function' &&
|
|
147
|
+
!this.hydrating) {
|
|
148
|
+
this.loadedFor = this.for;
|
|
149
|
+
this.load();
|
|
150
|
+
}
|
|
151
|
+
if (changed.has('value') || changed.has('read') || changed.has('defaultDepth')) {
|
|
152
|
+
this.seed();
|
|
153
|
+
}
|
|
154
|
+
if (changed.has('value') ||
|
|
155
|
+
changed.has('read') ||
|
|
156
|
+
changed.has('opened') ||
|
|
157
|
+
changed.has('defaultDepth')) {
|
|
158
|
+
this.rows = this.flatten();
|
|
159
|
+
this.active = Math.min(this.active, Math.max(0, this.rows.length - 1));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
firstUpdated(changed) {
|
|
163
|
+
super.firstUpdated(changed);
|
|
164
|
+
this.finishHydration();
|
|
165
|
+
}
|
|
166
|
+
updated(changed) {
|
|
167
|
+
super.updated?.(changed);
|
|
168
|
+
if (this.windowed) {
|
|
169
|
+
this.virtualize.measure();
|
|
170
|
+
}
|
|
171
|
+
if (this.revealing) {
|
|
172
|
+
this.revealing = false;
|
|
173
|
+
this.reveal();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
load() {
|
|
177
|
+
const text = this.for === '' ? '' : (this.ownerDocument.getElementById(this.for)?.textContent ?? '');
|
|
178
|
+
try {
|
|
179
|
+
this.read = text.trim() === '' ? undefined : JSON.parse(text);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
this.read = undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
reveal(attempts = 8) {
|
|
186
|
+
const tree = this.renderRoot?.querySelector('[part="tree"]');
|
|
187
|
+
const row = tree?.querySelector(`[data-index="${this.active}"]`);
|
|
188
|
+
if (row) {
|
|
189
|
+
row.scrollIntoView({ block: 'nearest' });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const drawn = [...(tree?.querySelectorAll('[part="row"]') ?? [])];
|
|
193
|
+
const first = drawn[0];
|
|
194
|
+
const last = drawn.at(-1);
|
|
195
|
+
if (!tree || !this.windowed || !first || !last || attempts === 0) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const height = first.offsetHeight || 24;
|
|
199
|
+
const from = Number(this.active > Number(last.dataset.index) ? last.dataset.index : first.dataset.index);
|
|
200
|
+
tree.scrollTop += (this.active - from) * height;
|
|
201
|
+
this.virtualize.measure();
|
|
202
|
+
void this.updateComplete.then(() => this.reveal(attempts - 1));
|
|
203
|
+
}
|
|
204
|
+
copyable(row) {
|
|
205
|
+
if (!row) {
|
|
206
|
+
return '';
|
|
207
|
+
}
|
|
208
|
+
if (typeof row.value === 'string') {
|
|
209
|
+
return row.value;
|
|
210
|
+
}
|
|
211
|
+
return JSON.stringify(row.value, null, 2) ?? String(row.value);
|
|
212
|
+
}
|
|
213
|
+
toggle(row, open = !row.open) {
|
|
214
|
+
const opened = new Set(this.opened);
|
|
215
|
+
if (open) {
|
|
216
|
+
opened.add(row.path);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
opened.delete(row.path);
|
|
220
|
+
}
|
|
221
|
+
this.opened = opened;
|
|
222
|
+
}
|
|
223
|
+
move(to) {
|
|
224
|
+
const last = this.rows.length - 1;
|
|
225
|
+
this.active = Math.max(0, Math.min(to, last));
|
|
226
|
+
this.revealing = true;
|
|
227
|
+
}
|
|
228
|
+
handleRowClick(index, row) {
|
|
229
|
+
this.active = index;
|
|
230
|
+
if (row.branch) {
|
|
231
|
+
this.toggle(row);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
renderValue(row) {
|
|
235
|
+
const { value } = row;
|
|
236
|
+
if (row.branch) {
|
|
237
|
+
const count = entries(value).length;
|
|
238
|
+
const array = Array.isArray(value);
|
|
239
|
+
const noun = array ? (count === 1 ? 'item' : 'items') : count === 1 ? 'key' : 'keys';
|
|
240
|
+
return html `<span part="summary">${array ? '[…]' : '{…}'}</span>
|
|
241
|
+
<span part="count">${`${count} ${noun}`}</span>`;
|
|
242
|
+
}
|
|
243
|
+
if (typeof value === 'string') {
|
|
244
|
+
return html `<span part="string">${JSON.stringify(value)}</span>`;
|
|
245
|
+
}
|
|
246
|
+
if (typeof value === 'number') {
|
|
247
|
+
return html `<span part="number">${String(value)}</span>`;
|
|
248
|
+
}
|
|
249
|
+
if (typeof value === 'boolean') {
|
|
250
|
+
return html `<span part="boolean">${String(value)}</span>`;
|
|
251
|
+
}
|
|
252
|
+
if (value === null) {
|
|
253
|
+
return html `<span part="null">null</span>`;
|
|
254
|
+
}
|
|
255
|
+
return html `<span part="string">${String(value)}</span>`;
|
|
256
|
+
}
|
|
257
|
+
renderRow(row, index) {
|
|
258
|
+
return html `<div
|
|
259
|
+
part="row"
|
|
260
|
+
role="treeitem"
|
|
261
|
+
id=${`row-${index}`}
|
|
262
|
+
data-index=${index}
|
|
263
|
+
?data-active=${index === this.active}
|
|
264
|
+
aria-level=${row.depth + 1}
|
|
265
|
+
aria-posinset=${row.position}
|
|
266
|
+
aria-setsize=${row.siblings}
|
|
267
|
+
aria-expanded=${row.branch ? String(row.open) : nothing}
|
|
268
|
+
style=${`--object-inspector-level: ${row.depth}`}
|
|
269
|
+
@click=${() => this.handleRowClick(index, row)}
|
|
270
|
+
>
|
|
271
|
+
${row.branch
|
|
272
|
+
? html `<svg
|
|
273
|
+
part="toggle"
|
|
274
|
+
fill="currentColor"
|
|
275
|
+
viewBox=${(row.open ? caretDown : caretRight).viewBox}
|
|
276
|
+
aria-hidden="true"
|
|
277
|
+
>
|
|
278
|
+
${unsafeSVG((row.open ? caretDown : caretRight).content)}
|
|
279
|
+
</svg>`
|
|
280
|
+
: nothing}${row.key === undefined ? nothing : html `<span part="key">${row.key}</span>: `}${this.renderValue(row)}
|
|
281
|
+
</div>`;
|
|
282
|
+
}
|
|
283
|
+
renderCopyButton(row) {
|
|
284
|
+
return staticHtml `<${this.tag('copy-button')}
|
|
285
|
+
part="copy"
|
|
286
|
+
exportparts="button: copy-button, mark: copy-mark, tooltip: copy-tooltip,
|
|
287
|
+
tooltip-arrow: copy-tooltip-arrow"
|
|
288
|
+
label=${row?.key === undefined ? 'Copy value' : `Copy ${row.key}`}
|
|
289
|
+
.value=${this.copyable(row)}
|
|
290
|
+
></${this.tag('copy-button')}>`;
|
|
291
|
+
}
|
|
292
|
+
render() {
|
|
293
|
+
const rows = this.rows;
|
|
294
|
+
const { after, before, first, last } = this.windowed
|
|
295
|
+
? this.virtualize.window
|
|
296
|
+
: { after: 0, before: 0, first: 0, last: rows.length };
|
|
297
|
+
const shown = rows.slice(first, last);
|
|
298
|
+
return html `${this.label ? html `<div part="header"><span part="label">${this.label}</span></div>` : nothing}
|
|
299
|
+
<div part="frame">
|
|
300
|
+
<div
|
|
301
|
+
part="tree"
|
|
302
|
+
role="tree"
|
|
303
|
+
tabindex="0"
|
|
304
|
+
?data-scroller=${this.windowed}
|
|
305
|
+
aria-label=${this.label || 'Value'}
|
|
306
|
+
aria-activedescendant=${rows.length > 0 ? `row-${this.active}` : nothing}
|
|
307
|
+
@keydown=${this.handleKeydown}
|
|
308
|
+
@scroll=${() => this.virtualize.measure()}
|
|
309
|
+
>
|
|
310
|
+
${this.windowed ? html `<div part="before" role="presentation" style="block-size: ${before}px"></div>` : nothing}
|
|
311
|
+
${shown.map((row, at) => this.renderRow(row, first + at))}
|
|
312
|
+
${this.windowed ? html `<div part="after" role="presentation" style="block-size: ${after}px"></div>` : nothing}
|
|
313
|
+
</div>
|
|
314
|
+
${this.noCopy ? nothing : this.renderCopyButton(rows[this.active])}
|
|
315
|
+
</div>`;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
__decorate([
|
|
319
|
+
property({ attribute: 'default-depth', type: Number })
|
|
320
|
+
], HelixHeadlessObjectInspector.prototype, "defaultDepth", void 0);
|
|
321
|
+
__decorate([
|
|
322
|
+
property()
|
|
323
|
+
], HelixHeadlessObjectInspector.prototype, "for", void 0);
|
|
324
|
+
__decorate([
|
|
325
|
+
property()
|
|
326
|
+
], HelixHeadlessObjectInspector.prototype, "label", void 0);
|
|
327
|
+
__decorate([
|
|
328
|
+
property({ attribute: 'no-copy', reflect: true, type: Boolean })
|
|
329
|
+
], HelixHeadlessObjectInspector.prototype, "noCopy", void 0);
|
|
330
|
+
__decorate([
|
|
331
|
+
property({ attribute: false })
|
|
332
|
+
], HelixHeadlessObjectInspector.prototype, "value", void 0);
|
|
333
|
+
__decorate([
|
|
334
|
+
property({ reflect: true, type: Boolean })
|
|
335
|
+
], HelixHeadlessObjectInspector.prototype, "windowed", void 0);
|
|
336
|
+
__decorate([
|
|
337
|
+
state()
|
|
338
|
+
], HelixHeadlessObjectInspector.prototype, "read", void 0);
|
|
339
|
+
__decorate([
|
|
340
|
+
state()
|
|
341
|
+
], HelixHeadlessObjectInspector.prototype, "opened", void 0);
|
|
342
|
+
__decorate([
|
|
343
|
+
state()
|
|
344
|
+
], HelixHeadlessObjectInspector.prototype, "active", void 0);
|
|
345
|
+
HelixHeadlessObjectInspector = __decorate([
|
|
346
|
+
customElement('helix-headless-object-inspector')
|
|
347
|
+
], HelixHeadlessObjectInspector);
|
|
348
|
+
export { HelixHeadlessObjectInspector };
|
|
@@ -58,7 +58,8 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
58
58
|
*/
|
|
59
59
|
label: string;
|
|
60
60
|
/**
|
|
61
|
-
* How far below the top of
|
|
61
|
+
* How far below the top of what scrolls a heading counts as reached, in
|
|
62
|
+
* pixels: the window, or the panel the sections scroll in.
|
|
62
63
|
*
|
|
63
64
|
* The reading line. A page with a sticky masthead sets this to its height, or
|
|
64
65
|
* a little more: without it the current section changes as a heading slides
|
|
@@ -119,6 +120,20 @@ export declare class HelixHeadlessOutline extends HelixElement {
|
|
|
119
120
|
* measured against.
|
|
120
121
|
*/
|
|
121
122
|
private get atEnd();
|
|
123
|
+
/** The element the page itself scrolls with, which `atEnd` explains. */
|
|
124
|
+
private get page();
|
|
125
|
+
/**
|
|
126
|
+
* What the sections scroll in: the nearest ancestor of the first one that
|
|
127
|
+
* scrolls, or the page when none does.
|
|
128
|
+
*
|
|
129
|
+
* **The window is not always what moves.** A contents rail beside a panel
|
|
130
|
+
* with its own scrollbar, which is a docs layout and every sandbox preview,
|
|
131
|
+
* measured the window. The window never scrolled, so it was always at its end
|
|
132
|
+
* and the last section was always current: the events example named `api`
|
|
133
|
+
* at rest with `install` on screen, a wheel over the panel changed nothing,
|
|
134
|
+
* and a press on `install` marked it for a moment and snapped back.
|
|
135
|
+
*/
|
|
136
|
+
private get scroller();
|
|
122
137
|
/** The frame a mark is already scheduled for, so a scroll asks for one. */
|
|
123
138
|
private frame;
|
|
124
139
|
/** Where a click said to go, while the page is still on its way there. */
|