@fusion.dev/helix 0.162.1 → 0.164.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.
@@ -4,9 +4,9 @@ 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, HelixBeforePanelResizeEvent, HelixBeforeSplitEvent, HelixElement, HelixPanelResizeEvent, HelixSplitEvent, requestChange, words, } from '@fusion.dev/helix-core';
7
+ import { customElement, formatWords, HelixBeforePanelResizeEvent, HelixBeforeSplitEvent, HelixElement, HelixPanelResizeEvent, HelixSplitEvent, inlineStep, requestChange, requestValue, words, } from '@fusion.dev/helix-core';
8
8
  import '../../splitter/index.js';
9
- import { html } from 'lit';
9
+ import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { styles } from './sandbox-root.css.js';
12
12
  const DEFAULT_SPLIT = 50;
@@ -14,12 +14,21 @@ const DEFAULT_PANEL = 160;
14
14
  const PANEL_MAX = 0.8;
15
15
  const PANE_KEYS = { css: 'styles', html: 'source', js: 'script' };
16
16
  const isPaneName = (name) => typeof name === 'string' && Object.hasOwn(PANE_KEYS, name);
17
+ const nameOf = (pane) => {
18
+ const { name } = pane;
19
+ return typeof name === 'string' ? name : (pane.getAttribute('name') ?? '');
20
+ };
21
+ const fileOf = (pane) => {
22
+ const { label } = pane;
23
+ return (typeof label === 'string' && label) || pane.getAttribute('label') || nameOf(pane);
24
+ };
17
25
  let HelixSandbox = class HelixSandbox extends HelixElement {
18
26
  constructor() {
19
27
  super(...arguments);
20
28
  this.cornerLabel = 'Resize panes and panel';
21
29
  this.defaultPanelSize = DEFAULT_PANEL;
22
30
  this.defaultSplit = DEFAULT_SPLIT;
31
+ this.filesLabel = 'Files';
23
32
  this.lead = 'source';
24
33
  this.panelCollapsedSize = 0;
25
34
  this.panelLabel = 'Resize panel';
@@ -27,6 +36,29 @@ let HelixSandbox = class HelixSandbox extends HelixElement {
27
36
  this.separatorLabel = '{pane, select, preview {Resize preview pane} other {Resize source pane}}';
28
37
  this.split = DEFAULT_SPLIT;
29
38
  this.hasPanel = false;
39
+ this.files = [];
40
+ this.hid = new Set();
41
+ this.handleFileKey = (event) => {
42
+ const files = this.files;
43
+ const at = files.indexOf(this.shownFile);
44
+ const step = inlineStep(this, event.key);
45
+ const to = event.key === 'Home'
46
+ ? 0
47
+ : event.key === 'End'
48
+ ? files.length - 1
49
+ : step === 0
50
+ ? -1
51
+ : (at + step + files.length) % files.length;
52
+ const pane = files[to];
53
+ if (!pane) {
54
+ return;
55
+ }
56
+ event.preventDefault();
57
+ this.chooseFile(pane);
58
+ void this.updateComplete.then(() => {
59
+ this.renderRoot.querySelector(`[part="file"][aria-selected="true"]`)?.focus();
60
+ });
61
+ };
30
62
  this.bodyHeight = 0;
31
63
  this.watch = typeof ResizeObserver === 'undefined'
32
64
  ? undefined
@@ -50,6 +82,7 @@ let HelixSandbox = class HelixSandbox extends HelixElement {
50
82
  });
51
83
  };
52
84
  this.seed = () => {
85
+ this.readFiles();
53
86
  const slot = this.renderRoot.querySelector('slot[name="source"]');
54
87
  for (const pane of slot?.assignedElements({ flatten: true }) ?? []) {
55
88
  const { name, value } = pane;
@@ -117,6 +150,53 @@ let HelixSandbox = class HelixSandbox extends HelixElement {
117
150
  };
118
151
  }
119
152
  static { this.words = 'helix-sandbox'; }
153
+ get shownFile() {
154
+ return this.files.find((pane) => nameOf(pane) === this.file) ?? this.files[0];
155
+ }
156
+ readFiles() {
157
+ const slot = this.renderRoot.querySelector('slot[name="source"]');
158
+ const panes = slot?.assignedElements({ flatten: true }) ?? [];
159
+ const files = panes.length > 1 ? panes : [];
160
+ if (files.length !== this.files.length || files.some((pane, at) => pane !== this.files[at])) {
161
+ this.files = files;
162
+ }
163
+ }
164
+ chooseFile(pane) {
165
+ if (pane !== this.shownFile) {
166
+ requestValue(this, nameOf(pane), (name) => {
167
+ this.file = name;
168
+ });
169
+ }
170
+ }
171
+ renderFiles() {
172
+ if (this.files.length === 0) {
173
+ return nothing;
174
+ }
175
+ const shown = this.shownFile;
176
+ return html `<div part="files" role="tablist" aria-label=${this.filesLabel} @keydown=${this.handleFileKey}>
177
+ ${this.files.map((pane, at) => {
178
+ const selected = pane === shown;
179
+ return html `<button part="file" id=${`file-${at}`} role="tab" type="button"
180
+ aria-controls="files-panel" aria-selected=${selected ? 'true' : 'false'}
181
+ tabindex=${selected ? '0' : '-1'} @click=${() => this.chooseFile(pane)}>${fileOf(pane)}</button>`;
182
+ })}
183
+ </div>`;
184
+ }
185
+ showFile() {
186
+ const shown = this.shownFile;
187
+ for (const pane of this.files) {
188
+ if (pane !== shown && !pane.hasAttribute('hidden')) {
189
+ pane.setAttribute('hidden', '');
190
+ this.hid.add(pane);
191
+ }
192
+ }
193
+ for (const pane of this.hid) {
194
+ if (pane === shown || !this.files.includes(pane)) {
195
+ pane.removeAttribute('hidden');
196
+ this.hid.delete(pane);
197
+ }
198
+ }
199
+ }
120
200
  shareOf(size) {
121
201
  if (this.bodyHeight <= 0) {
122
202
  return 0;
@@ -293,6 +373,23 @@ let HelixSandbox = class HelixSandbox extends HelixElement {
293
373
  this.removeEventListener('helix-before-edit', this.handleSourceChange);
294
374
  this.watch?.disconnect();
295
375
  }
376
+ updated(changed) {
377
+ super.updated(changed);
378
+ if (changed.has('files') || changed.has('file')) {
379
+ this.showFile();
380
+ }
381
+ }
382
+ renderPane(name, at) {
383
+ const slot = html `<slot name=${name} @slotchange=${this.seed}></slot>`;
384
+ if (name === 'preview' || this.files.length === 0) {
385
+ return html `<div part="pane" data-pane=${name} slot=${at}>${slot}</div>`;
386
+ }
387
+ const tab = `file-${this.files.indexOf(this.shownFile)}`;
388
+ return html `<div part="pane" data-pane=${name} data-files slot=${at}>
389
+ ${this.renderFiles()}
390
+ <div id="files-panel" role="tabpanel" aria-labelledby=${tab}>${slot}</div>
391
+ </div>`;
392
+ }
296
393
  render() {
297
394
  const vertical = this.axis === 'vertical';
298
395
  const first = this.lead === 'preview' ? 'preview' : 'source';
@@ -311,8 +408,8 @@ let HelixSandbox = class HelixSandbox extends HelixElement {
311
408
  label=${label} orientation=${vertical ? 'vertical' : 'horizontal'} .position=${split}
312
409
  @helix-before-collapse=${this.handleCollapse} @helix-before-resize=${this.handlePanesChange}
313
410
  @keydown=${this.handleDividerKey}>
314
- <div part="pane" data-pane=${first} slot="start"><slot name=${first} @slotchange=${this.seed}></slot></div>
315
- <div part="pane" data-pane=${second} slot="end"><slot name=${second} @slotchange=${this.seed}></slot></div>
411
+ ${this.renderPane(first, 'start')}
412
+ ${this.renderPane(second, 'end')}
316
413
  </helix-splitter>
317
414
  <div part="panel" slot="end"><slot name="panel" @slotchange=${this.readPanel}></slot></div>
318
415
  </helix-splitter>
@@ -330,6 +427,13 @@ __decorate([
330
427
  __decorate([
331
428
  property({ attribute: 'default-split', type: Number })
332
429
  ], HelixSandbox.prototype, "defaultSplit", void 0);
430
+ __decorate([
431
+ property()
432
+ ], HelixSandbox.prototype, "file", void 0);
433
+ __decorate([
434
+ words,
435
+ property({ attribute: 'files-label' })
436
+ ], HelixSandbox.prototype, "filesLabel", void 0);
333
437
  __decorate([
334
438
  property()
335
439
  ], HelixSandbox.prototype, "lead", void 0);
@@ -356,6 +460,9 @@ __decorate([
356
460
  __decorate([
357
461
  state()
358
462
  ], HelixSandbox.prototype, "hasPanel", void 0);
463
+ __decorate([
464
+ state()
465
+ ], HelixSandbox.prototype, "files", void 0);
359
466
  HelixSandbox = __decorate([
360
467
  customElement('helix-sandbox')
361
468
  ], HelixSandbox);
@@ -63,6 +63,11 @@ export declare class HelixSandboxSource extends HelixElement {
63
63
  * The field's description, read by screen readers while Tab indents.
64
64
  */
65
65
  keysDescription: string;
66
+ /**
67
+ * The pane's file name, such as `index.html`, on its tab where a sandbox
68
+ * holds several panes. Empty puts the pane's `name` on the tab.
69
+ */
70
+ label: string;
66
71
  /**
67
72
  * Which source this pane holds. Every `helix-before-edit` event includes it.
68
73
  *
@@ -15,6 +15,7 @@ let HelixSandboxSource = class HelixSandboxSource extends HelixElement {
15
15
  super(...arguments);
16
16
  this.fieldLabel = 'Example source';
17
17
  this.keysDescription = 'Tab inserts two spaces. To leave, press Escape and then Tab, or press Shift+Tab to go back.';
18
+ this.label = '';
18
19
  this.name = 'html';
19
20
  this.tabIndents = true;
20
21
  this.value = '';
@@ -115,6 +116,9 @@ __decorate([
115
116
  words,
116
117
  property({ attribute: 'keys-description' })
117
118
  ], HelixSandboxSource.prototype, "keysDescription", void 0);
119
+ __decorate([
120
+ property()
121
+ ], HelixSandboxSource.prototype, "label", void 0);
118
122
  __decorate([
119
123
  property()
120
124
  ], HelixSandboxSource.prototype, "name", void 0);
@@ -10,5 +10,7 @@ color-mix(in oklab, var(--helix-color-foreground) 8%, transparent)
10
10
  );}[part~="item"]:focus-visible {outline: var(--helix-size-focus-ring) solid var(--helix-color-focus-ring);outline-offset: calc(var(--helix-size-focus-offset) * -1);}[part~="item"][aria-current="true"] {background-color: var(
11
11
  --video-playlist-item-current-background-color,
12
12
  color-mix(in oklab, var(--helix-color-foreground) 6%, transparent)
13
- );color: var(--helix-color-foreground);}[part~="item"][aria-current="true"]::before {background-color: var(--video-playlist-item-current-mark-color, var(--helix-color-primary));border-radius: var(--helix-radii-full);content: "";inline-size: var(--helix-border-thick);inset-block: var(--helix-space-2xs);inset-inline-start: var(--helix-space-2xs);position: absolute;}:host([disabled]) [part~="item"] {cursor: not-allowed;opacity: 0.5;}[part="media"] {aspect-ratio: var(--video-playlist-media-aspect-ratio, 16 / 9);border-radius: var(--video-playlist-media-radius, var(--helix-radii-sm));flex: 0 0 auto;inline-size: var(--video-playlist-media-size, 5rem);overflow: hidden;}[part="media"] ::slotted(*),
13
+ );color: var(--helix-color-foreground);}[part~="item"][aria-current="true"]::before {background-color: var(--video-playlist-item-current-mark-color, var(--helix-color-primary));border-radius: var(--helix-radii-full);content: "";inline-size: var(--helix-border-thick);inset-block: var(--helix-space-2xs);inset-inline-start: var(--helix-space-2xs);position: absolute;}:host([disabled]) [part~="item"] {cursor: not-allowed;opacity: 0.5;}:host(:state(dragging)) {opacity: 0.5;}:host(:state(drop-before)),
14
+ :host(:state(drop-after)) {position: relative;}:host(:state(drop-before))::before,
15
+ :host(:state(drop-after))::before {background-color: var(--helix-color-primary);block-size: var(--helix-border-thick);content: "";inset-inline: 0;pointer-events: none;position: absolute;z-index: 1;}:host(:state(drop-before))::before {inset-block-start: 0;}:host(:state(drop-after))::before {inset-block-end: 0;}[part="media"] {aspect-ratio: var(--video-playlist-media-aspect-ratio, 16 / 9);border-radius: var(--video-playlist-media-radius, var(--helix-radii-sm));flex: 0 0 auto;inline-size: var(--video-playlist-media-size, 5rem);overflow: hidden;}[part="media"] ::slotted(*),
14
16
  [part="thumbnail"] {block-size: 100%;display: block;inline-size: 100%;object-fit: cover;}[part="label"] {color: var(--video-playlist-item-color, var(--helix-color-foreground));display: flex;flex-direction: column;font-family: var(--helix-font-sans);font-size: var(--helix-text-sm);font-weight: var(--helix-weight-medium);gap: var(--helix-space-2xs);line-height: var(--helix-leading-tight);min-inline-size: 0;}[part="label"] ::slotted([slot="meta"]) {color: var(--video-playlist-item-meta-color, var(--helix-color-foreground-subtle));font-size: var(--helix-text-xs);font-weight: var(--helix-weight-regular);}`);