dalila 1.9.26 → 1.10.1

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/dist/cli/check.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { buildRouteTree, injectHtmlPathTemplates, findFile, findProjectRoot, extractParamKeys, } from './routes-generator.js';
4
+ const VOID_HTML_TAGS = new Set([
5
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
6
+ 'link', 'meta', 'param', 'source', 'track', 'wbr',
7
+ ]);
4
8
  // ============================================================================
5
9
  // TypeScript Compiler API (dynamic import)
6
10
  // ============================================================================
@@ -406,6 +410,161 @@ function extractRootIdentifiers(expr) {
406
410
  }
407
411
  return result;
408
412
  }
413
+ function findContainingRange(offset, ranges) {
414
+ for (const range of ranges) {
415
+ if (offset < range.start)
416
+ break;
417
+ if (offset >= range.start && offset < range.end)
418
+ return range;
419
+ }
420
+ return null;
421
+ }
422
+ function isInsideRange(offset, ranges) {
423
+ return findContainingRange(offset, ranges) !== null;
424
+ }
425
+ function hasRawMarkerAttribute(attrs) {
426
+ let i = 0;
427
+ while (i < attrs.length) {
428
+ while (i < attrs.length && /\s/.test(attrs[i]))
429
+ i++;
430
+ if (i >= attrs.length)
431
+ break;
432
+ if (attrs[i] === '/') {
433
+ i++;
434
+ continue;
435
+ }
436
+ const nameStart = i;
437
+ while (i < attrs.length && !/[\s=\/]/.test(attrs[i]))
438
+ i++;
439
+ if (i === nameStart) {
440
+ i++;
441
+ continue;
442
+ }
443
+ const name = attrs.slice(nameStart, i).toLowerCase();
444
+ if (name === 'd-pre' || name === 'd-raw')
445
+ return true;
446
+ while (i < attrs.length && /\s/.test(attrs[i]))
447
+ i++;
448
+ if (attrs[i] !== '=')
449
+ continue;
450
+ i++; // skip '='
451
+ while (i < attrs.length && /\s/.test(attrs[i]))
452
+ i++;
453
+ if (i >= attrs.length)
454
+ break;
455
+ const quote = attrs[i];
456
+ if (quote === '"' || quote === "'") {
457
+ i++;
458
+ while (i < attrs.length && attrs[i] !== quote)
459
+ i++;
460
+ if (i < attrs.length)
461
+ i++;
462
+ continue;
463
+ }
464
+ while (i < attrs.length && !/\s/.test(attrs[i]))
465
+ i++;
466
+ }
467
+ return false;
468
+ }
469
+ function extractRawTemplateRanges(html, options = {}) {
470
+ const ranges = [];
471
+ const stack = [];
472
+ const lowerHtml = html.toLowerCase();
473
+ let i = 0;
474
+ while (i < html.length) {
475
+ // script/style contents are raw text in HTML parsing; ignore any
476
+ // "<...>" sequences inside them to avoid false tag detection.
477
+ const scriptLike = stack[stack.length - 1];
478
+ if (scriptLike && (scriptLike.tagName === 'script' || scriptLike.tagName === 'style')) {
479
+ const closeNeedle = `</${scriptLike.tagName}`;
480
+ const closeIndex = lowerHtml.indexOf(closeNeedle, i);
481
+ if (closeIndex === -1)
482
+ break;
483
+ if (closeIndex > i) {
484
+ i = closeIndex;
485
+ continue;
486
+ }
487
+ }
488
+ if (html[i] !== '<') {
489
+ i++;
490
+ continue;
491
+ }
492
+ let j = i + 1;
493
+ let inString = null;
494
+ let escaped = false;
495
+ while (j < html.length) {
496
+ const ch = html[j];
497
+ if (inString) {
498
+ if (escaped) {
499
+ escaped = false;
500
+ }
501
+ else if (ch === '\\') {
502
+ escaped = true;
503
+ }
504
+ else if (ch === inString) {
505
+ inString = null;
506
+ }
507
+ j++;
508
+ continue;
509
+ }
510
+ if (ch === '"' || ch === "'") {
511
+ inString = ch;
512
+ j++;
513
+ continue;
514
+ }
515
+ if (ch === '>')
516
+ break;
517
+ j++;
518
+ }
519
+ if (j >= html.length)
520
+ break;
521
+ const fullTag = html.slice(i, j + 1);
522
+ const inner = html.slice(i + 1, j).trim();
523
+ const isClosingTag = inner.startsWith('/');
524
+ const normalized = isClosingTag ? inner.slice(1).trim() : inner;
525
+ const nameMatch = /^([a-zA-Z][\w:-]*)/.exec(normalized);
526
+ if (!nameMatch) {
527
+ i = j + 1;
528
+ continue;
529
+ }
530
+ const tagName = nameMatch[1].toLowerCase();
531
+ const attrs = normalized.slice(nameMatch[1].length);
532
+ if (isClosingTag) {
533
+ for (let stackIdx = stack.length - 1; stackIdx >= 0; stackIdx--) {
534
+ if (stack[stackIdx].tagName !== tagName)
535
+ continue;
536
+ const entry = stack.splice(stackIdx, 1)[0];
537
+ if (entry.isRaw) {
538
+ ranges.push({ start: entry.start, end: i + fullTag.length });
539
+ }
540
+ break;
541
+ }
542
+ i = j + 1;
543
+ continue;
544
+ }
545
+ const isRawTag = (options.includePreCode && (tagName === 'pre' || tagName === 'code'))
546
+ || tagName === 'd-pre'
547
+ || tagName === 'd-raw'
548
+ || hasRawMarkerAttribute(attrs);
549
+ const isSelfClosingTag = VOID_HTML_TAGS.has(tagName);
550
+ if (isSelfClosingTag) {
551
+ if (isRawTag) {
552
+ ranges.push({ start: i, end: i + fullTag.length });
553
+ }
554
+ i = j + 1;
555
+ continue;
556
+ }
557
+ stack.push({ tagName, isRaw: isRawTag, start: i });
558
+ i = j + 1;
559
+ }
560
+ for (const entry of stack) {
561
+ if (entry.isRaw) {
562
+ ranges.push({ start: entry.start, end: html.length });
563
+ }
564
+ }
565
+ ranges.sort((a, b) => a.start - b.start);
566
+ return ranges;
567
+ }
409
568
  /**
410
569
  * Extract all template identifiers from HTML content.
411
570
  *
@@ -413,7 +572,7 @@ function extractRootIdentifiers(expr) {
413
572
  * 1. Text interpolation `{expr}` — only outside HTML tags
414
573
  * 2. Context-binding directives `d-*="value"` — specific set
415
574
  */
