@filipgutica/ui 1.11.0 → 1.11.2

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/README.md CHANGED
@@ -1,7 +1,14 @@
1
1
  # @filipgutica/ui
2
2
 
3
- Compact Vue 3.5+ components built on [Reka UI](https://reka-ui.com), with
4
- Tailwind CSS 4 and VS Code theme support.
3
+ @filipgutica/ui is a compact Vue 3.5+ component library built on
4
+ [Reka UI](https://reka-ui.com) and styled with Tailwind CSS 4. You can recolor
5
+ it with a VS Code color theme: the theme supplies the colors, and the library
6
+ keeps typography, spacing, and component structure the same.
7
+
8
+ Use it when you want dense, calm controls: neutral surfaces with one accent
9
+ color, compact sizes on a 4px rhythm, and visible keyboard focus. It also
10
+ provides the shared header, navigation, and page components of Filip's project
11
+ sites.
5
12
 
6
13
  [Docs and playground](https://filipgutica.github.io/ui/) ·
7
14
  [GitHub](https://github.com/filipgutica/ui) ·
@@ -9,6 +16,9 @@ Tailwind CSS 4 and VS Code theme support.
9
16
 
10
17
  ## Install
11
18
 
19
+ You need a Vue 3.5+ app whose stylesheet Tailwind CSS 4 already builds. This
20
+ package does not install Tailwind.
21
+
12
22
  ```sh
13
23
  pnpm add @filipgutica/ui
14
24
  ```
@@ -22,6 +32,9 @@ Import the styles from your application stylesheet:
22
32
  @import "@filipgutica/ui/components.css";
23
33
  ```
24
34
 
35
+ The third import adds Graphite Indigo, the palette of the project sites. Leave
36
+ it out to keep the base theme from `theme.css`, with blue actions.
37
+
25
38
  ## Use
26
39
 
27
40
  ```vue
@@ -37,45 +50,12 @@ import { UiButton } from '@filipgutica/ui'
37
50
  ```
38
51
 
39
52
  The [component docs](https://filipgutica.github.io/ui/#/components/button)
40
- cover examples, props, slots, and events. Place inputs and selects inside
41
- `UiField` to connect labels, descriptions, and errors automatically.
42
-
43
- ## Page navigation
44
-
45
- `UiDrawer` provides modal focus containment, Escape dismissal, and trigger focus
46
- restoration. Keep native navigation in the server-rendered page until hydration;
47
- the application decides when to replace it and when to close the drawer at a
48
- desktop breakpoint. To focus a link destination after closing, prevent the
49
- cancelable `closeAutoFocus` event and focus that heading.
50
-
51
- `UiTabs` renders every labelled panel during SSR and initial hydration. After
52
- mounting, it adds keyboard-accessible tabs and hides inactive panels without
53
- unmounting them. A tab strip narrower than its tabs scrolls, fades each clipped
54
- edge, and keeps the selected tab in view when it resizes. Use unique item values
55
- and keep page anchor headings outside the panels. Re-align an initial hash after
56
- layout settles if enhancement changes the page height.
57
-
58
- Track page sections from a component's setup function:
59
-
60
- ```ts
61
- import { useActiveSection } from '@filipgutica/ui'
62
-
63
- const activeSection = useActiveSection({
64
- targetIds: ['overview', 'install', 'commands'],
65
- })
66
- ```
67
-
68
- The returned readonly ref contains the last supplied heading above the root's
69
- CSS `scroll-padding-top` inset, or the final section at the document bottom.
70
- IDs must remain stable and in page order; missing elements are ignored after
71
- mounting. Bind matching links to `aria-current="location"`. Scrolling, resizing,
72
- layout changes, completed transitions, and restored pages update the value. After
73
- a jump to a section, through a `#id` link, a hash change, or a deep link, that
74
- section stays current until the reader scrolls away from where the jump lands, so
75
- a page too short to bring it to the top still marks it.
76
- The composable does not change focus, the URL, or browser history, and removes
77
- its listeners and observer when the component unmounts. During SSR it returns
78
- the first supplied ID without accessing browser globals.
53
+ show an example, a sandbox, and the props, slots, and events of the components
54
+ in `@filipgutica/ui`. Read the
55
+ [Drawer](https://filipgutica.github.io/ui/#/components/drawer) and
56
+ [Tabs](https://filipgutica.github.io/ui/#/components/tabs) pages before you use
57
+ them for page navigation: they explain how each one behaves before and after
58
+ hydration.
79
59
 
80
60
  ## Themes
81
61
 
@@ -88,20 +68,47 @@ const theme = parseVsCodeTheme({ source, fileName })
88
68
  applySemanticTheme({ root: document.documentElement, theme })
89
69
  ```
90
70
 
91
- Themes supply colors; the library keeps typography, spacing, and component
92
- structure consistent. Open VSX search and imports are available through
93
- `createOpenVsxThemeService` from `@filipgutica/ui/open-vsx`. It runs on Node
94
- or in a browser with HTTPS and Web Crypto. Browser requests depend on the
95
- registry and its resource host allowing cross-origin access.
71
+ The theme must define a background color and a foreground color, such as
72
+ `editor.background` and `editor.foreground`.
73
+
74
+ To let people search for and import themes from Open VSX, use
75
+ `createOpenVsxThemeService` from `@filipgutica/ui/open-vsx`. It runs on Node or
76
+ in a browser with HTTPS and Web Crypto. In a browser, it works only when the
77
+ registry and its resource host allow cross-origin requests.
78
+
79
+ The theme search-and-install dialog on the docs site is not exported. Build
80
+ your own picker from the exported components and theme helpers.
96
81
 
97
- The gallery's search-and-install dialog is docs-only, not an exported component.
98
- Applications can build their own picker with the exported components and theme
99
- helpers. `UiSiteHeader` includes the built-in System, Light, and Dark control.
82
+ ## Track the current section
83
+
84
+ `useActiveSection` tells you which section of a long page the reader is on,
85
+ so you can mark its link. Call it from a component's setup function:
86
+
87
+ ```ts
88
+ import { useActiveSection } from '@filipgutica/ui'
89
+
90
+ const activeSection = useActiveSection({
91
+ targetIds: ['overview', 'install', 'commands'],
92
+ })
93
+ ```
100
94
 
101
- The install steps above import Graphite Indigo, the palette of the project
102
- sites, after `theme.css`. To keep the base theme instead, with blue actions and
103
- warm greys, leave out the `themes/graphite-indigo.css` import. Graphite Indigo
104
- stays opt-in: `theme.css` alone still gives the base theme.
95
+ Bind `aria-current="location"` to the link whose section matches the value.
96
+
97
+ - The returned readonly ref holds the last supplied heading above the root's
98
+ CSS `scroll-padding-top` inset. At the bottom of the document, it holds the
99
+ final section.
100
+ - Keep the IDs stable and in page order. IDs with no element on the page are
101
+ ignored after mounting.
102
+ - Scrolling, resizing, layout changes, completed transitions, and restored
103
+ pages update the value.
104
+ - After a jump to a section, through a `#id` link, a hash change, or a deep
105
+ link, that section stays current until the reader scrolls away from where the
106
+ jump lands. So a page too short to bring the section to the top still marks
107
+ it.
108
+ - It does not change focus, the URL, or browser history. It removes its
109
+ listeners and observer when the component unmounts.
110
+ - During SSR it returns the first supplied ID without accessing browser
111
+ globals.
105
112
 
106
113
  ## Project sites
107
114
 
@@ -1,7 +1,7 @@
1
1
  type __VLS_Props = {
2
2
  html: string;
3
3
  label: string;
4
- /** Scales the capture down to the available width instead of scrolling it. */
4
+ /** Fits the capture to the available width when it remains readable. */
5
5
  fit: boolean;
6
6
  };
7
7
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
package/dist/site.js CHANGED
@@ -73,24 +73,24 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
73
73
  href: "https://filipgutica.github.io/ui/",
74
74
  repository: "https://github.com/filipgutica/ui"
75
75
  }
76
- ], re = { class: "fg-site-header" }, V = { class: "fg-site-header__inner" }, H = ["aria-label"], U = {
76
+ ], re = { class: "fg-site-header" }, ie = { class: "fg-site-header__inner" }, V = ["aria-label"], H = {
77
77
  class: "fg-site-projects__menu fg-site-nav",
78
78
  "aria-label": "Projects"
79
- }, W = ["href", "aria-current"], G = { class: "fg-site-header__end" }, K = {
79
+ }, U = ["href", "aria-current"], W = { class: "fg-site-header__end" }, G = {
80
80
  key: 0,
81
81
  class: "fg-site-header__links",
82
82
  "aria-label": "Site links"
83
- }, q = ["href"], J = {
83
+ }, K = ["href"], q = {
84
84
  key: 0,
85
85
  class: "fg-site-appearance",
86
86
  role: "group",
87
87
  "aria-label": "Color theme"
88
- }, Y = [
88
+ }, J = [
89
89
  "aria-label",
90
90
  "title",
91
91
  "aria-pressed",
92
92
  "onClick"
93
- ], X = {
93
+ ], Y = {
94
94
  viewBox: "0 0 24 24",
95
95
  width: "18",
96
96
  height: "18",
@@ -98,10 +98,10 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
98
98
  stroke: "currentColor",
99
99
  "stroke-width": "1.5",
100
100
  "aria-hidden": "true"
101
- }, ie = {
101
+ }, ae = {
102
102
  key: 2,
103
103
  d: "M20.5 13.5A9 9 0 0 1 10.5 3a9 9 0 1 0 10 10.5Z"
104
- }, ae = /* @__PURE__ */ m({
104
+ }, oe = /* @__PURE__ */ m({
105
105
  __name: "UiSiteHeader",
106
106
  props: {
107
107
  project: {},
@@ -132,7 +132,7 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
132
132
  };
133
133
  return b(() => {
134
134
  i.value = !0, document.addEventListener("pointerdown", h);
135
- }), y(() => document.removeEventListener("pointerdown", h)), (s, f) => (S(), u("header", re, [d("div", V, [d("details", {
135
+ }), y(() => document.removeEventListener("pointerdown", h)), (s, f) => (S(), u("header", re, [d("div", ie, [d("details", {
136
136
  ref_key: "menu",
137
137
  ref: a,
138
138
  class: "fg-site-projects",
@@ -152,14 +152,14 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
152
152
  stroke: "currentColor",
153
153
  "stroke-width": "1.5",
154
154
  "aria-hidden": "true"
155
- }, [d("path", { d: "m4 6 4 4 4-4" })], -1)], 8, H), d("nav", U, [(S(!0), u(o, null, w(O(B), (t) => (S(), u("a", {
155
+ }, [d("path", { d: "m4 6 4 4 4-4" })], -1)], 8, V), d("nav", H, [(S(!0), u(o, null, w(O(B), (t) => (S(), u("a", {
156
156
  key: t.id,
157
157
  href: t.href,
158
158
  "aria-current": t.id === e.project ? "page" : void 0
159
- }, D(t.label), 9, W))), 128))])], 544), d("div", G, [e.links.length > 0 ? (S(), u("nav", K, [(S(!0), u(o, null, w(e.links, (e) => (S(), u("a", {
159
+ }, D(t.label), 9, U))), 128))])], 544), d("div", W, [e.links.length > 0 ? (S(), u("nav", G, [(S(!0), u(o, null, w(e.links, (e) => (S(), u("a", {
160
160
  key: e.href,
161
161
  href: e.href
162
- }, D(e.label), 9, q))), 128))])) : l("", !0), T(s.$slots, "default", {}, () => [i.value && O(r) ? (S(), u("div", J, [(S(), u(o, null, w(t, (e) => d("button", {
162
+ }, D(e.label), 9, K))), 128))])) : l("", !0), T(s.$slots, "default", {}, () => [i.value && O(r) ? (S(), u("div", q, [(S(), u(o, null, w(t, (e) => d("button", {
163
163
  key: e.value,
164
164
  type: "button",
165
165
  class: "fg-site-appearance__choice",
@@ -167,7 +167,7 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
167
167
  title: `${e.label} theme`,
168
168
  "aria-pressed": O(r).choice.value === e.value,
169
169
  onClick: (t) => O(r).chooseAppearance(e.value)
170
- }, [(S(), u("svg", X, [e.value === "system" ? (S(), u(o, { key: 0 }, [f[1] ||= d("rect", {
170
+ }, [(S(), u("svg", Y, [e.value === "system" ? (S(), u(o, { key: 0 }, [f[1] ||= d("rect", {
171
171
  x: "3",
172
172
  y: "4",
173
173
  width: "18",
@@ -177,13 +177,13 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
177
177
  cx: "12",
178
178
  cy: "12",
179
179
  r: "4"
180
- }, null, -1), f[4] ||= d("path", { d: "M12 2v2m0 16v2M2 12h2m16 0h2M5 5l1.5 1.5m11 11L19 19M5 19l1.5-1.5m11-11L19 5" }, null, -1)], 64)) : (S(), u("path", ie))]))], 8, Y)), 64))])) : l("", !0)])])])]));
180
+ }, null, -1), f[4] ||= d("path", { d: "M12 2v2m0 16v2M2 12h2m16 0h2M5 5l1.5 1.5m11 11L19 19M5 19l1.5-1.5m11-11L19 5" }, null, -1)], 64)) : (S(), u("path", ae))]))], 8, J)), 64))])) : l("", !0)])])])]));
181
181
  }
