@fusion.dev/helix-headless 0.160.0 → 0.161.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 +1729 -50
- package/dist/canvas/root/canvas-root.d.ts +59 -2
- package/dist/canvas/root/canvas-root.js +123 -8
- package/dist/chat/message/chat-message.d.ts +8 -0
- package/dist/chat/message/chat-message.js +11 -0
- package/dist/chat/root/chat-root.d.ts +47 -1
- package/dist/chat/root/chat-root.js +130 -4
- package/dist/code-block/lines.d.ts +12 -0
- package/dist/code-block/lines.js +41 -0
- package/dist/code-block/root/code-block-root.d.ts +88 -5
- package/dist/code-block/root/code-block-root.js +154 -10
- package/dist/element-events.generated.js +4 -0
- package/dist/splitter/root/splitter-root.d.ts +15 -0
- package/dist/splitter/root/splitter-root.js +16 -2
- package/dist/table/grid-controller.d.ts +7 -1
- package/dist/table/grid-controller.js +9 -1
- package/dist/table/root/table-root.d.ts +122 -0
- package/dist/table/root/table-root.js +293 -17
- package/dist/table/row/table-row.d.ts +41 -0
- package/dist/table/row/table-row.js +56 -2
- package/dist/timeline/item/timeline-item.d.ts +47 -10
- package/dist/timeline/item/timeline-item.js +36 -4
- package/dist/video-playlist/item/video-playlist-item.d.ts +11 -0
- package/dist/video-playlist/item/video-playlist-item.js +5 -0
- package/dist/video-playlist/menu/video-playlist-menu.d.ts +6 -0
- package/dist/video-playlist/menu/video-playlist-menu.js +11 -0
- package/dist/video-playlist/root/video-playlist-root.d.ts +59 -5
- package/dist/video-playlist/root/video-playlist-root.js +82 -10
- package/dist/virtual-list/fill.d.ts +6 -1
- package/dist/virtual-list/fill.js +1 -1
- package/dist/virtual-list/keys.d.ts +8 -0
- package/dist/virtual-list/keys.js +20 -0
- package/dist/virtual-list/root/virtual-list-root.d.ts +98 -13
- package/dist/virtual-list/root/virtual-list-root.js +195 -20
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +936 -68
- package/types/vue.d.ts +226 -12
- package/types/words.d.ts +79 -0
|
@@ -4,14 +4,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
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
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, formatWords, HelixElement, words } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, formatWords, HelixBeforeToggleEvent, HelixElement, HelixToggleEvent, requestChange, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { nothing } from 'lit';
|
|
9
9
|
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
11
11
|
import { html } from 'lit/static-html.js';
|
|
12
12
|
import '../../copy-button/index.js';
|
|
13
13
|
import { highlightedLines } from '../highlight.js';
|
|
14
|
-
import { gutterNumbers, lineCount } from '../lines.js';
|
|
14
|
+
import { gutterNumbers, lineCount, lineRuns, lineSpans } from '../lines.js';
|
|
15
15
|
import { pairLines, parsePatch } from '../patch.js';
|
|
16
16
|
let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
17
17
|
constructor() {
|
|
@@ -28,12 +28,17 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
28
28
|
this.label = '';
|
|
29
29
|
this.language = '';
|
|
30
30
|
this.lineNumbers = false;
|
|
31
|
+
this.markLines = '';
|
|
31
32
|
this.noCopy = false;
|
|
33
|
+
this.wrap = false;
|
|
34
|
+
this.wrapLabel = 'Wrap lines';
|
|
35
|
+
this.wrappable = false;
|
|
32
36
|
this.parsed = { files: [], text: '' };
|
|
33
37
|
this.capped = false;
|
|
34
38
|
this.expanded = false;
|
|
35
39
|
this.measure = () => {
|
|
36
40
|
this.markScrollers();
|
|
41
|
+
this.placeLines();
|
|
37
42
|
if (!this.expanded) {
|
|
38
43
|
this.capped =
|
|
39
44
|
this.expandable &&
|
|
@@ -43,6 +48,20 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
43
48
|
};
|
|
44
49
|
}
|
|
45
50
|
static { this.words = 'helix-code-block'; }
|
|
51
|
+
toggleWrap() {
|
|
52
|
+
const next = !this.wrap;
|
|
53
|
+
const applied = requestChange({
|
|
54
|
+
apply: (checked) => {
|
|
55
|
+
this.wrap = checked;
|
|
56
|
+
},
|
|
57
|
+
event: (checked) => new HelixBeforeToggleEvent({ checked }),
|
|
58
|
+
host: this,
|
|
59
|
+
next,
|
|
60
|
+
});
|
|
61
|
+
if (applied) {
|
|
62
|
+
this.dispatchEvent(new HelixToggleEvent({ checked: next }));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
46
65
|
get files() {
|
|
47
66
|
if (!this.diff) {
|
|
48
67
|
return [];
|
|
@@ -85,6 +104,89 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
85
104
|
}
|
|
86
105
|
}
|
|
87
106
|
}
|
|
107
|
+
lineBoxes(code) {
|
|
108
|
+
const slot = code.querySelector('slot');
|
|
109
|
+
const roots = slot && !slot.hidden ? slot.assignedNodes({ flatten: true }) : [code];
|
|
110
|
+
const texts = [];
|
|
111
|
+
for (const root of roots) {
|
|
112
|
+
if (root.nodeType === Node.TEXT_NODE) {
|
|
113
|
+
texts.push(root);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const walker = (root.ownerDocument ?? document).createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
117
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
118
|
+
texts.push(node);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const starts = [];
|
|
122
|
+
let total = 0;
|
|
123
|
+
for (const text of texts) {
|
|
124
|
+
starts.push(total);
|
|
125
|
+
total += text.data.length;
|
|
126
|
+
}
|
|
127
|
+
const at = (offset) => {
|
|
128
|
+
for (let index = texts.length - 1; index >= 0; index -= 1) {
|
|
129
|
+
const start = starts[index] ?? 0;
|
|
130
|
+
if (offset >= start) {
|
|
131
|
+
const text = texts[index];
|
|
132
|
+
return [text, Math.min(offset - start, text.data.length)];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
};
|
|
137
|
+
const box = code.getBoundingClientRect();
|
|
138
|
+
const origin = box.top + code.clientTop - code.scrollTop;
|
|
139
|
+
const row = Number.parseFloat(getComputedStyle(code).lineHeight) || 0;
|
|
140
|
+
const boxes = [];
|
|
141
|
+
for (const [from, to] of lineSpans(texts.map((text) => text.data).join(''))) {
|
|
142
|
+
const start = at(from);
|
|
143
|
+
const end = at(to === from ? Math.min(to + 1, total) : to);
|
|
144
|
+
const previous = boxes.at(-1);
|
|
145
|
+
let top = previous ? previous.bottom : 0;
|
|
146
|
+
let bottom = top + row;
|
|
147
|
+
if (start && end) {
|
|
148
|
+
const range = (code.ownerDocument ?? document).createRange();
|
|
149
|
+
range.setStart(start[0], start[1]);
|
|
150
|
+
range.setEnd(end[0], end[1]);
|
|
151
|
+
const rects = [...range.getClientRects()].filter((rect) => rect.height > 0);
|
|
152
|
+
if (rects.length > 0) {
|
|
153
|
+
const half = Math.max(0, (row - (rects[0]?.height ?? row)) / 2);
|
|
154
|
+
top = Math.min(...rects.map((rect) => rect.top)) - origin - half;
|
|
155
|
+
bottom = Math.max(...rects.map((rect) => rect.bottom)) - origin + half;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
boxes.push({ bottom, top });
|
|
159
|
+
}
|
|
160
|
+
return boxes;
|
|
161
|
+
}
|
|
162
|
+
placeLines() {
|
|
163
|
+
const code = this.renderRoot.querySelector('[part="code"]');
|
|
164
|
+
const marks = [...this.renderRoot.querySelectorAll('[part="mark"]')];
|
|
165
|
+
const numbers = [
|
|
166
|
+
...this.renderRoot.querySelectorAll('[part="gutter"] > [part="number"]'),
|
|
167
|
+
];
|
|
168
|
+
if (!code || (marks.length === 0 && numbers.length === 0)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const boxes = this.lineBoxes(code);
|
|
172
|
+
for (const mark of marks) {
|
|
173
|
+
const [first, last] = (mark.dataset.lines ?? '').split('-').map(Number);
|
|
174
|
+
const top = boxes[(first ?? 0) - 1];
|
|
175
|
+
const bottom = boxes[Math.min(last ?? 0, boxes.length) - 1];
|
|
176
|
+
mark.hidden = !top || !bottom;
|
|
177
|
+
if (top && bottom) {
|
|
178
|
+
mark.style.setProperty('--code-block-mark-start', `${top.top}px`);
|
|
179
|
+
mark.style.setProperty('--code-block-mark-size', `${bottom.bottom - top.top}px`);
|
|
180
|
+
mark.style.setProperty('--code-block-mark-inline-size', `${code.scrollWidth}px`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
numbers.forEach((number, index) => {
|
|
184
|
+
const line = boxes[index];
|
|
185
|
+
if (line) {
|
|
186
|
+
number.style.setProperty('--code-block-line-size', `${line.bottom - line.top}px`);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
88
190
|
updated(changed) {
|
|
89
191
|
super.updated(changed);
|
|
90
192
|
if (typeof ResizeObserver !== 'undefined') {
|
|
@@ -126,21 +228,29 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
126
228
|
catch (error) {
|
|
127
229
|
globalThis.reportError?.(error);
|
|
128
230
|
}
|
|
231
|
+
const runs = this.childrenKnown ? lineRuns(this.markLines) : [];
|
|
232
|
+
const marks = runs.map(([first, last]) => html `<span part="mark" aria-hidden="true" data-lines="${first}-${last}"></span>`);
|
|
129
233
|
const code = highlighted === undefined
|
|
130
|
-
? html `<code part="code"
|
|
131
|
-
: html `<code part="code">${unsafeHTML(highlighted)}</code><slot
|
|
234
|
+
? html `<code part="code">${marks}<slot @slotchange=${() => this.requestUpdate()}></slot></code>`
|
|
235
|
+
: html `<code part="code">${marks}${unsafeHTML(highlighted)}</code><slot
|
|
132
236
|
hidden
|
|
133
237
|
@slotchange=${() => this.requestUpdate()}
|
|
134
238
|
></slot>`;
|
|
135
239
|
const lines = lineCount(text);
|
|
240
|
+
const numbers = lines === 0
|
|
241
|
+
? nothing
|
|
242
|
+
: this.wrap
|
|
243
|
+
? Array.from({ length: lines }, (_, index) => html `<span part="number">${index + 1}
|
|
244
|
+
</span>`)
|
|
245
|
+
: gutterNumbers(lines);
|
|
136
246
|
const gutter = this.lineNumbers
|
|
137
|
-
? html `<span part="gutter" aria-hidden="true">${
|
|
247
|
+
? html `<span part="gutter" aria-hidden="true">${numbers}</span>`
|
|
138
248
|
: nothing;
|
|
139
249
|
const name = this.label || this.language;
|
|
140
250
|
return html `${this.renderHeader(name)}
|
|
141
251
|
<div part="frame">
|
|
142
252
|
<pre id="block" part="block" translate="no">${gutter}${code}</pre>
|
|
143
|
-
${name ? nothing : this.renderCopy()}
|
|
253
|
+
${this.headed(name) ? nothing : this.renderCopy()}
|
|
144
254
|
</div>${this.renderExpand()}`;
|
|
145
255
|
}
|
|
146
256
|
renderExpand() {
|
|
@@ -160,11 +270,29 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
160
270
|
${this.expanded ? this.collapseLabel : this.expandLabel}
|
|
161
271
|
</button>`;
|
|
162
272
|
}
|
|
273
|
+
headed(name) {
|
|
274
|
+
return name !== '' || (this.wrappable && !this.diff) || this.slotted('actions');
|
|
275
|
+
}
|
|
163
276
|
renderHeader(name, value) {
|
|
164
|
-
|
|
165
|
-
|
|
277
|
+
const acting = this.slotted('actions');
|
|
278
|
+
return this.headed(name)
|
|
279
|
+
? html `<div part="header">${name ? html `<span part="label">${name}</span>` : nothing}${acting ? html `<span part="actions"><slot name="actions"></slot></span>` : nothing}${this.renderWrapToggle()}${this.renderCopy(value, name)}</div>`
|
|
166
280
|
: nothing;
|
|
167
281
|
}
|
|
282
|
+
renderWrapToggle() {
|
|
283
|
+
if (!this.wrappable || this.diff) {
|
|
284
|
+
return nothing;
|
|
285
|
+
}
|
|
286
|
+
return html `<button
|
|
287
|
+
part="wrap-toggle"
|
|
288
|
+
type="button"
|
|
289
|
+
aria-pressed=${this.wrap ? 'true' : 'false'}
|
|
290
|
+
@click=${() => this.toggleWrap()}
|
|
291
|
+
>${this.wrapLabel}</button>`;
|
|
292
|
+
}
|
|
293
|
+
slotted(name) {
|
|
294
|
+
return this.knownChildren.some((child) => child.getAttribute('slot') === name);
|
|
295
|
+
}
|
|
168
296
|
renderCopy(value, name) {
|
|
169
297
|
return this.noCopy ? nothing : this.renderCopyButton(value, name);
|
|
170
298
|
}
|
|
@@ -179,7 +307,10 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
179
307
|
copied-label=${this.copiedLabel}
|
|
180
308
|
label=${name ? formatWords(this, 'copyNameLabel', { copy: this.copyLabel, name }) : this.copyLabel}
|
|
181
309
|
.value=${value}
|
|
182
|
-
|
|
310
|
+
>${this.renderCopyIcons()}</${this.tag('copy-button')}>`;
|
|
311
|
+
}
|
|
312
|
+
renderCopyIcons() {
|
|
313
|
+
return html `${this.slotted('copy-icon') ? html `<slot name="copy-icon"></slot>` : nothing}${this.slotted('copied-icon') ? html `<slot name="copied-icon" slot="copied"></slot>` : nothing}`;
|
|
183
314
|
}
|
|
184
315
|
renderNumber(value, width) {
|
|
185
316
|
return html `<span part="number" aria-hidden="true">${String(value ?? '').padStart(width)} </span>`;
|
|
@@ -223,7 +354,7 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
|
|
|
223
354
|
${this.variant === 'split'
|
|
224
355
|
? this.renderSplit(file, width)
|
|
225
356
|
: html `<pre part="block" translate="no"><code part="code">${file.hunks.map((hunk) => html `${this.renderHunk(hunk)}${hunk.lines.map((line) => this.renderLine(line, width))}`)}</code></pre>`}
|
|
226
|
-
${name ? nothing : this.renderCopy(now)}
|
|
357
|
+
${this.headed(name) ? nothing : this.renderCopy(now)}
|
|
227
358
|
</div>
|
|
228
359
|
</div>`;
|
|
229
360
|
})}<slot hidden @slotchange=${() => this.requestUpdate()}></slot>`;
|
|
@@ -275,12 +406,25 @@ __decorate([
|
|
|
275
406
|
__decorate([
|
|
276
407
|
property({ attribute: 'line-numbers', reflect: true, type: Boolean })
|
|
277
408
|
], HelixHeadlessCodeBlock.prototype, "lineNumbers", void 0);
|
|
409
|
+
__decorate([
|
|
410
|
+
property({ attribute: 'mark-lines' })
|
|
411
|
+
], HelixHeadlessCodeBlock.prototype, "markLines", void 0);
|
|
278
412
|
__decorate([
|
|
279
413
|
property({ attribute: 'no-copy', reflect: true, type: Boolean })
|
|
280
414
|
], HelixHeadlessCodeBlock.prototype, "noCopy", void 0);
|
|
281
415
|
__decorate([
|
|
282
416
|
property({ reflect: true })
|
|
283
417
|
], HelixHeadlessCodeBlock.prototype, "variant", void 0);
|
|
418
|
+
__decorate([
|
|
419
|
+
property({ reflect: true, type: Boolean })
|
|
420
|
+
], HelixHeadlessCodeBlock.prototype, "wrap", void 0);
|
|
421
|
+
__decorate([
|
|
422
|
+
words,
|
|
423
|
+
property({ attribute: 'wrap-label' })
|
|
424
|
+
], HelixHeadlessCodeBlock.prototype, "wrapLabel", void 0);
|
|
425
|
+
__decorate([
|
|
426
|
+
property({ type: Boolean })
|
|
427
|
+
], HelixHeadlessCodeBlock.prototype, "wrappable", void 0);
|
|
284
428
|
__decorate([
|
|
285
429
|
state()
|
|
286
430
|
], HelixHeadlessCodeBlock.prototype, "capped", void 0);
|
|
@@ -36,6 +36,7 @@ registerElementEvents({
|
|
|
36
36
|
'helix-headless-chat-message': ['helix-send'],
|
|
37
37
|
'helix-headless-checkbox': ['change', 'helix-before-toggle', 'input'],
|
|
38
38
|
'helix-headless-checkbox-group': ['change', 'helix-before-change', 'input'],
|
|
39
|
+
'helix-headless-code-block': ['helix-before-toggle', 'helix-toggle'],
|
|
39
40
|
'helix-headless-color-input': [
|
|
40
41
|
'change',
|
|
41
42
|
'helix-before-change',
|
|
@@ -253,11 +254,14 @@ registerElementEvents({
|
|
|
253
254
|
'helix-headless-tab-panel': ['helix-load'],
|
|
254
255
|
'helix-headless-table': [
|
|
255
256
|
'helix-before-reorder',
|
|
257
|
+
'helix-before-toggle',
|
|
258
|
+
'helix-load-more',
|
|
256
259
|
'helix-reorder',
|
|
257
260
|
'helix-select',
|
|
258
261
|
'helix-selected',
|
|
259
262
|
'helix-sort',
|
|
260
263
|
'helix-sorted',
|
|
264
|
+
'helix-toggle',
|
|
261
265
|
],
|
|
262
266
|
'helix-headless-table-column': ['helix-before-resize', 'helix-resized'],
|
|
263
267
|
'helix-headless-tabs': ['helix-activate', 'helix-before-activate', 'helix-select'],
|
|
@@ -123,6 +123,16 @@ export declare class HelixHeadlessSplitter extends HelixElement {
|
|
|
123
123
|
* The name of the handle where two dividers cross, read by screen readers.
|
|
124
124
|
*/
|
|
125
125
|
cornerLabel: string;
|
|
126
|
+
/**
|
|
127
|
+
* What the handle where two dividers cross says it is set to, both dividers
|
|
128
|
+
* at once: `{across}` is where the divider that runs down stands, and
|
|
129
|
+
* `{down}` where the one that runs across stands, each a percentage.
|
|
130
|
+
*
|
|
131
|
+
* **Both, because the handle moves both**, and a number names one: the
|
|
132
|
+
* handle's `aria-valuenow` is the divider its `aria-orientation` names, and
|
|
133
|
+
* these words are the whole of where it is.
|
|
134
|
+
*/
|
|
135
|
+
cornerValueLabel: string;
|
|
126
136
|
/**
|
|
127
137
|
* Which pane starts shut, when `collapsed` is not set.
|
|
128
138
|
*/
|
|
@@ -410,6 +420,11 @@ export declare class HelixHeadlessSplitter extends HelixElement {
|
|
|
410
420
|
* a splitter told `horizontal` has been given its answer already.
|
|
411
421
|
*/
|
|
412
422
|
private watchWidth;
|
|
423
|
+
/**
|
|
424
|
+
* Where a crossing stands in words: this splitter's divider at `own` and the
|
|
425
|
+
* nested one's at `theirs`, as `corner-value-label` puts them.
|
|
426
|
+
*/
|
|
427
|
+
private cornerWords;
|
|
413
428
|
/**
|
|
414
429
|
* Whether the panes are stacked. Public because a nested splitter compares
|
|
415
430
|
* its axis with the one it is in.
|
|
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
var HelixHeadlessSplitter_1;
|
|
8
|
-
import { AriaForwardController, capturePointer, closestInstance, commaList, customElement, formatPercent, HelixBeforeCollapseEvent, HelixBeforeResizeEvent, HelixCollapseEvent, HelixElement, HelixResizedEvent, inlineStep, isRtl, requestChange, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { AriaForwardController, capturePointer, closestInstance, commaList, customElement, formatPercent, formatWords, HelixBeforeCollapseEvent, HelixBeforeResizeEvent, HelixCollapseEvent, HelixElement, HelixResizedEvent, inlineStep, isRtl, requestChange, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { snapTo } from '../snap.js';
|
|
@@ -168,6 +168,15 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
168
168
|
});
|
|
169
169
|
this.sizes.observe(this);
|
|
170
170
|
}
|
|
171
|
+
cornerWords(own, theirs) {
|
|
172
|
+
const locale = wordsLocale(this);
|
|
173
|
+
const mine = formatPercent(Math.round(own) / 100, locale);
|
|
174
|
+
const nested = formatPercent(Math.round(theirs) / 100, locale);
|
|
175
|
+
return formatWords(this, 'cornerValueLabel', {
|
|
176
|
+
across: this.vertical ? nested : mine,
|
|
177
|
+
down: this.vertical ? mine : nested,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
171
180
|
get vertical() {
|
|
172
181
|
return this.orientation === 'vertical' || (this.orientation === 'auto' && this.stacked);
|
|
173
182
|
}
|
|
@@ -269,6 +278,7 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
269
278
|
this.collapsedLabel = 'Collapsed';
|
|
270
279
|
this.collapsedSize = 0;
|
|
271
280
|
this.cornerLabel = 'Resize both';
|
|
281
|
+
this.cornerValueLabel = '{across} across, {down} down';
|
|
272
282
|
this.defaultPosition = 50;
|
|
273
283
|
this.label = 'Resize panes';
|
|
274
284
|
this.aria = new AriaForwardController(this, {
|
|
@@ -633,7 +643,7 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
633
643
|
aria-valuemax="100"
|
|
634
644
|
aria-valuemin="0"
|
|
635
645
|
aria-valuenow=${Math.round(crossing.at)}
|
|
636
|
-
aria-valuetext=${
|
|
646
|
+
aria-valuetext=${this.cornerWords(position, crossing.at)}
|
|
637
647
|
data-cross=${index}
|
|
638
648
|
style="--splitter-corner-at: ${crossing.at}%; --splitter-position: ${position}%"
|
|
639
649
|
@dblclick=${this.handleCornerDoubleClick}
|
|
@@ -659,6 +669,10 @@ __decorate([
|
|
|
659
669
|
words,
|
|
660
670
|
property({ attribute: 'corner-label' })
|
|
661
671
|
], HelixHeadlessSplitter.prototype, "cornerLabel", void 0);
|
|
672
|
+
__decorate([
|
|
673
|
+
words,
|
|
674
|
+
property({ attribute: 'corner-value-label' })
|
|
675
|
+
], HelixHeadlessSplitter.prototype, "cornerValueLabel", void 0);
|
|
662
676
|
__decorate([
|
|
663
677
|
property({ attribute: 'default-collapsed' })
|
|
664
678
|
], HelixHeadlessSplitter.prototype, "defaultCollapsed", void 0);
|
|
@@ -5,7 +5,7 @@ import type { HelixHeadlessTableColumn } from './column/table-column.js';
|
|
|
5
5
|
* A column the table draws before the consumer's cells: the grips of a
|
|
6
6
|
* `reorderable` table, and the checkboxes of a `selectable` one.
|
|
7
7
|
*/
|
|
8
|
-
export type LeadColumn = 'grip' | 'select';
|
|
8
|
+
export type LeadColumn = 'expand' | 'grip' | 'select';
|
|
9
9
|
/** A place in the grid with the element that holds focus there. */
|
|
10
10
|
export interface GridCell extends GridPlace {
|
|
11
11
|
cell: HTMLElement;
|
|
@@ -36,6 +36,12 @@ export interface TableGridOptions {
|
|
|
36
36
|
selectRange: (to: number, from: number) => boolean;
|
|
37
37
|
/** Selects every row for Control or Command with A, and says whether it did. */
|
|
38
38
|
selectAll: () => boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Focus moved to another row by a key with no Shift, row `to`, and
|
|
41
|
+
* `control` says whether Control or Command was held. A table whose moves
|
|
42
|
+
* carry the selection selects the row there.
|
|
43
|
+
*/
|
|
44
|
+
arrive?: (to: number, control: boolean) => void;
|
|
39
45
|
/** Whether the table is a grid now. */
|
|
40
46
|
on: () => boolean;
|
|
41
47
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { deepActiveElement, deepContains, focusableElements, gridMove, inlineStep, pageRows, scrollingAncestor, scrollRoom, selectsAll, Typeahead, typesCharacter, whenHydrated, } from '@fusion.dev/helix-core';
|
|
2
|
-
const HEADS = {
|
|
2
|
+
const HEADS = {
|
|
3
|
+
expand: 'expand-head',
|
|
4
|
+
grip: 'grip-head',
|
|
5
|
+
select: 'select-all',
|
|
6
|
+
};
|
|
3
7
|
const shown = (element) => {
|
|
4
8
|
const hidden = element.getAttribute('hidden');
|
|
5
9
|
return hidden === null || hidden === 'until-found';
|
|
@@ -83,6 +87,9 @@ export class TableGridController {
|
|
|
83
87
|
to.row >= 0) {
|
|
84
88
|
this.options.selectRange(to.row, place.row);
|
|
85
89
|
}
|
|
90
|
+
else if (!event.shiftKey && to.row >= 0 && to.row !== place.row) {
|
|
91
|
+
this.options.arrive?.(to.row, event.ctrlKey || event.metaKey);
|
|
92
|
+
}
|
|
86
93
|
if (to.row !== place.row || to.column !== place.column) {
|
|
87
94
|
this.moveTo(to);
|
|
88
95
|
}
|
|
@@ -246,6 +253,7 @@ export class TableGridController {
|
|
|
246
253
|
row.gridCell = on;
|
|
247
254
|
row.gripStop = here && lead[at.column] === 'grip';
|
|
248
255
|
row.selectStop = here && lead[at.column] === 'select';
|
|
256
|
+
row.expandStop = here && lead[at.column] === 'expand';
|
|
249
257
|
});
|
|
250
258
|
}
|
|
251
259
|
for (const [place, cell] of this.cellsOf(line).entries()) {
|
|
@@ -53,6 +53,27 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
|
|
|
53
53
|
* Tab reaches each checkbox, and Space ticks it. To act on one row, put a
|
|
54
54
|
* link or a button in a cell, or give the row an `href`.
|
|
55
55
|
*
|
|
56
|
+
* `selection-mode="single"` selects one row at a time: ticking a row lets go
|
|
57
|
+
* of the one before, the heading holds no checkbox, and Shift and Control with
|
|
58
|
+
* A take in nothing more. `selection-behavior="replace"` makes a press on a
|
|
59
|
+
* row, anywhere but its own controls, select it alone, as a file list does,
|
|
60
|
+
* and in a `grid` table the arrow keys carry the selection with them. Control
|
|
61
|
+
* or Command with a press adds or takes out the row, and with an arrow moves
|
|
62
|
+
* without selecting. Shift with a press takes in the rows from the one last
|
|
63
|
+
* chosen.
|
|
64
|
+
*
|
|
65
|
+
* **Nesting rows.** A row with `parent` sits under the row of that key, as
|
|
66
|
+
* a file sits in a folder. The table then draws a column of disclosure
|
|
67
|
+
* buttons, under a heading named by `expand-column-label`: a row with rows
|
|
68
|
+
* under it has a button named by `expand-row-label` ("Expand {row}") that
|
|
69
|
+
* says with `aria-expanded` whether it is open, and the rows under a shut row
|
|
70
|
+
* are not drawn. `expanded` holds the open rows' keys, comma separated, and
|
|
71
|
+
* `default-expanded` is where it starts. A press asks with
|
|
72
|
+
* `helix-before-toggle` and tells with `helix-toggle`, both from the row, so
|
|
73
|
+
* you can refuse it or load the rows on the way. A sort orders the rows
|
|
74
|
+
* under each row among themselves, and in a `grid` table the table is a
|
|
75
|
+
* `treegrid` whose rows say their depth.
|
|
76
|
+
*
|
|
56
77
|
* **Moving rows.** `reorderable` adds a column of grips before the checkboxes,
|
|
57
78
|
* under a heading named by `row-grips-label`.
|
|
58
79
|
*
|
|
@@ -89,13 +110,20 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
|
|
|
89
110
|
* | Enter on a cell of a row with `href` | Follows the row's link. |
|
|
90
111
|
* | Enter, Space on a sortable heading | Sorts by the column. |
|
|
91
112
|
* | Space on a row, Enter or Space on its checkbox cell | Selects the row, in a `selectable` table. |
|
|
113
|
+
* | Enter or Space on a disclosure cell | Shows or hides the rows under the row, in a table whose rows nest. |
|
|
92
114
|
* | Enter or Space on a grip cell | Picks the row up, or drops it, in a `reorderable` table. Up, Down, Home, and End move a row picked up, and Escape puts it back. |
|
|
115
|
+
* | Down, Up, Page Down, Page Up under `selection-behavior="replace"` | Selects the row it moves to alone. Control or Command with the key moves without selecting. |
|
|
93
116
|
* | Shift with Down or Up | Selects the rows it moves across, in a `selectable` table. |
|
|
94
117
|
* | Shift with Space | Selects every row from the one last chosen, in a `selectable` table. |
|
|
95
118
|
* | Control or Command with A | Selects every row, in a `selectable` table. |
|
|
96
119
|
* | A letter or a word | Moves to the next row whose first cell starts with what you typed. |
|
|
97
120
|
* | Tab | Leaves the table. |
|
|
98
121
|
*
|
|
122
|
+
* **Loading more.** `load-more` sends `helix-load-more` once the end of the
|
|
123
|
+
* table comes within a screen of view, in the box that scrolls it or the
|
|
124
|
+
* page, once for each number of rows: add rows and it can send it again. Set
|
|
125
|
+
* `loading` while you fetch, and the table asks for nothing until it clears.
|
|
126
|
+
*
|
|
99
127
|
* When the rows did not load, set `error-text`, such as "The orders did not
|
|
100
128
|
* load", and put a button that tries again in the `error` slot. The line shows
|
|
101
129
|
* after the rows in place of the empty and loading lines, the live region says
|
|
@@ -119,11 +147,14 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
|
|
|
119
147
|
* @slot loading - Content shown while the rows load, such as a spinner. Falls back to `loading-text`, which the live region still says.
|
|
120
148
|
*
|
|
121
149
|
* @event {HelixBeforeReorderEvent} helix-before-reorder - Fired when a row is dropped at a new place, in a `reorderable` table, with every row's key in the new order. Cancelable: cancel it to keep the row where it is.
|
|
150
|
+
* @event {HelixBeforeToggleEvent} helix-before-toggle - Fired from a row when its disclosure button is pressed, with whether it would be open in `detail.checked`. Cancelable: cancel it to keep the row as it is.
|
|
151
|
+
* @event {HelixLoadMoreEvent} helix-load-more - Fired, in a table with `load-more`, when the end of the table comes within a screen of view, once for each number of rows.
|
|
122
152
|
* @event {HelixReorderEvent} helix-reorder - Fired after `helix-before-reorder` when nobody canceled it. Move your rows to the order in `keys`. Cancelable: cancel it to say the move did not happen.
|
|
123
153
|
* @event {HelixSelectEvent} helix-select - The selection is about to change. The event carries every selected key, separated by commas. Cancelable: cancel it to keep the current selection.
|
|
124
154
|
* @event {HelixSelectedEvent} helix-selected - Fired after the selection changes, with every selected key, separated by commas.
|
|
125
155
|
* @event {HelixSortEvent} helix-sort - A different sort order was requested, as `field:direction`, or empty for the order the rows were written in. Cancelable: cancel it to keep the current order.
|
|
126
156
|
* @event {HelixSortedEvent} helix-sorted - Fired after the sort order changes, with the new order.
|
|
157
|
+
* @event {HelixToggleEvent} helix-toggle - Fired from a row once the rows under it are shown or hidden, with whether it is open in `detail.checked`.
|
|
127
158
|
*
|
|
128
159
|
* @csspart checkbox - The checkbox in the heading that selects every row, in a `selectable` table.
|
|
129
160
|
* @csspart checkbox-box - The box drawn for the checkbox's input.
|
|
@@ -139,6 +170,7 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
|
|
|
139
170
|
* @csspart drag-preview - A copy of the row a pointer drags by its grip, which follows the pointer in the top layer until the row is let go, in a `reorderable` table. Hidden from assistive technology.
|
|
140
171
|
* @csspart empty - The text shown when there are no rows.
|
|
141
172
|
* @csspart error - The line shown after the rows while `error-text` is set, in place of the empty and loading text.
|
|
173
|
+
* @csspart expand-head - The heading over the disclosure buttons, in a table whose rows nest.
|
|
142
174
|
* @csspart grid-end - An empty box after the rows, in a `grid` table. Tab focuses it on its way out of the table, so the key leaves from the end.
|
|
143
175
|
* @csspart grid-start - An empty box before the rows, in a `grid` table. Shift with Tab focuses it on its way out of the table.
|
|
144
176
|
* @csspart grip-head - The heading over the row grips, in a `reorderable` table.
|
|
@@ -205,6 +237,11 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
205
237
|
* declared in it has nowhere else to be sized from.
|
|
206
238
|
*/
|
|
207
239
|
columns: string;
|
|
240
|
+
/**
|
|
241
|
+
* The keys of the rows whose rows are shown at the start, separated by
|
|
242
|
+
* commas. Used until `expanded` is set.
|
|
243
|
+
*/
|
|
244
|
+
defaultExpanded: string;
|
|
208
245
|
/**
|
|
209
246
|
* The keys selected at the start, separated by commas. Used until `selected`
|
|
210
247
|
* is set.
|
|
@@ -234,6 +271,17 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
234
271
|
* says it.
|
|
235
272
|
*/
|
|
236
273
|
errorText: string;
|
|
274
|
+
/** The name of the heading over the disclosure buttons. */
|
|
275
|
+
expandColumnLabel: string;
|
|
276
|
+
/**
|
|
277
|
+
* The keys of the rows whose rows are shown, separated by commas, in a table
|
|
278
|
+
* whose rows nest. A press on a row's disclosure button asks first, and
|
|
279
|
+
* `default-expanded` seeds what this element then owns.
|
|
280
|
+
*/
|
|
281
|
+
get expanded(): string;
|
|
282
|
+
set expanded(next: string);
|
|
283
|
+
/** The name of a row's disclosure button. `{row}` is the words of the row's first cell. */
|
|
284
|
+
expandRowLabel: string;
|
|
237
285
|
/**
|
|
238
286
|
* The table's name where neither `label` nor an `aria-label` or
|
|
239
287
|
* `aria-labelledby` on the host gives it one.
|
|
@@ -256,6 +304,11 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
256
304
|
* wins over this.
|
|
257
305
|
*/
|
|
258
306
|
label: string;
|
|
307
|
+
/**
|
|
308
|
+
* Sends `helix-load-more` once the end of the table comes within a screen
|
|
309
|
+
* of view, so you can add the next rows as a reader nears them.
|
|
310
|
+
*/
|
|
311
|
+
loadMore: boolean;
|
|
259
312
|
/**
|
|
260
313
|
* What screen readers hear each time a key moves a held row, in a
|
|
261
314
|
* `reorderable` table. `{name}` is the row's words, `{position}` the place it
|
|
@@ -286,6 +339,11 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
286
339
|
selectable: boolean;
|
|
287
340
|
/** The name of the checkbox in the heading that selects every row. */
|
|
288
341
|
selectAllLabel: string;
|
|
342
|
+
/**
|
|
343
|
+
* The name of the heading over the checkboxes under
|
|
344
|
+
* `selection-mode="single"`, where it holds no checkbox of its own.
|
|
345
|
+
*/
|
|
346
|
+
selectColumnLabel: string;
|
|
289
347
|
/**
|
|
290
348
|
* The keys of the selected rows, separated by commas.
|
|
291
349
|
*
|
|
@@ -302,6 +360,17 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
302
360
|
* {# wybranych} other {# wybranego}}`.
|
|
303
361
|
*/
|
|
304
362
|
selectedLabel: string;
|
|
363
|
+
/**
|
|
364
|
+
* What a press on a row, or in a `grid` table a key that moves to one, does
|
|
365
|
+
* to the rows already selected: `toggle`, the default, leaves them to the
|
|
366
|
+
* checkboxes, and `replace` selects the row alone.
|
|
367
|
+
*/
|
|
368
|
+
selectionBehavior: 'replace' | 'toggle';
|
|
369
|
+
/**
|
|
370
|
+
* How many rows a `selectable` table selects: `multiple`, the default, or
|
|
371
|
+
* `single`, one at a time.
|
|
372
|
+
*/
|
|
373
|
+
selectionMode?: 'multiple' | 'single';
|
|
305
374
|
/**
|
|
306
375
|
* The name of a row's checkbox. `{row}` is replaced with the words of the
|
|
307
376
|
* row's first cell.
|
|
@@ -329,6 +398,16 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
329
398
|
unsortedLabel: string;
|
|
330
399
|
private sortState;
|
|
331
400
|
private selectedState;
|
|
401
|
+
private expandedState;
|
|
402
|
+
/** Whether any row sits under another, which makes the table a tree. */
|
|
403
|
+
private get nests();
|
|
404
|
+
/** The keys of the rows whose rows are shown. */
|
|
405
|
+
private get open();
|
|
406
|
+
/**
|
|
407
|
+
* Shows or hides the rows under the row of `key`, asking first from the row,
|
|
408
|
+
* and saying so from it once it stands.
|
|
409
|
+
*/
|
|
410
|
+
private toggleRow;
|
|
332
411
|
/** Whether the heading names the table, so taking it away clears only this element's own name. */
|
|
333
412
|
private headed;
|
|
334
413
|
/**
|
|
@@ -377,6 +456,15 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
377
456
|
* which is what a framework holding those rows needs.
|
|
378
457
|
*/
|
|
379
458
|
private get arrangedRows();
|
|
459
|
+
/**
|
|
460
|
+
* The rows of a tree in the order they are drawn: each row followed by the
|
|
461
|
+
* rows under it while it is open, at every depth, keeping the order `rows`
|
|
462
|
+
* is in among the rows under one row. A row under a key no row has is drawn
|
|
463
|
+
* at the top.
|
|
464
|
+
*/
|
|
465
|
+
private nested;
|
|
466
|
+
/** How deep each row sits, from 1, and whether rows sit under it, by key. */
|
|
467
|
+
private shape;
|
|
380
468
|
/** The sort last heard, so a render that changed nothing says nothing. */
|
|
381
469
|
private heardSort;
|
|
382
470
|
/**
|
|
@@ -419,6 +507,22 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
419
507
|
* them where each already is. A `disabled` row keeps what it had.
|
|
420
508
|
*/
|
|
421
509
|
private chooseAll;
|
|
510
|
+
/** Whether the table selects one row at a time. */
|
|
511
|
+
private get single();
|
|
512
|
+
/** Whether a press or a move on a row selects it alone. */
|
|
513
|
+
private get replaces();
|
|
514
|
+
/**
|
|
515
|
+
* Selects the drawn row `at` alone, and makes it where a range runs from.
|
|
516
|
+
* Asks nothing where it is already the only one selected.
|
|
517
|
+
*/
|
|
518
|
+
private chooseOnly;
|
|
519
|
+
/**
|
|
520
|
+
* A press on a row under `selection-behavior="replace"`: it selects the row
|
|
521
|
+
* alone, Control or Command adds or takes out the row, and Shift takes in
|
|
522
|
+
* the rows from the one last chosen. A press on the row's own controls, its
|
|
523
|
+
* checkbox or its grip is theirs.
|
|
524
|
+
*/
|
|
525
|
+
private readonly handleRowClick;
|
|
422
526
|
/** The key of the row a Shift range runs from: the one last chosen. */
|
|
423
527
|
private anchorKey;
|
|
424
528
|
/**
|
|
@@ -588,8 +692,26 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
|
|
|
588
692
|
private alignCells;
|
|
589
693
|
protected willUpdate(changed: PropertyValues): void;
|
|
590
694
|
protected updated(changed: PropertyValues): void;
|
|
695
|
+
/** Watches the end of a `load-more` table coming near the box that scrolls it. */
|
|
696
|
+
private endWatcher;
|
|
697
|
+
/** The end being watched, so a new one is watched again. */
|
|
698
|
+
private watchedEnd;
|
|
699
|
+
/** The number of rows `helix-load-more` was last sent for. */
|
|
700
|
+
private askedAt;
|
|
701
|
+
/**
|
|
702
|
+
* Starts or stops watching the end of the table, against the box that
|
|
703
|
+
* scrolls it, or the page, with a screen of room below it, so the next rows
|
|
704
|
+
* are asked for before a reader reaches the last. List does the same.
|
|
705
|
+
*/
|
|
706
|
+
private watchEnd;
|
|
707
|
+
/** Whether the end of the table was in reach when last measured. */
|
|
708
|
+
private endInView;
|
|
709
|
+
/** Sends `helix-load-more`, once for each number of rows, and never while loading. */
|
|
710
|
+
private askForMore;
|
|
591
711
|
/** The heading over the row grips, named by `row-grips-label`. */
|
|
592
712
|
private renderGripHead;
|
|
713
|
+
/** The heading over the disclosure buttons, in a table whose rows nest. */
|
|
714
|
+
private renderExpandHead;
|
|
593
715
|
/** The heading of the checkbox column, which selects every row. */
|
|
594
716
|
private renderSelectAll;
|
|
595
717
|
protected render(): TemplateResult;
|