416
- function extractTemplateIdentifiers(html) {
575
+ function extractTemplateIdentifiers(html, rawRanges = [], interpolationRawRanges = rawRanges) {
417
576
  const identifiers = [];
418
577
  const lines = html.split('\n');
419
578
  const lineOffsets = [];
@@ -439,6 +598,13 @@ function extractTemplateIdentifiers(html) {
439
598
  let tagQuote = null;
440
599
  let i = 0;
441
600
  while (i < html.length) {
601
+ if (!inTag) {
602
+ const rawRange = findContainingRange(i, interpolationRawRanges);
603
+ if (rawRange) {
604
+ i = rawRange.end;
605
+ continue;
606
+ }
607
+ }
442
608
  const ch = html[i];
443
609
  if (!inTag && ch === '<') {
444
610
  inTag = true;
@@ -524,6 +690,8 @@ function extractTemplateIdentifiers(html) {
524
690
  DIRECTIVE_RE.lastIndex = 0;
525
691
  let match;
526
692
  while ((match = DIRECTIVE_RE.exec(html))) {
693
+ if (isInsideRange(match.index, rawRanges))
694
+ continue;
527
695
  const directive = match[1];
528
696
  const value = match[3].trim();
529
697
  if (!value)
@@ -544,11 +712,16 @@ function extractTemplateIdentifiers(html) {
544
712
  }
545
713
  return identifiers;
546
714
  }
547
- function extractLoopRanges(html) {
715
+ function extractLoopRanges(html, rawRanges = []) {
548
716
  const ranges = [];
549
717
  const stack = [];
550
718
  let i = 0;
551
719
  while (i < html.length) {
720
+ const rawRange = findContainingRange(i, rawRanges);
721
+ if (rawRange) {
722
+ i = rawRange.end;
723
+ continue;
724
+ }
552
725
  if (html[i] !== '<') {
553
726
  i++;
554
727
  continue;
@@ -591,7 +764,7 @@ function extractLoopRanges(html) {
591
764
  i = j + 1;
592
765
  continue;
593
766
  }
594
- const tagName = nameMatch[1];
767
+ const tagName = nameMatch[1].toLowerCase();
595
768
  const attrs = normalized.slice(tagName.length);
596
769
  const isSelfClosingTag = !isClosingTag && /\/\s*$/.test(normalized);
597
770
  if (isClosingTag) {
@@ -699,8 +872,10 @@ const LOOP_FORCED_CHECK_SOURCES = new Set([
699
872
  'd-virtual-overscan',
700
873
  ]);
701
874
  function checkHtmlContent(html, filePath, validIdentifiers, diagnostics) {
702
- const ids = extractTemplateIdentifiers(html);
703
- const loopRanges = extractLoopRanges(html);
875
+ const rawRanges = extractRawTemplateRanges(html);
876
+ const interpolationRawRanges = extractRawTemplateRanges(html, { includePreCode: true });
877
+ const ids = extractTemplateIdentifiers(html, rawRanges, interpolationRawRanges);
878
+ const loopRanges = extractLoopRanges(html, rawRanges);
704
879
  for (const id of ids) {
705
880
  if (validIdentifiers.has(id.name))
706
881
  continue;
@@ -30,6 +30,12 @@ const TAG_ALIASES = {
30
30
  "d-table-wrapper": "div", "d-table": "table",
31
31
  "d-pagination": "nav", "d-page": "button", "d-page-ellipsis": "span",
32
32
  "d-breadcrumb": "ol", "d-breadcrumb-item": "li",
33
+ "d-nav-bar": "nav", "d-nav-brand": "a", "d-nav-links": "div", "d-nav-link": "a",
34
+ "d-side-bar": "aside", "d-side-bar-header": "div", "d-side-bar-sections": "div",
35
+ "d-side-bar-inner": "div", "d-side-bar-footer": "div", "d-side-bar-avatar": "span",
36
+ "d-side-bar-top": "div", "d-side-bar-brand": "a", "d-side-bar-logo": "span", "d-side-bar-meta": "div",
37
+ "d-side-bar-name": "span", "d-side-bar-subtitle": "span", "d-side-bar-toggle": "button",
38
+ "d-side-bar-section": "details", "d-side-bar-title": "summary", "d-side-bar-links": "div", "d-side-bar-link": "a",
33
39
  "d-separator": "hr", "d-separator-label": "div",
34
40
  "d-skeleton": "div", "d-loading": "div", "d-spinner": "span",
35
41
  "d-empty": "div", "d-empty-icon": "div", "d-empty-title": "h3", "d-empty-text": "p",
@@ -72,6 +78,12 @@ const TAG_DEFAULT_CLASS = {
72
78
  "d-table-wrapper": "d-table-wrapper", "d-table": "d-table",
73
79
  "d-pagination": "d-pagination", "d-page": "d-page", "d-page-ellipsis": "d-page-ellipsis",
74
80
  "d-breadcrumb": "d-breadcrumb", "d-breadcrumb-item": "d-breadcrumb-item",
81
+ "d-nav-bar": "d-nav-bar", "d-nav-brand": "d-nav-brand", "d-nav-links": "d-nav-links", "d-nav-link": "d-nav-link",
82
+ "d-side-bar": "d-side-bar", "d-side-bar-header": "d-side-bar-header", "d-side-bar-sections": "d-side-bar-sections",
83
+ "d-side-bar-inner": "d-side-bar-inner", "d-side-bar-footer": "d-side-bar-footer", "d-side-bar-avatar": "d-side-bar-avatar",
84
+ "d-side-bar-top": "d-side-bar-top", "d-side-bar-brand": "d-side-bar-brand", "d-side-bar-logo": "d-side-bar-logo", "d-side-bar-meta": "d-side-bar-meta",
85
+ "d-side-bar-name": "d-side-bar-name", "d-side-bar-subtitle": "d-side-bar-subtitle", "d-side-bar-toggle": "d-side-bar-toggle",
86
+ "d-side-bar-section": "d-side-bar-section", "d-side-bar-title": "d-side-bar-title", "d-side-bar-links": "d-side-bar-links", "d-side-bar-link": "d-side-bar-link",
75
87
  "d-separator": "d-separator", "d-separator-label": "d-separator-label",
76
88
  "d-skeleton": "d-skeleton", "d-loading": "d-loading", "d-spinner": "d-spinner",
77
89
  "d-empty": "d-empty", "d-empty-icon": "d-empty-icon", "d-empty-title": "d-empty-title", "d-empty-text": "d-empty-text",
@@ -125,6 +137,7 @@ const TAG_UPGRADE_RULES = {
125
137
  "d-calendar-nav": { defaultType: "button" },
126
138
  "d-calendar-day": { defaultType: "button" },
127
139
  "d-combobox-input": { defaultType: "text" },
140
+ "d-side-bar-toggle": { defaultType: "button" },
128
141
  };
129
142
  // ── Tag upgrade engine ──────────────────────────────────────────────
130
143
  function upgradeDalilaTags(root) {
@@ -15,7 +15,8 @@ export interface BindOptions {
15
15
  */
16
16
  events?: string[];
17
17
  /**
18
- * Selectors for elements where text interpolation should be skipped
18
+ * Selectors for elements where text interpolation should be skipped.
19
+ * `d-pre` / `d-raw` subtrees are always skipped regardless of this option.
19
20
  */
20
21
  rawTextSelectors?: string;
21
22
  /**
@@ -103,6 +103,73 @@ function isWarnAsErrorEnabledForActiveScope() {
103
103
  }
104
104
  return false;
105
105
  }
106
+ const RAW_BLOCK_SELECTORS = '[d-pre], [d-raw], d-pre, d-raw, [data-dalila-raw]';
107
+ const RAW_BLOCK_STYLE_ID = 'dalila-raw-block-default-styles';
108
+ function ensureRawBlockDefaultStyles() {
109
+ if (typeof document === 'undefined')
110
+ return;
111
+ if (document.getElementById(RAW_BLOCK_STYLE_ID))
112
+ return;
113
+ const style = document.createElement('style');
114
+ style.id = RAW_BLOCK_STYLE_ID;
115
+ style.textContent = `
116
+ [data-dalila-raw] { white-space: pre-wrap; }
117
+ d-pre[data-dalila-raw], d-raw[data-dalila-raw] { display: block; }
118
+ `.trim();
119
+ if (document.head) {
120
+ document.head.appendChild(style);
121
+ return;
122
+ }
123
+ document.documentElement?.appendChild(style);
124
+ }
125
+ function elementDepth(el) {
126
+ let depth = 0;
127
+ let cursor = el.parentElement;
128
+ while (cursor) {
129
+ depth += 1;
130
+ cursor = cursor.parentElement;
131
+ }
132
+ return depth;
133
+ }
134
+ function collectRawBlocks(root) {
135
+ const boundary = root.closest('[data-dalila-internal-bound]');
136
+ const matches = [];
137
+ if (root.matches(RAW_BLOCK_SELECTORS)) {
138
+ matches.push(root);
139
+ }
140
+ matches.push(...Array.from(root.querySelectorAll(RAW_BLOCK_SELECTORS)));
141
+ const inScope = matches.filter((el) => {
142
+ const bound = el.closest('[data-dalila-internal-bound]');
143
+ return bound === boundary;
144
+ });
145
+ inScope.sort((a, b) => elementDepth(a) - elementDepth(b));
146
+ const roots = [];
147
+ for (const el of inScope) {
148
+ if (roots.some((parent) => parent.contains(el)))
149
+ continue;
150
+ roots.push(el);
151
+ }
152
+ return roots;
153
+ }
154
+ function materializeRawBlocks(root) {
155
+ const rawBlocks = collectRawBlocks(root);
156
+ if (rawBlocks.length > 0) {
157
+ ensureRawBlockDefaultStyles();
158
+ }
159
+ for (const block of rawBlocks) {
160
+ if (block.hasAttribute('data-dalila-raw'))
161
+ continue;
162
+ const source = block.innerHTML;
163
+ block.setAttribute('data-dalila-raw', '');
164
+ block.textContent = source;
165
+ }
166
+ }
167
+ function isInsideRawBlock(el, boundary) {
168
+ const rawRoot = el.closest(RAW_BLOCK_SELECTORS);
169
+ if (!rawRoot)
170
+ return false;
171
+ return rawRoot.closest('[data-dalila-internal-bound]') === boundary;
172
+ }
106
173
  function createBindScanPlan(root) {
107
174
  const boundary = root.closest('[data-dalila-internal-bound]');
108
175
  const elements = [];
@@ -208,16 +275,19 @@ function resolveSelectorFromIndex(plan, selector) {
208
275
  return null;
209
276
  }
210
277
  function qsaFromPlan(plan, selector) {
278
+ const boundary = plan.root.closest('[data-dalila-internal-bound]');
211
279
  const cacheable = !selector.includes('[');
212
280
  if (cacheable) {
213
281
  const cached = plan.selectorCache.get(selector);
214
282
  if (cached) {
215
- return cached.filter(el => el === plan.root || plan.root.contains(el));
283
+ return cached.filter((el) => (el === plan.root || plan.root.contains(el))
284
+ && !isInsideRawBlock(el, boundary));
216
285
  }
217
286
  }
218
287
  const indexed = resolveSelectorFromIndex(plan, selector);
219
288
  const source = indexed ?? plan.elements.filter(el => el.matches(selector));
220
- const matches = source.filter(el => el === plan.root || plan.root.contains(el));
289
+ const matches = source.filter((el) => (el === plan.root || plan.root.contains(el))
290
+ && !isInsideRawBlock(el, boundary));
221
291
  if (cacheable)
222
292
  plan.selectorCache.set(selector, matches);
223
293
  return matches;
@@ -239,7 +309,9 @@ function qsaIncludingRoot(root, selector) {
239
309
  const boundary = root.closest('[data-dalila-internal-bound]');
240
310
  return out.filter(el => {
241
311
  const bound = el.closest('[data-dalila-internal-bound]');
242
- return bound === boundary;
312
+ if (bound !== boundary)
313
+ return false;
314
+ return !isInsideRawBlock(el, boundary);
243
315
  });
244
316
  }
245
317
  /**
@@ -1486,6 +1558,8 @@ function createInterpolationTemplatePlan(root, rawTextSelectors) {
1486
1558
  const parent = node.parentElement;
1487
1559
  if (parent && parent.closest(rawTextSelectors))
1488
1560
  continue;
1561
+ if (parent && parent.closest(RAW_BLOCK_SELECTORS))
1562
+ continue;
1489
1563
  if (parent) {
1490
1564
  const bound = parent.closest('[data-dalila-internal-bound]');
1491
1565
  if (bound !== textBoundary)
@@ -3381,6 +3455,9 @@ export function bind(root, ctx, options = {}) {
3381
3455
  warnAsErrorScopes.add(templateScope);
3382
3456
  }
3383
3457
  linkScopeToDom(templateScope, root, describeBindRoot(root));
3458
+ // Harden raw code-example blocks early so nested markup is rendered as
3459
+ // inert text before any directive/event pass runs.
3460
+ materializeRawBlocks(root);
3384
3461
  const bindScanPlan = createBindScanPlan(root);
3385
3462
  let bindCompleted = false;
3386
3463
  let disposed = false;
@@ -12,6 +12,10 @@ import { bind, warnSecurityRuntime } from './bind.js';
12
12
  import { defineComponent } from './component.js';
13
13
  import { hasExecutableHtmlSinkPattern, setElementInnerHTML } from './html-sinks.js';
14
14
  // ============================================================================
15
+ // Types
16
+ // ============================================================================
17
+ const RAW_BLOCK_SELECTORS = '[d-pre], [d-raw], d-pre, d-raw, [data-dalila-raw]';
18
+ // ============================================================================
15
19
  // Error Boundary Component
16
20
  // ============================================================================
17
21
  /**
@@ -97,6 +101,10 @@ export function bindBoundary(root, ctx, cleanups, options = {}) {
97
101
  continue;
98
102
  if (el.closest('[data-dalila-internal-bound]') !== boundary)
99
103
  continue;
104
+ // Raw blocks are inert by contract: d-* directives must not run inside.
105
+ const rawRoot = el.closest(RAW_BLOCK_SELECTORS);
106
+ if (rawRoot && rawRoot.closest('[data-dalila-internal-bound]') === boundary)
107
+ continue;
100
108
  for (const nested of Array.from(el.querySelectorAll('[d-boundary]'))) {
101
109
  consumedNested.add(nested);
102
110
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dalila",
3
- "version": "1.9.26",
3
+ "version": "1.10.1",
4
4
  "description": "DOM-first reactive framework based on signals",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -35,6 +35,8 @@
35
35
  @import "../table/table.css";
36
36
  @import "../pagination/pagination.css";
37
37
  @import "../breadcrumb/breadcrumb.css";
38
+ @import "../nav-bar/nav-bar.css";
39
+ @import "../side-bar/side-bar.css";
38
40
  @import "../separator/separator.css";
39
41
  @import "../skeleton/skeleton.css";
40
42
  @import "../spinner/spinner.css";
@@ -0,0 +1,75 @@
1
+ /* Dalila UI — Nav Bar */
2
+
3
+ .d-nav-bar {
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: space-between;
7
+ gap: var(--d-space-4);
8
+ width: 100%;
9
+ padding: var(--d-space-3) var(--d-space-4);
10
+ border: 1px solid var(--d-border-color);
11
+ border-radius: var(--d-radius-lg);
12
+ background: var(--d-surface);
13
+ }
14
+
15
+ .d-nav-brand {
16
+ display: inline-flex;
17
+ align-items: center;
18
+ gap: var(--d-space-2);
19
+ font-family: var(--d-font-sans);
20
+ font-size: var(--d-text-base);
21
+ font-weight: var(--d-font-semibold);
22
+ color: var(--d-text-primary);
23
+ text-decoration: none;
24
+ white-space: nowrap;
25
+ }
26
+
27
+ .d-nav-links {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: var(--d-space-1);
31
+ flex-wrap: wrap;
32
+ }
33
+
34
+ .d-nav-link {
35
+ display: inline-flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ min-height: 2rem;
39
+ padding: var(--d-space-1) var(--d-space-3);
40
+ border-radius: var(--d-radius-md);
41
+ font-family: var(--d-font-sans);
42
+ font-size: var(--d-text-sm);
43
+ font-weight: var(--d-font-medium);
44
+ color: var(--d-text-secondary);
45
+ text-decoration: none;
46
+ transition:
47
+ background var(--d-duration-fast) var(--d-ease),
48
+ color var(--d-duration-fast) var(--d-ease);
49
+ }
50
+
51
+ .d-nav-link:hover {
52
+ color: var(--d-text-primary);
53
+ background: var(--d-surface-raised);
54
+ }
55
+
56
+ .d-nav-link[aria-current="page"],
57
+ .d-nav-link.active {
58
+ color: #fff;
59
+ background: var(--d-accent);
60
+ }
61
+
62
+ @media (max-width: 640px) {
63
+ .d-nav-bar {
64
+ flex-direction: column;
65
+ align-items: stretch;
66
+ }
67
+
68
+ .d-nav-links {
69
+ width: 100%;
70
+ }
71
+
72
+ .d-nav-link {
73
+ flex: 1 1 auto;
74
+ }
75
+ }
@@ -0,0 +1,414 @@
1
+ /* Dalila UI — Side Bar */
2
+
3
+ .d-side-bar {
4
+ display: block;
5
+ box-sizing: border-box;
6
+ width: 18rem;
7
+ max-width: 18rem;
8
+ min-height: 22.75rem;
9
+ padding: var(--d-space-3);
10
+ border: 1px solid var(--d-border-color);
11
+ border-radius: var(--d-radius-xl);
12
+ background: var(--d-surface);
13
+ transition:
14
+ width var(--d-duration-slow) var(--d-ease),
15
+ max-width var(--d-duration-slow) var(--d-ease),
16
+ padding var(--d-duration) var(--d-ease);
17
+ }
18
+
19
+ .d-side-bar-shell {
20
+ position: relative;
21
+ display: inline-grid;
22
+ grid-template-columns: max-content 2.75rem;
23
+ column-gap: var(--d-space-3);
24
+ justify-self: start;
25
+ align-self: start;
26
+ width: fit-content;
27
+ }
28
+
29
+ .d-side-bar-rail {
30
+ position: relative;
31
+ display: flex;
32
+ align-items: flex-start;
33
+ justify-content: center;
34
+ width: 2.75rem;
35
+ min-height: 100%;
36
+ padding-top: calc(var(--d-space-4) + 1px);
37
+ }
38
+
39
+ .d-side-bar-rail::before {
40
+ content: "";
41
+ position: absolute;
42
+ inset: 0 auto 0 50%;
43
+ width: 1px;
44
+ background: color-mix(in srgb, var(--d-border-color) 72%, transparent);
45
+ transform: translateX(-50%);
46
+ }
47
+
48
+ .d-side-bar-floating-toggle {
49
+ position: relative;
50
+ display: inline-flex;
51
+ align-items: center;
52
+ justify-content: center;
53
+ width: 2.25rem;
54
+ height: 2.25rem;
55
+ border: 1px solid var(--d-border-color);
56
+ border-radius: var(--d-radius-lg);
57
+ background: color-mix(in srgb, var(--d-surface-card) 88%, var(--d-surface-raised));
58
+ color: var(--d-text-secondary);
59
+ box-shadow: var(--d-shadow-sm);
60
+ cursor: pointer;
61
+ z-index: 2;
62
+ transition:
63
+ background var(--d-duration-fast) var(--d-ease),
64
+ border-color var(--d-duration-fast) var(--d-ease),
65
+ color var(--d-duration-fast) var(--d-ease),
66
+ box-shadow var(--d-duration-fast) var(--d-ease);
67
+ }
68
+
69
+ .d-side-bar-floating-toggle:hover {
70
+ background: var(--d-surface-card);
71
+ border-color: color-mix(in srgb, var(--d-border-color) 50%, var(--d-accent) 50%);
72
+ color: var(--d-text-primary);
73
+ }
74
+
75
+ .d-side-bar-floating-toggle:focus-visible {
76
+ outline: none;
77
+ border-color: var(--d-accent);
78
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--d-accent) 18%, transparent);
79
+ }
80
+
81
+ .d-side-bar-toggle-icon {
82
+ width: 1rem;
83
+ height: 1rem;
84
+ stroke: currentColor;
85
+ fill: none;
86
+ stroke-width: 1.9;
87
+ stroke-linecap: round;
88
+ stroke-linejoin: round;
89
+ }
90
+
91
+ .d-side-bar-floating-toggle .d-side-bar-toggle-icon-expand {
92
+ display: none;
93
+ }
94
+
95
+ .d-side-bar-floating-toggle.collapsed .d-side-bar-toggle-icon-expand {
96
+ display: block;
97
+ }
98
+
99
+ .d-side-bar-floating-toggle.collapsed .d-side-bar-toggle-icon-collapse {
100
+ display: none;
101
+ }
102
+
103
+ .d-side-bar-inner {
104
+ display: flex;
105
+ flex-direction: column;
106
+ min-height: 100%;
107
+ }
108
+
109
+ .d-side-bar-top {
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: space-between;
113
+ gap: var(--d-space-2);
114
+ margin-bottom: var(--d-space-3);
115
+ }
116
+
117
+ .d-side-bar-brand {
118
+ display: inline-flex;
119
+ align-items: center;
120
+ gap: var(--d-space-3);
121
+ min-width: 0;
122
+ color: inherit;
123
+ text-decoration: none;
124
+ }
125
+
126
+ .d-side-bar-logo {
127
+ display: inline-flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ width: 2.25rem;
131
+ height: 2.25rem;
132
+ border-radius: var(--d-radius-md);
133
+ background: var(--d-primary-600);
134
+ color: var(--d-primary-50);
135
+ font-size: var(--d-text-base);
136
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--d-primary-50) 20%, transparent);
137
+ }
138
+
139
+ .d-side-bar-meta {
140
+ min-width: 0;
141
+ }
142
+
143
+ .d-side-bar-name {
144
+ display: block;
145
+ font-family: var(--d-font-sans);
146
+ font-size: var(--d-text-sm);
147
+ font-weight: var(--d-font-semibold);
148
+ color: var(--d-text-primary);
149
+ line-height: 1.3;
150
+ white-space: nowrap;
151
+ overflow: hidden;
152
+ text-overflow: ellipsis;
153
+ }
154
+
155
+ .d-side-bar-subtitle {
156
+ display: block;
157
+ font-family: var(--d-font-sans);
158
+ font-size: var(--d-text-xs);
159
+ color: var(--d-text-tertiary);
160
+ line-height: 1.3;
161
+ white-space: nowrap;
162
+ overflow: hidden;
163
+ text-overflow: ellipsis;
164
+ }
165
+
166
+ .d-side-bar-toggle {
167
+ display: inline-flex;
168
+ align-items: center;
169
+ justify-content: center;
170
+ width: 2rem;
171
+ height: 2rem;
172
+ border: 1px solid var(--d-border-color);
173
+ border-radius: var(--d-radius-md);
174
+ background: transparent;
175
+ color: var(--d-text-secondary);
176
+ cursor: pointer;
177
+ font-family: var(--d-font-sans);
178
+ font-size: var(--d-text-sm);
179
+ line-height: 1;
180
+ transition:
181
+ background var(--d-duration-fast) var(--d-ease),
182
+ color var(--d-duration-fast) var(--d-ease);
183
+ }
184
+
185
+ .d-side-bar-toggle:hover {
186
+ background: var(--d-surface-raised);
187
+ color: var(--d-text-primary);
188
+ }
189
+
190
+ .d-side-bar-toggle:focus-visible {
191
+ outline: none;
192
+ box-shadow: inset 0 0 0 2px var(--d-accent);
193
+ }
194
+
195
+ .d-side-bar-header {
196
+ margin-bottom: var(--d-space-3);
197
+ padding: 0 var(--d-space-2);
198
+ font-family: var(--d-font-sans);
199
+ font-size: var(--d-text-xs);
200
+ font-weight: var(--d-font-semibold);
201
+ letter-spacing: 0.08em;
202
+ text-transform: uppercase;
203
+ color: var(--d-text-tertiary);
204
+ }
205
+
206
+ .d-side-bar-sections {
207
+ display: flex;
208
+ flex-direction: column;
209
+ gap: var(--d-space-1);
210
+ }
211
+
212
+ .d-side-bar-section {
213
+ border-radius: var(--d-radius-md);
214
+ overflow: hidden;
215
+ }
216
+
217
+ .d-side-bar-title {
218
+ display: flex;
219
+ align-items: center;
220
+ justify-content: space-between;
221
+ width: 100%;
222
+ min-height: 2.25rem;
223
+ padding: var(--d-space-2) var(--d-space-3);
224
+ font-family: var(--d-font-sans);
225
+ font-size: var(--d-text-sm);
226
+ font-weight: var(--d-font-medium);
227
+ color: var(--d-text-primary);
228
+ cursor: pointer;
229
+ list-style: none;
230
+ user-select: none;
231
+ transition: background var(--d-duration-fast) var(--d-ease);
232
+ border-radius: var(--d-radius-md);
233
+ }
234
+
235
+ .d-side-bar-title:hover {
236
+ background: var(--d-surface-raised);
237
+ }
238
+
239
+ .d-side-bar-section[open] > .d-side-bar-title,
240
+ .d-side-bar-section.open > .d-side-bar-title {
241
+ border-radius: var(--d-radius-md);
242
+ }
243
+
244
+ .d-side-bar-title::-webkit-details-marker {
245
+ display: none;
246
+ }
247
+
248
+ .d-side-bar-title::after {
249
+ content: "";
250
+ width: 0.45rem;
251
+ height: 0.45rem;
252
+ border-right: 2px solid var(--d-text-tertiary);
253
+ border-bottom: 2px solid var(--d-text-tertiary);
254
+ transform: rotate(45deg);
255
+ transition: transform var(--d-duration) var(--d-ease);
256
+ flex-shrink: 0;
257
+ }
258
+
259
+ .d-side-bar-section[open] > .d-side-bar-title::after,
260
+ .d-side-bar-section.open > .d-side-bar-title::after {
261
+ transform: rotate(-135deg);
262
+ }
263
+
264
+ .d-side-bar-links {
265
+ display: grid;
266
+ gap: var(--d-space-1);
267
+ padding: var(--d-space-2) var(--d-space-2) var(--d-space-2) var(--d-space-4);
268
+ transition: padding var(--d-duration) var(--d-ease);
269
+ }
270
+
271
+ .d-side-bar-link {
272
+ display: inline-flex;
273
+ align-items: center;
274
+ gap: var(--d-space-2);
275
+ width: 100%;
276
+ min-height: 2rem;
277
+ padding: var(--d-space-1) var(--d-space-2);
278
+ border-radius: var(--d-radius-sm);
279
+ font-family: var(--d-font-sans);
280
+ font-size: var(--d-text-sm);
281
+ color: var(--d-text-secondary);
282
+ position: relative;
283
+ text-decoration: none;
284
+ transition:
285
+ background var(--d-duration-fast) var(--d-ease),
286
+ color var(--d-duration-fast) var(--d-ease);
287
+ }
288
+
289
+ .d-side-bar-link:hover {
290
+ color: var(--d-text-primary);
291
+ background: color-mix(in srgb, var(--d-accent) 12%, transparent);
292
+ }
293
+
294
+ .d-side-bar-link[aria-current="page"],
295
+ .d-side-bar-link.active {
296
+ color: var(--d-accent);
297
+ background: color-mix(in srgb, var(--d-accent) 16%, transparent);
298
+ }
299
+
300
+ .d-side-bar-link-icon {
301
+ display: inline-flex;
302
+ align-items: center;
303
+ justify-content: center;
304
+ width: 1.125rem;
305
+ flex-shrink: 0;
306
+ }
307
+
308
+ .d-side-bar-link-label {
309
+ white-space: nowrap;
310
+ overflow: hidden;
311
+ text-overflow: ellipsis;
312
+ }
313
+
314
+ .d-side-bar.collapsed {
315
+ width: 4.25rem;
316
+ max-width: 4.25rem;
317
+ padding: var(--d-space-3) var(--d-space-2);
318
+ }
319
+
320
+ .d-side-bar.collapsed .d-side-bar-top {
321
+ margin-bottom: var(--d-space-2);
322
+ justify-content: center;
323
+ }
324
+
325
+ .d-side-bar.collapsed .d-side-bar-meta,
326
+ .d-side-bar.collapsed .d-side-bar-header,
327
+ .d-side-bar.collapsed .d-side-bar-footer {
328
+ display: none;
329
+ }
330
+
331
+ .d-side-bar.collapsed .d-side-bar-brand {
332
+ justify-content: center;
333
+ width: 100%;
334
+ gap: 0;
335
+ }
336
+
337
+ .d-side-bar.collapsed .d-side-bar-sections {
338
+ gap: var(--d-space-2);
339
+ }
340
+
341
+ .d-side-bar.collapsed .d-side-bar-section {
342
+ width: 100%;
343
+ overflow: visible;
344
+ }
345
+
346
+ .d-side-bar.collapsed .d-side-bar-link:nth-child(n + 2) {
347
+ display: none;
348
+ }
349
+
350
+ .d-side-bar.collapsed .d-side-bar-title {
351
+ display: none;
352
+ }
353
+
354
+ .d-side-bar.collapsed .d-side-bar-links {
355
+ padding: 0;
356
+ }
357
+
358
+ .d-side-bar.collapsed .d-side-bar-link {
359
+ justify-content: center;
360
+ min-height: 2.5rem;
361
+ padding: 0;
362
+ border-radius: var(--d-radius-md);
363
+ }
364
+
365
+ .d-side-bar.collapsed .d-side-bar-link-icon {
366
+ width: 1.25rem;
367
+ font-size: var(--d-text-base);
368
+ }
369
+
370
+ .d-side-bar.collapsed .d-side-bar-link-label {
371
+ position: absolute;
372
+ width: 1px;
373
+ height: 1px;
374
+ margin: -1px;
375
+ padding: 0;
376
+ overflow: hidden;
377
+ clip: rect(0, 0, 0, 0);
378
+ white-space: nowrap;
379
+ border: 0;
380
+ }
381
+
382
+ .d-side-bar-footer {
383
+ margin-top: auto;
384
+ padding-top: var(--d-space-3);
385
+ display: flex;
386
+ justify-content: center;
387
+ }
388
+
389
+ .d-side-bar-avatar {
390
+ width: 1.9rem;
391
+ height: 1.9rem;
392
+ border-radius: 999px;
393
+ overflow: hidden;
394
+ display: inline-flex;
395
+ align-items: center;
396
+ justify-content: center;
397
+ }
398
+
399
+ .d-side-bar-avatar img {
400
+ width: 100%;
401
+ height: 100%;
402
+ object-fit: cover;
403
+ }
404
+
405
+ .d-side-bar-avatar-text {
406
+ width: 100%;
407
+ height: 100%;
408
+ display: inline-flex;
409
+ align-items: center;
410
+ justify-content: center;
411
+ font-size: 0.95rem;
412
+ background: linear-gradient(145deg, var(--d-primary-500), var(--d-accent-500));
413
+ color: #fff;
414
+ }
@@ -116,6 +116,7 @@
116
116
 
117
117
  --d-surface-page: var(--d-bg);
118
118
  --d-surface-card: var(--d-bg-card);
119
+ --d-surface: var(--d-surface-card);
119
120
  --d-surface-raised: var(--d-bg-elevated);
120
121
  --d-surface-overlay: rgba(0, 0, 0, 0.4);
121
122