182
- }), oe = ["id"], se = ["role", "aria-labelledby"], ce = ["id"], le = [
182
+ }), se = ["id"], ce = ["role", "aria-labelledby"], le = ["id"], ue = [
183
183
  "href",
184
184
  "aria-current",
185
185
  "onClick"
186
- ], ue = { key: 0 }, Z = /* @__PURE__ */ m({
186
+ ], de = { key: 0 }, X = /* @__PURE__ */ m({
187
187
  __name: "SiteNavLinks",
188
188
  props: {
189
189
  contentId: {},
@@ -212,14 +212,14 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
212
212
  key: 0,
213
213
  id: i(t),
214
214
  class: "fg-site-rail__group-label"
215
- }, D(e.label), 9, ce)) : l("", !0), (S(!0), u(o, null, w(e.items, (e) => (S(), u("a", {
215
+ }, D(e.label), 9, le)) : l("", !0), (S(!0), u(o, null, w(e.items, (e) => (S(), u("a", {
216
216
  key: e.href,
217
217
  href: e.href,
218
218
  "aria-current": a(e),
219
219
  onClick: (t) => n("navigate", e.href, t)
220
- }, [e.code ? (S(), u("code", ue, D(e.label), 1)) : (S(), u(o, { key: 1 }, [f(D(e.label), 1)], 64))], 8, le))), 128))], 8, se))), 128))])], 8, oe));
220
+ }, [e.code ? (S(), u("code", de, D(e.label), 1)) : (S(), u(o, { key: 1 }, [f(D(e.label), 1)], 64))], 8, ue))), 128))], 8, ce))), 128))])], 8, se));
221
221
  }
