@stacksjs/components 0.2.112 → 0.2.114

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.
@@ -31,7 +31,6 @@ export const sidebarUid = $props.id || `stx-sidebar-${Math.random().toString(36)
31
31
  const themeStyle = resolveSidebarTheme(theme)
32
32
  export const selectionModel = themeStyle.selection
33
33
  export const hasShimmerRim = themeStyle.layers.shimmerRim === true
34
- export const isFrosted = themeStyle.layers.frosted === true
35
34
  export const tintClasses = themeStyle.layers.tint || ''
36
35
  export const scrollAreaClasses = themeStyle.scrollArea
37
36
 
@@ -68,6 +67,8 @@ export const activeClasses = themeStyle.item.active
68
67
  </script>
69
68
 
70
69
  <script client>
70
+ import { findActiveSidebarHref } from './route-selection'
71
+
71
72
  const emit = defineEmits()
72
73
  const sidebarUidProp = '{{ sidebarUid }}'
73
74
  const collapsedProp = {{ collapsed }}
@@ -101,12 +102,33 @@ function setActiveRow(button) {
101
102
  const classes = activeClassList()
102
103
  pane.querySelectorAll('[data-sidebar-item][data-active="true"]').forEach((el) => {
103
104
  el.removeAttribute('data-active')
105
+ el.removeAttribute('aria-current')
104
106
  el.classList.remove(...classes)
105
107
  })
106
108
  button.setAttribute('data-active', 'true')
109
+ button.setAttribute('aria-current', 'page')
107
110
  button.classList.add(...classes)
108
111
  }
109
112
 
113
+ function syncRouteSelection(url) {
114
+ const links = [...pane.querySelectorAll('a[data-sidebar-item][href]')]
115
+ let currentPath = window.location.pathname
116
+ if (url) {
117
+ try {
118
+ currentPath = new URL(url, window.location.origin).pathname
119
+ }
120
+ catch {}
121
+ }
122
+ const href = findActiveSidebarHref(currentPath, links.map(link => link.getAttribute('href') || ''))
123
+ const active = href ? links.find(link => link.getAttribute('href') === href) : null
124
+ if (active)
125
+ setActiveRow(active)
126
+ }
127
+
128
+ function onRouteChange(event) {
129
+ syncRouteSelection(event?.detail?.url)
130
+ }
131
+
110
132
  // ---------------------------------------------------------------------------
111
133
  // Disclosure — rows know their ancestors (data-parents="a/b"). Collapsing a
112
134
  // row (or section) marks every descendant with data-collapsed, which the
@@ -324,18 +346,22 @@ onMount(() => {
324
346
 
325
347
  pane.addEventListener('click', onClick)
326
348
  pane.addEventListener('keydown', onKeyDown)
349
+ window.addEventListener('stx:navigate', onRouteChange)
350
+ window.addEventListener('popstate', onRouteChange)
327
351
 
328
352
  const scrollArea = pane.querySelector('[data-sidebar-scroll]')
329
353
  const thumb = pane.querySelector('[data-sidebar-scroll-thumb]')
330
354
  if (scrollArea && thumb) attachOverlayScrollbar(scrollArea, thumb)
331
355
 
332
- // Ensure the initial active row carries the theme's highlight classes.
356
+ // Route-derived selection wins over stale server-declared state on direct
357
+ // loads, nested routes, SPA transitions, and browser history navigation.
358
+ syncRouteSelection()
333
359
  const active = pane.querySelector('[data-sidebar-item][data-active="true"]')
334
360
  if (active) active.classList.add(...activeClassList())
335
361
  syncRowVisibility()
336
362
  })
337
363
 
338
- defineExpose({ collapsed, toggle: toggleCollapse })
364
+ defineExpose({ collapsed, toggle: toggleCollapse, syncRouteSelection })
339
365
  </script>
340
366
 
341
367
  <aside
@@ -343,7 +369,6 @@ defineExpose({ collapsed, toggle: toggleCollapse })
343
369
  data-stx-sidebar
344
370
  data-sidebar-theme="{{ theme }}"
345
371
  data-sidebar-selection="{{ selectionModel }}"
346
- @if(isFrosted)data-sidebar-frosted@endif
347
372
  data-window-key="true"
348
373
  data-active-class="{{ activeClasses }}"
349
374
  class="{{ paneClasses }}"
@@ -454,20 +479,6 @@ defineExpose({ collapsed, toggle: toggleCollapse })
454
479
  .stx-sidebar-rim::after { animation: none; }
455
480
  }
456
481
 
457
- /* Frosted sidebar material. Hand-written CSS on purpose: as the utilities
458
- `backdrop-blur-[50px]` / `backdrop-saturate-[180%]` the arbitrary values
459
- did not survive class extraction in the stx dev server, leaving the pane
460
- with `backdrop-filter: none`; an earlier `backdrop-saturate-[1.7]` lost
461
- its brackets on the way to the generator, was read as a percentage, and
462
- compiled to `saturate(0.017)`, which drained the colour out of the whole
463
- backdrop. In a Craft window with webSidebarMaterial the native
464
- NSVisualEffectView supplies the real vibrancy; this approximates it
465
- everywhere else. */
466
- [data-sidebar-frosted] {
467
- -webkit-backdrop-filter: saturate(180%) blur(50px);
468
- backdrop-filter: saturate(180%) blur(50px);
469
- }
470
-
471
482
  /* ---------------------------------------------------------------------------
472
483
  * Selection, `accent` model (macOS and its derivatives).
473
484
  *
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  // @bun
2
- // ../../../../../../../tmp/stx-components-build-ph5rIj/stubs/stub-0.js
2
+ // ../../../../../../../tmp/stx-components-build-FWtLPS/stubs/stub-0.js
3
3
  var stub_0_default = { __stx: true, src: "./components/CodeBlock.stx" };
4
- // ../../../../../../../tmp/stx-components-build-ph5rIj/stubs/stub-1.js
4
+ // ../../../../../../../tmp/stx-components-build-FWtLPS/stubs/stub-1.js
5
5
  var stub_1_default = { __stx: true, src: "./components/Footer.stx" };
6
- // ../../../../../../../tmp/stx-components-build-ph5rIj/stubs/stub-2.js
6
+ // ../../../../../../../tmp/stx-components-build-FWtLPS/stubs/stub-2.js
7
7
  var stub_2_default = { __stx: true, src: "./components/Hero.stx" };
8
- // ../../../../../../../tmp/stx-components-build-ph5rIj/stubs/stub-3.js
8
+ // ../../../../../../../tmp/stx-components-build-FWtLPS/stubs/stub-3.js
9
9
  var stub_3_default = { __stx: true, src: "./components/Installation.stx" };
10
10
  // src/composables/useCopyCode.ts
11
11
  function useCopyCode(options) {
@@ -1132,7 +1132,7 @@ var RadioGroupOption_default = "./RadioGroupOption-s1d6r221.stx";
1132
1132
  // src/ui/select/Select.stx
1133
1133
  var Select_default = "./Select-1vnd0brz.stx";
1134
1134
  // src/ui/sidebar/Sidebar.stx
1135
- var Sidebar_default = "./Sidebar-bg47fqmq.stx";
1135
+ var Sidebar_default = "./Sidebar-hjazqxv8.stx";
1136
1136
  // src/ui/sidebar/SidebarFooter.stx
1137
1137
  var SidebarFooter_default = "./SidebarFooter-0aegr99p.stx";
1138
1138
  // src/ui/sidebar/SidebarHeader.stx
@@ -1159,13 +1159,13 @@ var macosColors = {
1159
1159
  var macos = {
1160
1160
  pane: [
1161
1161
  "bg-[#f2f2f4]/90 dark:bg-[#1c1c1e]/88",
1162
+ "backdrop-blur-[50px] backdrop-saturate-[180%]",
1162
1163
  "text-black dark:text-white",
1163
1164
  "select-none"
1164
1165
  ].join(" "),
1165
1166
  selection: "accent",
1166
1167
  layers: {
1167
- shimmerRim: true,
1168
- frosted: true
1168
+ shimmerRim: true
1169
1169
  },
1170
1170
  scrollArea: "flex-1 overflow-y-auto overflow-x-hidden px-[10px] pb-[10px]",
1171
1171
  sectionHeader: [
@@ -0,0 +1 @@
1
+ export declare function findActiveSidebarHref(currentPath: string, hrefs: string[]): string | null;
@@ -60,7 +60,6 @@ export declare interface SidebarTheme {
60
60
  selection: SidebarSelectionModel
61
61
  layers: {
62
62
  shimmerRim?: boolean
63
- frosted?: boolean
64
63
  tint?: string
65
64
  }
66
65
  scrollArea: string
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/components",
3
3
  "type": "module",
4
- "version": "0.2.112",
4
+ "version": "0.2.114",
5
5
  "description": "A modern component library built with STX, featuring headwind utility classes and TypeScript syntax highlighting.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -62,9 +62,9 @@
62
62
  "prepublishOnly": "bun run build"
63
63
  },
64
64
  "dependencies": {
65
- "@cwcss/crosswind": "^0.2.6",
66
- "@stacksjs/stx": "0.2.112",
67
- "bun-plugin-stx": "0.2.112",
65
+ "@cwcss/crosswind": "^0.2.12",
66
+ "@stacksjs/stx": "0.2.114",
67
+ "bun-plugin-stx": "0.2.114",
68
68
  "ts-syntax-highlighter": "^0.2.1"
69
69
  },
70
70
  "devDependencies": {
@@ -31,7 +31,6 @@ export const sidebarUid = $props.id || `stx-sidebar-${Math.random().toString(36)
31
31
  const themeStyle = resolveSidebarTheme(theme)
32
32
  export const selectionModel = themeStyle.selection
33
33
  export const hasShimmerRim = themeStyle.layers.shimmerRim === true
34
- export const isFrosted = themeStyle.layers.frosted === true
35
34
  export const tintClasses = themeStyle.layers.tint || ''
36
35
  export const scrollAreaClasses = themeStyle.scrollArea
37
36
 
@@ -68,6 +67,8 @@ export const activeClasses = themeStyle.item.active
68
67
  </script>
69
68
 
70
69
  <script client>
70
+ import { findActiveSidebarHref } from './route-selection'
71
+
71
72
  const emit = defineEmits()
72
73
  const sidebarUidProp = '{{ sidebarUid }}'
73
74
  const collapsedProp = {{ collapsed }}
@@ -101,12 +102,33 @@ function setActiveRow(button) {
101
102
  const classes = activeClassList()
102
103
  pane.querySelectorAll('[data-sidebar-item][data-active="true"]').forEach((el) => {
103
104
  el.removeAttribute('data-active')
105
+ el.removeAttribute('aria-current')
104
106
  el.classList.remove(...classes)
105
107
  })
106
108
  button.setAttribute('data-active', 'true')
109
+ button.setAttribute('aria-current', 'page')
107
110
  button.classList.add(...classes)
108
111
  }
109
112
 
113
+ function syncRouteSelection(url) {
114
+ const links = [...pane.querySelectorAll('a[data-sidebar-item][href]')]
115
+ let currentPath = window.location.pathname
116
+ if (url) {
117
+ try {
118
+ currentPath = new URL(url, window.location.origin).pathname
119
+ }
120
+ catch {}
121
+ }
122
+ const href = findActiveSidebarHref(currentPath, links.map(link => link.getAttribute('href') || ''))
123
+ const active = href ? links.find(link => link.getAttribute('href') === href) : null
124
+ if (active)
125
+ setActiveRow(active)
126
+ }
127
+
128
+ function onRouteChange(event) {
129
+ syncRouteSelection(event?.detail?.url)
130
+ }
131
+
110
132
  // ---------------------------------------------------------------------------
111
133
  // Disclosure — rows know their ancestors (data-parents="a/b"). Collapsing a
112
134
  // row (or section) marks every descendant with data-collapsed, which the
@@ -324,18 +346,22 @@ onMount(() => {
324
346
 
325
347
  pane.addEventListener('click', onClick)
326
348
  pane.addEventListener('keydown', onKeyDown)
349
+ window.addEventListener('stx:navigate', onRouteChange)
350
+ window.addEventListener('popstate', onRouteChange)
327
351
 
328
352
  const scrollArea = pane.querySelector('[data-sidebar-scroll]')
329
353
  const thumb = pane.querySelector('[data-sidebar-scroll-thumb]')
330
354
  if (scrollArea && thumb) attachOverlayScrollbar(scrollArea, thumb)
331
355
 
332
- // Ensure the initial active row carries the theme's highlight classes.
356
+ // Route-derived selection wins over stale server-declared state on direct
357
+ // loads, nested routes, SPA transitions, and browser history navigation.
358
+ syncRouteSelection()
333
359
  const active = pane.querySelector('[data-sidebar-item][data-active="true"]')
334
360
  if (active) active.classList.add(...activeClassList())
335
361
  syncRowVisibility()
336
362
  })
337
363
 
338
- defineExpose({ collapsed, toggle: toggleCollapse })
364
+ defineExpose({ collapsed, toggle: toggleCollapse, syncRouteSelection })
339
365
  </script>
340
366
 
341
367
  <aside
@@ -343,7 +369,6 @@ defineExpose({ collapsed, toggle: toggleCollapse })
343
369
  data-stx-sidebar
344
370
  data-sidebar-theme="{{ theme }}"
345
371
  data-sidebar-selection="{{ selectionModel }}"
346
- @if(isFrosted)data-sidebar-frosted@endif
347
372
  data-window-key="true"
348
373
  data-active-class="{{ activeClasses }}"
349
374
  class="{{ paneClasses }}"
@@ -454,20 +479,6 @@ defineExpose({ collapsed, toggle: toggleCollapse })
454
479
  .stx-sidebar-rim::after { animation: none; }
455
480
  }
