@design.estate/dees-catalog 12.2.0 → 12.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist_bundle/bundle.js +120 -18
  2. package/dist_bundle/bundle.js.map +1 -1
  3. package/dist_ts_web/00_commitinfo_data.js +1 -1
  4. package/dist_ts_web/elements/00group-appui/dees-appui-mainmenu/dees-appui-mainmenu.d.ts +5 -0
  5. package/dist_ts_web/elements/00group-button/dees-button/dees-button.d.ts +18 -0
  6. package/dist_ts_web/elements/00group-button/dees-button/dees-button.demo.js +10 -1
  7. package/dist_ts_web/elements/00group-button/dees-button/dees-button.js +103 -5
  8. package/dist_ts_web/elements/00group-form/dees-form/dees-form.d.ts +2 -1
  9. package/dist_ts_web/elements/00group-form/dees-form/dees-form.js +2 -1
  10. package/dist_ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.d.ts +12 -2
  11. package/dist_ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js +11 -1
  12. package/dist_ts_web/elements/00group-simple/dees-simple-appdash/dees-simple-appdash.js +7 -2
  13. package/dist_ts_web/elements/00group-workspace/dees-workspace/dees-workspace.js +3 -3
  14. package/dist_ts_web/elements/00group-workspace/dees-workspace/styles.js +10 -2
  15. package/package.json +1 -1
  16. package/readme.md +27 -1
  17. package/ts_web/00_commitinfo_data.ts +1 -1
  18. package/ts_web/elements/00group-appui/dees-appui-mainmenu/dees-appui-mainmenu.ts +6 -0
  19. package/ts_web/elements/00group-button/dees-button/dees-button.demo.ts +10 -0
  20. package/ts_web/elements/00group-button/dees-button/dees-button.ts +96 -3
  21. package/ts_web/elements/00group-form/dees-form/dees-form.ts +2 -1
  22. package/ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.ts +11 -1
  23. package/ts_web/elements/00group-simple/dees-simple-appdash/dees-simple-appdash.ts +6 -1
  24. package/ts_web/elements/00group-workspace/dees-workspace/dees-workspace.ts +2 -2
  25. package/ts_web/elements/00group-workspace/dees-workspace/styles.ts +9 -1