222
- }), de = ["data-enhanced"], fe = ["open"], pe = ["aria-controls"], me = "(max-width: 800px)", he = /* @__PURE__ */ m({
222
+ }), fe = ["data-enhanced"], pe = ["open"], me = ["aria-controls"], he = "(max-width: 800px)", ge = /* @__PURE__ */ m({
223
223
  __name: "UiSiteNav",
224
224
  props: {
225
225
  label: {},
@@ -237,7 +237,7 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
237
237
  _ ? (e.preventDefault(), _.tabIndex = -1, _.focus({ preventScroll: !0 }), _ = null) : s.value || (e.preventDefault(), y());
238
238
  };
239
239
  return b(() => {
240
- h = matchMedia(me), w(), o.value = !0, h.addEventListener("change", w);
240
+ h = matchMedia(he), w(), o.value = !0, h.addEventListener("change", w);
241
241
  }), x(() => h?.removeEventListener("change", w)), (a, h) => (S(), u("div", {
242
242
  ref_key: "root",
243
243
  ref: n,
@@ -250,7 +250,7 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
250
250
  }, [d("summary", {
251
251
  class: "fg-site-rail__summary",
252
252
  "aria-controls": O(t)
253
- }, D(e.label), 9, pe), p(Z, {
253
+ }, D(e.label), 9, me), p(X, {
254
254
  "content-id": O(t),
255
255
  label: e.label,
256
256
  groups: e.groups,
@@ -260,7 +260,7 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
260
260
  "label",
261
261
  "groups",
262
262
  "active-id"
263
- ])], 8, fe)) : (S(), c(i, {
263
+ ])], 8, pe)) : (S(), c(i, {
264
264
  key: 1,
265
265
  open: l.value,
266
266
  "onUpdate:open": h[0] ||= (e) => l.value = e,
@@ -275,7 +275,7 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
275
275
  default: M(() => [f(D(e.label), 1)]),
276
276
  _: 1
277
277
  })]),