456
481
 
457
- /* Frosted sidebar material. Hand-written CSS on purpose: as the utilities
458
- `backdrop-blur-[50px]` / `backdrop-saturate-[180%]` the arbitrary values
459
- did not survive class extraction in the stx dev server, leaving the pane
460
- with `backdrop-filter: none`; an earlier `backdrop-saturate-[1.7]` lost
461
- its brackets on the way to the generator, was read as a percentage, and
462
- compiled to `saturate(0.017)`, which drained the colour out of the whole
463
- backdrop. In a Craft window with webSidebarMaterial the native
464
- NSVisualEffectView supplies the real vibrancy; this approximates it
465
- everywhere else. */
466
- [data-sidebar-frosted] {
467
- -webkit-backdrop-filter: saturate(180%) blur(50px);
468
- backdrop-filter: saturate(180%) blur(50px);
469
- }
470
-
471
482
  /* ---------------------------------------------------------------------------
472
483
  * Selection, `accent` model (macOS and its derivatives).
473
484
  *
@@ -0,0 +1,32 @@
1
+ function normalizePath(path: string): string {
2
+ const clean = (path || '/').split(/[?#]/, 1)[0] || '/'
3
+ const withSlash = clean.startsWith('/') ? clean : `/${clean}`
4
+ return withSlash.replace(/\/+$/, '') || '/'
5
+ }
6
+
7
+ function isLocalHref(href: string): boolean {
8
+ return href.startsWith('/') && !href.startsWith('//')
9
+ }
10
+
11
+ export function findActiveSidebarHref(currentPath: string, hrefs: string[]): string | null {
12
+ const current = normalizePath(currentPath)
13
+ let best: string | null = null
14
+ let bestLength = -1
15
+
16
+ for (const href of hrefs) {
17
+ if (!isLocalHref(href))
18
+ continue
19
+
20
+ const candidate = normalizePath(href)
21
+ const matches = candidate === '/'
22
+ ? current === '/'
23
+ : current === candidate || current.startsWith(`${candidate}/`)
24
+
25
+ if (matches && candidate.length > bestLength) {
26
+ best = href
27
+ bestLength = candidate.length
28
+ }
29
+ }
30
+
31
+ return best
32
+ }
@@ -67,8 +67,6 @@ export interface SidebarTheme {
67
67
  layers: {
68
68
  /** Liquid Glass edge highlight — a bright rim that subtly shimmers. */