package/readme.md CHANGED
@@ -20,7 +20,7 @@ switcher**, **view-toggle**, and **single-selection toggle** all lead to
20
20
  | Switch between a few views, modes, or time periods; segmented control | [`dees-input-multitoggle`](#deesinputmultitoggle) | One selected value in a shared track. Use keyed options when labels differ from stored values. |
21
21
  | Choose one form answer from visible radio options | [`dees-input-radiogroup`](#deesinputradiogroup) | Radio indicators, required validation, optional payload values. |
22
22
  | Turn one setting on or off | [`dees-input-toggle`](#deesinputtoggle), [`dees-input-checkbox`](#deesinputcheckbox) | Boolean setting or checkable form choice. Multitoggle's boolean mode is useful when both alternatives need explicit labels. |
23
- | Toggle an independent toolbar option | [`dees-input-toggle`](#deesinputtoggle) | Boolean state. A set of independent options is not a single-selection multitoggle. |
23
+ | Toggle an independent toolbar option | [`dees-button`](#deesbutton) with `pressed`, [`dees-input-toggle`](#deesinputtoggle) | A toolbar or app-bar command that stays on uses a button with `pressed`; a labelled setting uses the switch. A set of independent options is not a single-selection multitoggle. |
24
24
  | Choose from a longer menu of values | [`dees-input-dropdown`](#deesinputdropdown) | Searchable single selection; `enableSearch` defaults to true. |
25
25
  | Group related commands | [`dees-button-group`](#deesbuttongroup), [`dees-actionbar`](#deesactionbar) | Layout for actions. A button group does not own a selected value. |
26
26
  | Navigate open content panels, with optional close controls | [`dees-appui-tabs`](#deesappuitabs) | Tab selection, close actions and keyboard focus. Use multitoggle for changing a display mode within a panel. |
@@ -299,10 +299,36 @@ button.text = 'Click me';
299
299
  status="pending" // Options: normal, pending, success, error
300
300
  disabled={false} // Optional: disables the button
301
301
  full-width // Optional: stretches the button face to the host width
302
+ .pressed=${true} // Optional: makes this button a toggle — true (on), false (off), unset (not a toggle)
302
303
  @click=${handleClick}
303
304
  >Click me</dees-button>
304
305
  ```
305
306
 
307
+ `pressed` turns a button into a toggle and is what assistive technology announces: the face
308
+ carries `aria-pressed="true"`/`"false"` while `pressed` is set, and carries no `aria-pressed`
309
+ at all while it is unset, so a plain button is never announced as an unpressed toggle. Use it
310
+ for toolbar options — a DevTools or sidebar button in an app bar — rather than expressing the
311
+ state through `type`, which decides the button's role colour. The on face is an accent wash,
312
+ ring and label layered over whatever `type` paints (a filled `accent`/`destructive` face
313
+ recesses behind a scrim instead), it stays on when the pointer leaves, and a status face keeps
314
+ its own surface while it shows; `type="link"` has no surface at all, so its on state is only a
315
+ permanent underline — prefer `ghost` or `outline` for a link-styled toggle. At `size="icon"`
316
+ the accessible name comes from `text`, so an icon-only toggle must still set it.
317
+
318
+ ```typescript
319
+ <dees-button
320
+ size="icon" icon="lucide:terminal" text="DevTools"
321
+ .pressed=${this.devtoolsOpen}
322
+ @clicked=${() => { this.devtoolsOpen = !this.devtoolsOpen; }}
323
+ ></dees-button>
324
+ ```
325
+
326
+ The button is stateless: a click never flips `pressed`, so the consumer owns the state. Only
327
+ the on state reflects to the host, which makes `dees-button[pressed]` and `:host([pressed])`
328
+ select a toggle that is on; the attribute form (`<dees-button pressed>`) therefore means on —
329
+ any value, including `pressed="false"`, means on, as for every HTML boolean attribute — and off
330
+ has to be set through the property.
331
+
306
332
  The button is keyboard operable: its face carries `role="button"` and `tabindex`, Enter and
307
333
  Space activate it (dispatching a real `click`, so both `@click` and `@clicked` fire), a
308
334
  disabled button leaves the tab order, and keyboard focus draws a `:focus-visible` ring.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: "@design.estate/dees-catalog",
6
- version: "12.2.0",
6
+ version: "12.4.0",
7
7
  description: "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript."
8
8
  }
@@ -428,3 +428,9 @@ export class DeesAppuiMainmenu extends DeesElement {
428
428
  }));
429
429
  }
430
430
  }
431
+
432
+ declare global {
433
+ interface HTMLElementTagNameMap {
434
+ 'dees-appui-mainmenu': DeesAppuiMainmenu;
435
+ }
436
+ }
@@ -5,6 +5,13 @@ import type { DeesButton } from './dees-button.js';
5
5
 
6
6
  const clicked = (event: Event) => showDemoResult(event.currentTarget as Element, `${(event.target as DeesButton).text || 'Button'} selected.`);
7
7
 
8
+ /** The button never flips `pressed` itself — the consumer owns the state, as it does here. */
9
+ const toggled = (event: Event) => {
10
+ const button = event.target as DeesButton;
11
+ button.pressed = !button.pressed;
12
+ showDemoResult(button, `${button.text}: ${button.pressed ? 'on' : 'off'}`);
13
+ };
14
+
8
15
  export const demoFunc = () => demoPage('Button', 'A shared shape and rhythm for actions, with a clear hierarchy of intent.', html`
9
16
  ${demoSection('Intent', 'Accent marks the primary action. Quieter treatments keep supporting actions available.', html`
10
17
  <div class="dees-demo-row" @clicked=${clicked}>${(['default', 'accent', 'secondary', 'destructive', 'outline', 'ghost', 'link'] as const).map((type) => html`<dees-button .type=${type} .text=${type}></dees-button>`)}</div>
@@ -18,6 +25,9 @@ export const demoFunc = () => demoPage('Button', 'A shared shape and rhythm for
18
25
  ${demoOutput()}${demoCode('<dees-button size="icon" text="Settings" icon="lucide:settings"></dees-button>')}`)}
19
26
  ${demoSection('Status', 'Pending, success, and error faces are inert. Disabled actions stay visible.', html`
20
27
  <div class="dees-demo-row">${(['normal', 'pending', 'success', 'error'] as const).map((status) => html`<dees-button .status=${status} .text=${status}></dees-button>`)}<dees-button disabled text="Unavailable"></dees-button></div>`)}
28
+ ${demoSection('Toggle', 'Setting pressed makes a button a toggle and announces it as one. The on face layers over the type face and stays on when the pointer leaves. A button without pressed stays a plain action.', html`
29
+ <div class="dees-demo-row" @clicked=${toggled}><dees-button type="ghost" size="icon" icon="lucide:terminal" text="DevTools" .pressed=${true}></dees-button><dees-button type="ghost" icon="lucide:panel-left" text="Sidebar" .pressed=${false}></dees-button><dees-button type="outline" text="Wrap lines" .pressed=${true}></dees-button><dees-button text="Live preview" .pressed=${false}></dees-button><dees-button type="accent" text="Recording" .pressed=${true}></dees-button></div>
30
+ ${demoOutput()}${demoCode('<dees-button size="icon" icon="lucide:terminal" text="DevTools" .pressed=${this.devtoolsOpen} @clicked=${() => this.devtoolsOpen = !this.devtoolsOpen}></dees-button>')}`)}
21
31
  ${demoSection('Event data', 'Activation uses the clicked event for pointer and keyboard input.', html`
22
32
  <div class="dees-demo-row"><dees-button text="Inspect event" .eventDetailData=${'workspace-preview'} @clicked=${(event: CustomEvent) => showDemoResult(event.currentTarget as Element, event.detail)}></dees-button></div>${demoOutput()}`)}
23
33
  ${demoSection('In a form', 'The same button proportions carry through to form submission.', workspaceForm())}
@@ -63,6 +63,29 @@ export class DeesButton extends DeesElement {
63
63
  })
64
64
  accessor status: 'normal' | 'pending' | 'success' | 'error' = 'normal';
65
65
 
66
+ /**
67
+ * Toggle state, reported to assistive technology as `aria-pressed`.
68
+ *
69
+ * Tri-state by design: `undefined` (the default) means this button is not a toggle at all
70
+ * and nothing is announced, `false` a toggle that is off, `true` a toggle that is on. A
71
+ * plain boolean defaulting to `false` would announce every button in the catalog as an
72
+ * unpressed toggle.
73
+ *
74
+ * The element stays stateless — a click never flips `pressed`. The consumer owns the state
75
+ * and sets it from its `clicked` handler.
76
+ *
77
+ * Only the on state reflects to the host, so `:host([pressed])` and `dees-button[pressed]`
78
+ * select a toggle that is on. Off and not-a-toggle are indistinguishable as attributes, so
79
+ * the attribute form (`<dees-button pressed>`) can only express "on" — any value, including
80
+ * `pressed="false"`, means on, as for every HTML boolean attribute. The off state has to be
81
+ * set through the property.
82
+ */
83
+ @property({
84
+ type: Boolean,
85
+ reflect: true
86
+ })
87
+ accessor pressed: boolean | undefined = undefined;
88
+
66
89
  /**
67
90
  * Corner treatment. 'squircle' (default) is the standard rounded rect for
68
91
  * controls that align with fields and other rectangular siblings; 'pill'
@@ -139,6 +162,12 @@ export class DeesButton extends DeesElement {
139
162
  --button-success-fg: var(--dees-color-text-success);
140
163
  --button-error-bg: color-mix(in srgb, var(--dees-color-accent-error) 20%, transparent);
141
164
  --button-error-fg: var(--dees-color-text-error);
165
+ /* toggle-on face: the shared selected-surface wash, an accent ring and the accent
166
+ label colour. All three inputs are theme-aware tokens, so this face needs no
167
+ bright/dark split of its own. */
168
+ --button-pressed-wash: var(--dees-color-nav-selected);
169
+ --button-pressed-ring: color-mix(in srgb, var(--dees-color-accent-primary) 50%, transparent);
170
+ --button-pressed-fg: var(--dees-color-link);
142
171
  }
143
172
  :host([gobright]) {
144
173
  --button-pending-bg: var(--dees-color-accent-primary);
@@ -158,11 +187,15 @@ export class DeesButton extends DeesElement {
158
187
  }
159
188
 
160
189
  /* A full-width button is sized by its container, so its label has to yield rather
161
- than spill out of the face when the container is narrower than the text. */
190
+ than spill out of the face when the container is narrower than the text. It clips with
191
+ a 1px margin rather than hiding: a button face is no scroll container, and the
192
+ toggle-on overlay sits 1px outside the padding box so that its ring lands on the
193
+ type's own border edge — overflow: hidden would cut exactly that pixel away. */
162
194
  :host([full-width]) .button {
163
195
  width: 100%;
164
196
  min-width: 0;
165
- overflow: hidden;
197
+ overflow: clip;
198
+ overflow-clip-margin: 1px;
166
199
  }
167
200
 
168
201
  :host([full-width]) .textbox {
@@ -366,6 +399,59 @@ export class DeesButton extends DeesElement {
366
399
  text-decoration-color: currentColor;
367
400
  }
368
401
 
402
+ /* Toggle-on face (the pressed property).
403
+ A toggle has to stay visibly on once the pointer leaves it, so this cannot be the
404
+ hover face; it layers on top of whatever the type paints instead of replacing it. The
405
+ wash rides on an overlay rather than on the face's own background, because every
406
+ type, hover and active rule sets the background shorthand and would reset a layer
407
+ declared on the face itself. isolation scopes the overlay's negative layer to this
408
+ face, which paints it above the type's background and below the label and icon. */
409
+ .button.pressed {
410
+ isolation: isolate;
411
+ }
412
+
413
+ .button.pressed::after {
414
+ content: '';
415
+ position: absolute;
416
+ /* the face's border box, so the ring lands on the type's own border edge */
417
+ inset: -1px;
418
+ z-index: -1;
419
+ border-radius: inherit;
420
+ corner-shape: inherit;
421
+ background: var(--button-pressed-wash);
422
+ box-shadow: inset 0 0 0 1px var(--button-pressed-ring);
423
+ pointer-events: none;
424
+ }
425
+
426
+ /* A filled face already carries the accent as its surface, where an accent wash cannot
427
+ read, so it recesses behind a scrim with a light inner ring instead. Both faces are
428
+ dark in either theme, which is why these two values do not follow the theme. */
429
+ .button.accent,
430
+ .button.destructive {
431
+ --button-pressed-wash: color-mix(in srgb, black 24%, transparent);
432
+ --button-pressed-ring: color-mix(in srgb, white 40%, transparent);
433
+ }
434
+
435
+ /* At size="icon" there is no label, so the foreground carries the state as well. Filled
436
+ faces keep their on-accent label, and a status face keeps its own. */
437
+ .button.pressed:not(.accent):not(.destructive):not(.pending):not(.success):not(.error) {
438
+ color: var(--button-pressed-fg);
439
+ }
440
+
441
+ /* A status face owns the whole surface for as long as it shows, and type="link" has no
442
+ surface to wash at all (no border, no padding) — its on state is the hover underline
443
+ made permanent. aria-pressed reports the state in every one of these cases. */
444
+ .button.pressed.pending::after,
445
+ .button.pressed.success::after,
446
+ .button.pressed.error::after,
447
+ .button.pressed.link::after {
448
+ content: none;
449
+ }
450
+
451
+ .button.pressed.link {
452
+ text-decoration-color: currentColor;
453
+ }
454
+
369
455
  /* Status states */
370
456
  .button.pending,
371
457
  .button.success,
@@ -585,10 +671,16 @@ export class DeesButton extends DeesElement {
585
671
  // size-icon additionally suppresses text to keep its square geometry.
586
672
  const showText = Boolean(effectiveText) && !(actualSize === 'icon' && effectiveIcon);
587
673
 
674
+ // A button that is not a toggle must carry no `aria-pressed` attribute at all, and an
675
+ // attribute binding cannot remove one — lit only removes on its `nothing` sentinel, which
676
+ // `@design.estate/dees-element` does not re-export. The state therefore goes through the
677
+ // reflected ARIA property, the same channel `.ariaLabel` uses below: `null` removes it.
588
678
  return html`
589
679
  <div
590
680
  part="button"
591
- class="button ${this.isHidden ? 'hidden' : ''} ${actualType} size-${actualSize} shape-${this.shape} ${this.status} ${this.disabled
681
+ class="button ${this.isHidden ? 'hidden' : ''} ${actualType} size-${actualSize} shape-${this.shape} ${this.status} ${this.pressed
682
+ ? 'pressed'
683
+ : ''} ${this.disabled
592
684
  ? 'disabled'
593
685
  : ''}"
594
686
  role="button"
@@ -596,6 +688,7 @@ export class DeesButton extends DeesElement {
596
688
  aria-disabled=${this.disabled ? 'true' : 'false'}
597
689
  aria-busy=${this.status === 'pending' ? 'true' : 'false'}
598
690
  .ariaLabel=${showText ? null : effectiveText || null}
691
+ .ariaPressed=${this.pressed === undefined ? null : String(this.pressed)}
599
692
  @click="${this.dispatchClick}"
600
693
  @keydown=${this.handleKeydown}
601
694
  @keyup=${this.handleKeyup}
@@ -157,7 +157,8 @@ export class DeesForm extends DeesElement {
157
157
  public static demoGroups = ['Form'];
158
158
 
159
159
  public name: string = 'myform';
160
- public changeSubject = new domtools.plugins.smartrx.rxjs.Subject();
160
+ /** emits the collected form data on every child change; the only producer is `collectFormData()` */
161
+ public changeSubject = new domtools.plugins.smartrx.rxjs.Subject<TFormData>();
161
162
  public readyDeferred = domtools.plugins.smartpromise.defer();
162
163
 
163
164
  /**
@@ -85,7 +85,17 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
85
85
  public blockOperations: WysiwygBlockOperations;
86
86
  private inputHandler: WysiwygInputHandler;
87
87
  private keyboardHandler: WysiwygKeyboardHandler;
88
- private dragDropHandler: WysiwygDragDropHandler;
88
+ /**
89
+ * Block drag and drop, public alongside `blockOperations` because the drag *interaction* has
90
+ * no other seam: `dragState`, the drop indicator and the global drag-over listener all live in
91
+ * this handler, and a native HTML5 drag sequence cannot be synthesised from script, so a host
92
+ * or a test that exercises dragging has to drive `handleDragStart` / `handleDragEnd` itself.
93
+ * Reordering a block programmatically needs none of that: `blockOperations.moveBlock(blockId,
94
+ * targetIndex)` is the public path for that, on the public `blocks` accessor. The remaining
95
+ * handlers stay private because everything they own is reachable through ordinary input and
96
+ * keyboard events.
97
+ */
98
+ public dragDropHandler: WysiwygDragDropHandler;
89
99
  private history: WysiwygHistory;
90
100
 
91
101
  public static styles = [
@@ -109,7 +109,12 @@ export class DeesSimpleAppDash extends DeesElement {
109
109
  .controlbar { grid-column: 1 / -1; display: flex; align-items: center; justify-content: flex-end; border-top: 1px solid var(--dees-color-border-subtle); background: var(--dees-color-bg-secondary); font-size: 11px; color: var(--dees-color-text-secondary); }
110
110
  .control { display: flex; align-items: center; gap: 6px; padding: 0 12px; height: 100%; }
111
111
  .status-terminal { border-left: 1px solid var(--dees-color-border-subtle); }
112
- .status-terminal[aria-pressed='true'] { color: var(--dees-color-accent-primary); background: var(--dees-color-nav-selected); }
112
+ /* The on face is the catalog's toggle-on face: the shared selected surface plus the accent
113
+ *text* colour, not --dees-color-accent-primary, which only reaches 3.6:1 as a label. The
114
+ hovered rule exists because the shared button:hover rule below outranks a plain
115
+ [aria-pressed] rule and would wash the on state away under the pointer. */
116
+ .status-terminal[aria-pressed='true'] { color: var(--dees-color-link); background: var(--dees-color-nav-selected); }
117
+ .status-terminal[aria-pressed='true']:hover { color: var(--dees-color-link); background: color-mix(in srgb, var(--dees-color-nav-selected) 88%, var(--dees-color-text-primary)); }
113
118
  .collapsed .sidebar-header { justify-content: center; padding: 0; }
114
119
  .collapsed .sidebar-header > dees-icon, .collapsed .appName, .collapsed .section-label, .collapsed .viewTab span, .collapsed .viewTab .chevron, .collapsed .action span { display: none; }
115
120
  .collapsed .collapseToggle { margin: 0; width: 40px; height: 38px; }
@@ -281,8 +281,8 @@ export class DeesWorkspace extends DeesElement {
281
281
  <span class="save-state" role="status">${this.isInitializing ? 'Starting…' : this.initializationError ? 'Connection failed' : !ready ? 'Not connected' : modifiedCount ? `${modifiedCount} unsaved` : 'All changes saved'}</span>
282
282
  <div class="toolbar-actions">
283
283
  <button class="panel-action" type="button" aria-label="Save all files" title="Save all files" ?disabled=${!ready || !modifiedCount} @click=${this.saveAllFiles}><dees-icon icon="lucide:Save" iconSize="16"></dees-icon></button>
284
- ${this.showFileTree ? html`<button class="panel-action" type="button" aria-label="Toggle files" aria-controls="workspace-files" aria-expanded=${!this.isFileTreeCollapsed} aria-pressed=${!this.isFileTreeCollapsed} title="Toggle files" @click=${() => this.isFileTreeCollapsed = !this.isFileTreeCollapsed}><dees-icon icon="lucide:PanelLeft" iconSize="16"></dees-icon></button>` : ''}
285
- ${this.showTerminal ? html`<button class="panel-action" type="button" aria-label="Toggle terminal" aria-controls="workspace-terminal" aria-expanded=${!this.isTerminalCollapsed} aria-pressed=${!this.isTerminalCollapsed} title="Toggle terminal" @click=${this.toggleTerminal}><dees-icon icon="lucide:PanelBottom" iconSize="16"></dees-icon></button>` : ''}
284
+ ${this.showFileTree ? html`<button class="panel-action panel-toggle" type="button" aria-label="Toggle files" aria-controls="workspace-files" aria-expanded=${!this.isFileTreeCollapsed} title="Toggle files" @click=${() => this.isFileTreeCollapsed = !this.isFileTreeCollapsed}><dees-icon icon="lucide:PanelLeft" iconSize="16"></dees-icon></button>` : ''}
285
+ ${this.showTerminal ? html`<button class="panel-action panel-toggle" type="button" aria-label="Toggle terminal" aria-controls="workspace-terminal" aria-expanded=${!this.isTerminalCollapsed} title="Toggle terminal" @click=${this.toggleTerminal}><dees-icon icon="lucide:PanelBottom" iconSize="16"></dees-icon></button>` : ''}
286
286
  <button class="panel-action" type="button" aria-label="Editor options" aria-haspopup="menu" title="Editor options" @click=${this.showEditorMenu}><dees-icon icon="lucide:Ellipsis" iconSize="16"></dees-icon></button>
287
287
  </div>
288
288
  </header>
@@ -15,7 +15,15 @@ export const workspaceStyles = css`
15
15
  .toolbar-actions, .panel-header-actions { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
16
16
  .panel-action { width: 30px; height: 30px; display: grid; place-items: center; border-radius: var(--dees-radius-md); color: var(--dees-color-text-secondary); transition: color 140ms ease, background-color 140ms ease; }
17
17
  .panel-action:hover:not(:disabled) { color: var(--dees-color-text-primary); background: var(--dees-color-hover); }
18
- .panel-action[aria-pressed=true] { color: var(--dees-color-link); background: var(--dees-color-hover); }
18
+ /* A panel toggle announces one state, aria-expanded, and paints it with the shared selected
19
+ surface — the same wash and accent label dees-button gives its pressed state. It cannot
20
+ reuse the hover background: an expanded toggle would then be indistinguishable from a
21
+ hovered collapsed one. Both on-state rules repeat the hover rule's :not(:disabled), which
22
+ puts them at (0,4,0) and (0,5,0) against that rule's (0,3,0), so the on face outranks hover
23
+ on specificity instead of on source order, and the hovered-expanded rule keeps it under the
24
+ pointer. A disabled toggle falls back to the base face, the way it does for hover. */
25
+ .panel-action.panel-toggle[aria-expanded='true']:not(:disabled) { color: var(--dees-color-link); background: var(--dees-color-nav-selected); }
26
+ .panel-action.panel-toggle[aria-expanded='true']:not(:disabled):hover { color: var(--dees-color-link); background: color-mix(in srgb, var(--dees-color-nav-selected) 88%, var(--dees-color-text-primary)); }
19
27
  .workspace-container { display: flex; flex: 1; min-height: 0; min-width: 0; position: relative; }
20
28
  .editor-area { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; overflow: hidden; }
21
29
  .filetree-panel { position: relative; flex-shrink: 0; min-width: 0; overflow: hidden; background: var(--dees-color-bg-secondary); }