278
- default: M(() => [p(Z, {
278
+ default: M(() => [p(X, {
279
279
  "content-id": O(t),
280
280
  label: e.label,
281
281
  groups: e.groups,
@@ -288,14 +288,14 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
288
288
  "active-id"
289
289
  ])]),
290
290
  _: 1
291
- }, 8, ["open", "title"]))], 8, de));
291
+ }, 8, ["open", "title"]))], 8, fe));
292
292
  }
293
- }), ge = [
293
+ }), _e = [
294
294
  "aria-label",
295
295
  "tabindex",
296
296
  "data-fit",
297
297
  "innerHTML"
298
- ], Q = /* @__PURE__ */ m({
298
+ ], Z = .7, Q = /* @__PURE__ */ m({
299
299
  __name: "CaptureScreen",
300
300
  props: {
301
301
  html: {},
@@ -303,35 +303,37 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
303
303
  fit: { type: Boolean }
304
304
  },
305
305
  setup(e) {
306
- let t = C(), n = C(1), r = C(), i, a = () => {
306
+ let t = C(), n = C(1), r = C(), i = s(() => r.value !== void 0), a, o = () => {
307
307
  if (!e.fit) {
308
308
  r.value = void 0, n.value = 1;
309
309
  return;
310
310
  }
311
311
  let i = t.value, a = i?.firstElementChild;
312
- !i?.clientWidth || !(a instanceof HTMLElement) || !a.offsetWidth || (n.value = Math.min(1, i.clientWidth / a.offsetWidth), r.value = Math.ceil(a.offsetHeight * n.value) + i.offsetHeight - i.clientHeight);
312
+ if (!i?.clientWidth || !(a instanceof HTMLElement) || !a.offsetWidth) return;
313
+ let o = Math.min(1, i.clientWidth / a.offsetWidth);
314
+ o >= Z && (i.scrollLeft = 0), n.value = o >= Z ? o : 1, r.value = o >= Z ? Math.ceil(a.offsetHeight * n.value) + i.offsetHeight - i.clientHeight : void 0;
313
315
  };
314
- return j(() => [e.fit, e.html], a, { flush: "post" }), b(() => {
315
- typeof ResizeObserver < "u" && (i = new ResizeObserver(a), t.value && i.observe(t.value)), a();
316
- }), x(() => i?.disconnect()), (i, a) => (S(), u("div", {
316
+ return j(() => [e.fit, e.html], o, { flush: "post" }), b(() => {
317
+ typeof ResizeObserver < "u" && (a = new ResizeObserver(o), t.value && a.observe(t.value)), o();
318
+ }), x(() => a?.disconnect()), (a, o) => (S(), u("div", {
317
319
  ref_key: "container",
318
320
  ref: t,
319
321
  class: "fg-capture__screen",
320
322
  role: "region",
321
323
  "aria-label": e.label,
322
- tabindex: e.fit ? void 0 : 0,
323
- "data-fit": e.fit || void 0,
324
+ tabindex: i.value ? void 0 : 0,
325
+ "data-fit": i.value || void 0,
324
326
  style: v({
325
327
  height: r.value === void 0 ? void 0 : `${r.value}px`,
326
328
  "--capture-scale": n.value
327
329
  }),
328
330
  innerHTML: e.html
329
- }, null, 12, ge));
331
+ }, null, 12, _e));
330
332
  }
331
- }), _e = ["data-enhanced"], ve = ["aria-label"], ye = ["id"], be = { class: "fg-capture__preview" }, xe = ["title", "onClick"], Se = {
333
+ }), ve = ["data-enhanced"], ye = ["aria-label"], be = ["id"], xe = { class: "fg-capture__preview" }, Se = ["title", "onClick"], Ce = {
332
334
  key: 0,
333
335
  class: "fg-capture__caption"
334
- }, Ce = { key: 0 }, we = /* @__PURE__ */ m({
336
+ }, we = { key: 0 }, Te = /* @__PURE__ */ m({
335
337
  __name: "UiCapture",
336
338
  props: /*@__PURE__*/ h({
337
339
  items: {},
@@ -394,7 +396,7 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
394
396
  stroke: "currentColor",
395
397
  "stroke-width": "1.5",
396
398
  "aria-hidden": "true"
397
- }, [d("path", { d: "M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7" })], -1), d("span", { class: "fg-capture__full-label" }, "Full size", -1)]], 8, ve)) : l("", !0),
399
+ }, [d("path", { d: "M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7" })], -1), d("span", { class: "fg-capture__full-label" }, "Full size", -1)]], 8, ye)) : l("", !0),
398
400
  p(n, {
399
401
  "model-value": h.value,
400
402
  items: m.value,
@@ -405,7 +407,7 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
405
407
  key: 0,
406
408
  id: `${e.idPrefix}${n.id}`,
407
409
  class: "fg-capture__figure"
408
- }, [d("div", be, [p(Q, {
410
+ }, [d("div", xe, [p(Q, {
409
411
  html: n.html,
410
412
  label: `Terminal capture: ${n.label}`,
411
413
  fit: a.value
@@ -421,7 +423,7 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
421
423
  "aria-hidden": "true",
422
424
  title: `Open ${n.label} capture at full size`,
423
425
  onClick: (e) => j(n)
424
- }, null, 8, xe)) : l("", !0)]), n.command || s.$slots.caption ? (S(), u("figcaption", Se, [n.command ? (S(), u("code", Ce, D(n.command), 1)) : l("", !0), T(s.$slots, "caption", { item: n })])) : l("", !0)], 8, ye)) : l("", !0)], 64))), 128))]),
426
+ }, null, 8, Se)) : l("", !0)]), n.command || s.$slots.caption ? (S(), u("figcaption", Ce, [n.command ? (S(), u("code", we, D(n.command), 1)) : l("", !0), T(s.$slots, "caption", { item: n })])) : l("", !0)], 8, be)) : l("", !0)], 64))), 128))]),
425
427
  _: 3