69
69
  shimmerRim?: boolean
70
- /** Frosted material behind the pane (a real `backdrop-filter`). */
71
- frosted?: boolean
72
70
  /** Soft tint gradient blended over the material. */
73
71
  tint?: string
74
72
  }
@@ -116,22 +114,13 @@ const macos: SidebarTheme = {
116
114
  // hint of the backdrop through. A half-transparent pane reads as a web
117
115
  // imitation, and over a dark page it inverts into a dark bar entirely.
118
116
  'bg-[#f2f2f4]/90 dark:bg-[#1c1c1e]/88',
117
+ 'backdrop-blur-[50px] backdrop-saturate-[180%]',
119
118
  'text-black dark:text-white',
120
119
  'select-none',
121
120
  ].join(' '),
122
121
  selection: 'accent',
123
122
  layers: {
124
123
  shimmerRim: true,
125
- // The blur itself is real CSS in Sidebar.stx rather than a utility.
126
- // Expressed as `backdrop-blur-[50px]` / `backdrop-saturate-[180%]` the
127
- // arbitrary values did not survive the stx dev server's class
128
- // extraction: the pane computed `backdrop-filter: none`, and an earlier
129
- // `backdrop-saturate-[1.7]` reached the generator with its brackets
130
- // stripped, so it was read as a percentage and compiled to
131
- // `saturate(0.017)` — draining the colour out of the whole backdrop.
132
- // Crosswind parses and generates both correctly on its own, so the
133
- // material is written as plain CSS to stay out of that path entirely.
134
- frosted: true,
135
124
  },
136
125
  scrollArea: 'flex-1 overflow-y-auto overflow-x-hidden px-[10px] pb-[10px]',
137
126
  sectionHeader: [