426
428
  }, 8, [
427
429
  "model-value",
@@ -452,26 +454,26 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
452
454
  }, null, 8, ["html", "label"])) : l("", !0)]),
453
455
  _: 1
454
456
  }, 8, ["open", "title"])
455
- ], 8, _e));
457
+ ], 8, ve));
456
458
  }
457
- }), Te = { class: "fg-command-list" }, Ee = { class: "fg-command-list__table" }, De = { scope: "col" }, $ = { scope: "col" }, Oe = { key: 0 }, ke = { class: "fg-command-list__command" }, Ae = { class: "fg-command-list__description" }, je = {
459
+ }), Ee = { class: "fg-command-list" }, De = { class: "fg-command-list__table" }, $ = { scope: "col" }, Oe = { scope: "col" }, ke = { key: 0 }, Ae = { class: "fg-command-list__command" }, je = { class: "fg-command-list__description" }, Me = {
458
460
  key: 0,
459
461
  class: "fg-command-list__action"
460
- }, Me = [
462
+ }, Ne = [
461
463
  "disabled",
462
464
  "aria-label",
463
465
  "title",
464
466
  "onClick"
465
- ], Ne = {
467
+ ], Pe = {
466
468
  viewBox: "0 0 24 24",
467
469
  fill: "none",
468
470
  stroke: "currentColor",
469
471
  "stroke-width": "1.5",
470
472
  "aria-hidden": "true"
471
- }, Pe = {
473
+ }, Fe = {
472
474
  key: 0,
473
475
  d: "m5 12 4 4L19 6"
474
- }, Fe = /* @__PURE__ */ m({
476
+ }, Ie = /* @__PURE__ */ m({
475
477
  __name: "UiCommandList",
476
478
  props: {
477
479
  items: {},
@@ -491,14 +493,14 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
491
493
  unavailableMessage: "Clipboard unavailable. Select the command to copy it.",
492
494
  failureMessage: "Could not copy. Select the command to copy it."
493
495
  }), p = C(""), m = s(() => a.value || (r.value ? `${p.value} copied to clipboard.` : "")), h = (e) => (p.value = e, c(e));
494
- return (e, s) => (S(), u("div", Te, [d("table", Ee, [d("thead", null, [d("tr", null, [
495
- d("th", De, D(t.commandHeader), 1),
496
- d("th", $, D(t.descriptionHeader), 1),
497
- t.copyable ? (S(), u("td", Oe)) : l("", !0)
496
+ return (e, s) => (S(), u("div", Ee, [d("table", De, [d("thead", null, [d("tr", null, [
497
+ d("th", $, D(t.commandHeader), 1),
498
+ d("th", Oe, D(t.descriptionHeader), 1),
499
+ t.copyable ? (S(), u("td", ke)) : l("", !0)
498
500
  ])]), d("tbody", null, [(S(!0), u(o, null, w(t.items, (c) => (S(), u("tr", { key: c.command }, [
499
- d("td", ke, [T(e.$slots, "command", { item: c }, () => [d("code", null, D(c.command), 1)])]),
500
- d("td", Ae, [T(e.$slots, "description", { item: c }, () => [f(D(c.description), 1)])]),
501
- t.copyable ? (S(), u("td", je, [n.value ? (S(), u("button", {
501
+ d("td", Ae, [T(e.$slots, "command", { item: c }, () => [d("code", null, D(c.command), 1)])]),
502
+ d("td", je, [T(e.$slots, "description", { item: c }, () => [f(D(c.description), 1)])]),
503
+ t.copyable ? (S(), u("td", Me, [n.value ? (S(), u("button", {
502
504
  key: 0,
503
505
  type: "button",
504
506
  class: "fg-command-list__copy",
@@ -506,13 +508,13 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
506
508
  "aria-label": `Copy ${c.command}`,
507
509
  title: `Copy ${c.command}`,
508
510
  onClick: (e) => h(c.command)
509
- }, [(S(), u("svg", Ne, [O(r) && !O(a) && p.value === c.command ? (S(), u("path", Pe)) : (S(), u(o, { key: 1 }, [s[0] ||= d("rect", {
511
+ }, [(S(), u("svg", Pe, [O(r) && !O(a) && p.value === c.command ? (S(), u("path", Fe)) : (S(), u(o, { key: 1 }, [s[0] ||= d("rect", {
510
512
  x: "8",
511
513
  y: "8",
512
514
  width: "12",
513
515
  height: "13",
514
516
  rx: "2"
515
- }, null, -1), s[1] ||= d("path", { d: "M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3" }, null, -1)], 64))]))], 8, Me)) : l("", !0)])) : l("", !0)
517
+ }, null, -1), s[1] ||= d("path", { d: "M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3" }, null, -1)], 64))]))], 8, Ne)) : l("", !0)])) : l("", !0)
516
518
  ]))), 128))])]), d("span", {
517
519
  class: _(["fg-command-list__status", { "fg-site-sr-only": !O(a) }]),
518
520
  role: "status"
@@ -520,4 +522,4 @@ var I = "tool-site-theme", L = (e) => e === "system" || e === "light" || e === "
520
522
  }
521
523
  });
522
524
  //#endregion
523
- export { I as SITE_APPEARANCE_KEY, B as SITE_PROJECTS, we as UiCapture, Fe as UiCommandList, ae as UiSiteHeader, he as UiSiteNav, R as applySiteAppearance, ne as siteThemeBootScript, z as useSiteAppearance };
525
+ export { I as SITE_APPEARANCE_KEY, B as SITE_PROJECTS, Te as UiCapture, Ie as UiCommandList, oe as UiSiteHeader, ge as UiSiteNav, R as applySiteAppearance, ne as siteThemeBootScript, z as useSiteAppearance };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@filipgutica/ui",
3
- "version": "1.11.0",
3
+ "version": "1.11.2",
4
4
  "description": "A compact Vue design system with Tailwind CSS and VS Code theme support",
5
5
  "type": "module",
6
6
  "sideEffects": [
package/src/site.css CHANGED
@@ -222,6 +222,8 @@
222
222
  flex: none;
223
223
  border: 1px solid var(--color-border-subtle);
224
224
  border-radius: var(--radius-control);
225
+ /* A muted track, so the pressed choice reads as a raised thumb in both themes. */
226
+ background: var(--color-surface-muted);
225
227
  }
226
228
  .fg-site-appearance__choice {
227
229
  display: inline-flex;
@@ -240,7 +242,16 @@
240
242
  .fg-site-appearance__choice:first-child { border-radius: var(--radius-control) 0 0 var(--radius-control); }
241
243
  .fg-site-appearance__choice:last-child { border-radius: 0 var(--radius-control) var(--radius-control) 0; }
242
244
  .fg-site-appearance__choice:hover { color: var(--color-text); background: var(--color-row-hover); }
243
- .fg-site-appearance__choice[aria-pressed="true"] { color: var(--color-text); background: var(--color-row-selected); }
245
+ /* The ring gives the pressed choice 3:1 against the track in both themes (WCAG 1.4.11). */
246
+ .fg-site-appearance__choice[aria-pressed="true"] {
247
+ color: var(--color-text);
248
+ background: var(--color-row-selected);
249
+ box-shadow: inset 0 0 0 1px var(--color-border);
250
+ }
251
+ /* Forced colors drop the fill and the ring, so the pressed choice takes the system highlight. */
252
+ @media (forced-colors: active) {
253
+ .fg-site-appearance__choice[aria-pressed="true"] { forced-color-adjust: none; color: HighlightText; background: Highlight; }
254
+ }
244
255
 
245
256
  /* Focus is a 2px outline outside the control, which forced-colors mode keeps. An
246
257
  * inset ring is used only where a scrolling ancestor would clip an outer one. */
@@ -367,7 +378,7 @@
367
378
  .fg-capture__screen .tb1 { background: var(--t1, #f0776c); }
368
379
  .fg-capture__screen .tb2 { background: var(--t2, #4cc38a); }
369
380
  .fg-capture__screen .tb3 { background: var(--t3, #e2a54b); }
370
- .fg-capture__screen .tb4 { background: var(--t4, #7d97ff); }
381
+ .fg-capture__screen .tb4 { background: var(--t4, #3354c4); }
371
382
  .fg-capture__screen .tb5 { background: var(--t5, #c792ea); }
372
383
  .fg-capture__screen .tb6 { background: var(--t6, #5fd3d9); }
373
384
  .fg-capture__screen .tb7 { background: var(--t7, #c8c8d0); }
@@ -382,6 +393,8 @@
382
393
  .fg-capture__screen .tf-dim { color: var(--t-dim, #a0a0ab); }
383
394
  .fg-capture__screen .tf-bg { color: var(--t-bg, #131316); }
384
395
  .fg-capture__screen .tb-fg { background: var(--t-fg, #e6e6ea); }
396
+ /* Text on the light selection row stays dark, whatever its own colour class. */
397
+ .fg-capture__screen .tb8 { color: #1b1b1f; }
385
398
  .fg-capture__screen .tbold { font-weight: var(--weight-semibold); }
386
399
  .fg-capture__screen .tital { font-style: italic; }
387
400
  .fg-capture__screen .tul { text-decoration: underline; text-underline-offset: 0.18em; }
@@ -398,6 +411,8 @@
398
411
  background: transparent;
399
412
  cursor: zoom-in;
400
413
  }
414
+ /* Leave pointer gestures to the screen when the capture needs to scroll. */
415
+ .fg-capture__screen:not([data-fit]) + .fg-capture__expand { pointer-events: none; }
401
416
  .fg-capture__caption {
402
417
  display: grid;
403
418
  justify-items: start;
@@ -612,6 +627,12 @@
612
627
  }
613
628
  }
614
629
  @media (max-width: 640px) {
630
+ /* Phones: three or more site links do not fit beside the project menu, so the menu and
631
+ * the header controls share the first row and the links take the second. That keeps the
632
+ * header to two rows. With fewer links, the header keeps its wrapping row. */
633
+ .fg-site-header__inner:has(.fg-site-header__links > :nth-child(3)) .fg-site-header__end { display: contents; }
634
+ .fg-site-header__inner:has(.fg-site-header__links > :nth-child(3)) .fg-site-projects { margin-inline-end: auto; }
635
+ .fg-site-header__inner:has(.fg-site-header__links > :nth-child(3)) .fg-site-header__links { order: 1; flex-basis: 100%; }
615
636
  .fg-site-docs { padding-inline: var(--space-4); }
616
637
  /* Docs-shell pages keep one header row on phones and repeat these links in their footer. */
617
638
  .fg-site-docs .fg-site-header__links { display: none; }