@meddleware/ui 0.1.11 → 0.1.13

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,6 +1,6 @@
1
1
  # @meddleware/ui
2
2
 
3
- Shared Vue 3 component library for the `@meddleware` UIs. Delivers a layout shell (header, sidebar, footer), colour-mode control, and UI primitives — all styled with [`@meddleware/design-tokens`](https://www.npmjs.com/package/@meddleware/design-tokens) (Oxblood / Indigo palette).
3
+ Shared Vue 3 component library for the `@meddleware` UIs. Delivers a layout shell (header, sidebar, footer), colour-mode control, and UI primitives — all styled with [`@meddleware/design-tokens`](https://www.npmjs.com/package/@meddleware/design-tokens): a warm-neutral canvas with functional primary/rainbow accents, sacred-geometry (φ/Fibonacci) type + spacing scales, and optional seasonal theming.
4
4
 
5
5
  ## Installation
6
6
 
@@ -14,18 +14,39 @@ yarn add @meddleware/ui @meddleware/design-tokens
14
14
 
15
15
  ## Setup
16
16
 
17
- Import both CSS files once at your application entry point:
17
+ Import the CSS once at your application entry point:
18
18
 
19
19
  ```ts
20
20
  // src/main.ts (or equivalent)
21
- import '@meddleware/design-tokens/tokens.css' // required — registers CSS custom properties
22
- import '@meddleware/ui/base.css' // optional — element-level defaults
21
+ import '@meddleware/design-tokens/tokens.css' // required — registers CSS custom properties
22
+ import '@meddleware/design-tokens/seasons.css' // optional — enables data-season theming
23
+ import '@meddleware/ui/base.css' // optional — element defaults + scale + utilities
23
24
  import { createApp } from 'vue'
24
25
  import App from './App.vue'
25
26
 
26
27
  createApp(App).mount('#app')
27
28
  ```
28
29
 
30
+ ## Styling, scales & utilities
31
+
32
+ Components consume the design-token **role** tokens (`--accent`, `--warning`, `--focus-ring`, …) and
33
+ the **sacred-geometry scales** (`--space-*`, `--font-size-*`, `--leading-*`, `--tracking-*`) — never
34
+ colour-named or magic-number values. Focus states use the dedicated `--focus-ring` (blue) so they are
35
+ always visible and never read as an error. Component **props and accessibility are unchanged** by the
36
+ theming system — only the styling tokens they resolve to.
37
+
38
+ `base.css` also ships opt-in utilities (respecting `prefers-reduced-motion`):
39
+
40
+ | Utility | Purpose |
41
+ | --- | --- |
42
+ | `.mw-noise` | Subtle CSS-native SVG-noise background layer (controlled imperfection over flat polish). |
43
+ | `.mw-mono` | Monospace font (`--mw-font-mono`). |
44
+ | `.mw-spinner` | Sigil-like ring loader that settles into a simple form. |
45
+ | `.mw-hand-drawn` | Empty positioned **placeholder hook** for bespoke hand-drawn SVG/canvas added later — drop an `<svg>`/`<canvas>` inside, or target `.mw-hand-drawn > svg` from your app. |
46
+
47
+ Seasonal theming is entirely in `@meddleware/design-tokens` (import `seasons.css` + set
48
+ `data-season`); the components pick it up automatically through the role tokens.
49
+
29
50
  ## Exports
30
51
 
31
52
  ### Layout shell
package/dist/base.css CHANGED
@@ -1,2 +1,2 @@
1
- *,:before,:after{box-sizing:border-box}html,body{margin:0;padding:0}body{background:var(--bg);min-height:100vh;color:var(--text);font-family:var(--mw-font-sans);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;font-size:16px;line-height:1.55}:where(ul,ol):where([role=list],[role=listitem]){margin:0;padding:0;list-style:none}:where(img,svg,video,canvas){max-width:100%;display:block}:where(fieldset){border:0;margin:0;padding:0}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}:where(button,input,select,textarea){font-family:inherit}:where(a,button,input,select,textarea):focus-visible{outline:2px solid var(--accent);outline-offset:2px}.mw-mono{font-family:var(--mw-font-mono)}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:.001ms!important;animation-duration:.001ms!important}}.mw-header[data-v-ad7cd1c5]{height:var(--mw-header-height,56px);background:var(--_bg);color:var(--_text);border-bottom:1px solid var(--_border);font-family:var(--mw-font-sans);align-items:center;gap:1rem;padding:0 1rem;display:flex}.mw-header--sticky[data-v-ad7cd1c5]{z-index:50;position:sticky;top:0}.mw-header__brand[data-v-ad7cd1c5]{letter-spacing:.01em;white-space:nowrap;align-items:center;gap:.5rem;font-weight:650;display:flex}.mw-header__center[data-v-ad7cd1c5]{flex:auto;min-width:0}.mw-header__actions[data-v-ad7cd1c5]{align-items:center;gap:.5rem;margin-left:auto;display:flex}.mw-sidebar[data-v-23c3c7f3]{height:100%;width:var(--mw-sidebar-width,240px);background:var(--_bg);color:var(--_text);border-right:1px solid var(--_border);font-family:var(--mw-font-sans);flex-direction:column;display:flex}.mw-sidebar__head[data-v-23c3c7f3]{border-bottom:1px solid var(--_border);padding:1rem}.mw-sidebar__nav[data-v-23c3c7f3]{flex-direction:column;flex:auto;gap:.25rem;padding:.75rem;display:flex;overflow-y:auto}.mw-sidebar__body[data-v-23c3c7f3]{flex-shrink:0}.mw-sidebar__foot[data-v-23c3c7f3]{border-top:1px solid var(--_border);color:var(--_muted);padding:.75rem 1rem;font-size:.85rem}.mw-footer[data-v-c646b8e1]{background:var(--_bg);color:var(--_muted);border-top:1px solid var(--_border);font-family:var(--mw-font-sans);flex-wrap:wrap;align-items:center;gap:1rem;padding:.75rem 1rem;font-size:.85rem;display:flex}.mw-footer--transparent[data-v-c646b8e1]{border-top:1px solid var(--border)}.mw-footer__center[data-v-c646b8e1]{flex:auto;min-width:0}.mw-footer__end[data-v-c646b8e1]{margin-left:auto}.mw-mode[data-v-186f8312]{border:1px solid var(--_border,var(--border));border-radius:var(--mw-radius,10px);background:color-mix(in srgb, var(--_text,var(--text)) 6%, transparent);gap:2px;padding:2px;display:inline-flex}.mw-mode__btn[data-v-186f8312]{border-radius:calc(var(--mw-radius,10px) - 3px);color:var(--_text,var(--text));font:inherit;cursor:pointer;opacity:.7;background:0 0;border:0;align-items:center;gap:.35rem;padding:.25rem .5rem;font-size:.8rem;display:inline-flex}.mw-mode__btn[data-v-186f8312]:hover{opacity:1}.mw-mode__btn.is-active[data-v-186f8312]{opacity:1;background:color-mix(in srgb, var(--_text,var(--text)) 14%, transparent)}.mw-mode__btn[data-v-186f8312]:focus-visible{outline:2px solid var(--accent);outline-offset:1px}@media (width<=640px){.mw-mode__label[data-v-186f8312]{display:none}}.mw-btn[data-v-299de2c3]{border-radius:var(--mw-radius,10px);font:inherit;cursor:pointer;border:1px solid #0000;justify-content:center;align-items:center;gap:.4rem;padding:.5rem .9rem;font-weight:550;transition:filter .12s,background .12s;display:inline-flex}.mw-btn[data-v-299de2c3]:disabled{opacity:.55;cursor:not-allowed}.mw-btn[data-v-299de2c3]:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.mw-btn--primary[data-v-299de2c3]{background:var(--accent);color:var(--accent-contrast)}.mw-btn--primary[data-v-299de2c3]:not(:disabled):hover{filter:brightness(1.08)}.mw-btn--secondary[data-v-299de2c3]{background:var(--secondary);color:var(--secondary-contrast)}.mw-btn--secondary[data-v-299de2c3]:not(:disabled):hover{filter:brightness(1.08)}.mw-btn--ghost[data-v-299de2c3]{color:var(--text);border-color:var(--border);background:0 0}.mw-btn--ghost[data-v-299de2c3]:not(:disabled):hover{background:var(--lift)}.mw-btn--danger[data-v-299de2c3]{background:var(--danger);color:var(--mw-neutral-000)}.mw-btn--danger[data-v-299de2c3]:not(:disabled):hover{filter:brightness(1.08)}.mw-card[data-v-fd2fd7ab]{background:var(--surface);border:1px solid var(--border);border-radius:var(--mw-radius,10px);color:var(--text);overflow:hidden}.mw-card__header[data-v-fd2fd7ab]{border-bottom:1px solid var(--border);padding:.85rem 1rem}.mw-card__title[data-v-fd2fd7ab]{margin:0;font-size:1rem;font-weight:600}.mw-card__body[data-v-fd2fd7ab]{padding:1rem}.mw-card__footer[data-v-fd2fd7ab]{border-top:1px solid var(--border);background:var(--lift);padding:.75rem 1rem}.mw-select[data-v-b9468387]{position:relative}.mw-select select[data-v-b9468387]{appearance:none;background:var(--surface);width:100%;color:var(--text);border:1px solid var(--border);border-radius:var(--mw-radius,10px);font:inherit;padding:.5rem 2.5rem .5rem .7rem}.mw-select select[data-v-b9468387]:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.mw-select__chevron[data-v-b9468387]{pointer-events:none;stroke:var(--muted);stroke-width:1.5px;fill:none;position:absolute;top:50%;right:.75rem;transform:translateY(-50%)}.mw-notice[data-v-6f53d0be]{border:1px solid var(--border);border-radius:var(--mw-radius,10px);background:var(--lift);color:var(--text);border-left-width:3px;margin:0;padding:.6rem .8rem;font-size:.9rem}.mw-notice--error[data-v-6f53d0be]{border-left-color:var(--danger);color:var(--danger);background:color-mix(in srgb, var(--danger) 8%, transparent)}.mw-notice--ok[data-v-6f53d0be]{border-left-color:var(--ok);color:var(--ok);background:color-mix(in srgb, var(--ok) 8%, transparent)}.sidebar-item[data-v-e15afdf7]{border-radius:var(--mw-radius,10px);width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer;opacity:.85;background:0 0;border:0;align-items:center;gap:.6rem;padding:.55rem .7rem;display:flex}.sidebar-item[data-v-e15afdf7]:hover:not(:disabled){background:color-mix(in srgb, currentColor 10%, transparent);opacity:1}.sidebar-item.is-active[data-v-e15afdf7]{background:color-mix(in srgb, var(--accent) 26%, transparent);opacity:1}.sidebar-item[data-v-e15afdf7]:disabled{cursor:default}.sidebar-item[data-v-e15afdf7]:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.sidebar-item__icon[data-v-e15afdf7]{text-align:center;width:1.1rem}.mw-tab-nav[data-v-88758ede]{border-bottom:2px solid var(--border);gap:.25rem;display:flex}.mw-tab-nav__tab[data-v-88758ede]{appearance:none;cursor:pointer;color:var(--muted);background:0 0;border:none;border-bottom:2px solid #0000;margin-bottom:-2px;padding:.5rem 1rem;font-size:.95rem;transition:color .1s,border-color .1s}.mw-tab-nav__tab[data-v-88758ede]:hover{color:var(--text)}.mw-tab-nav__tab--active[data-v-88758ede]{border-bottom-color:var(--accent);color:var(--text);font-weight:600}.ca-root[data-v-08c3420b]{align-items:center;gap:.35em;display:inline-flex}.ca-value[data-v-08c3420b]{font-family:monospace;font-size:inherit}.ca-copy[data-v-08c3420b]{color:var(--muted,#888);cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;padding:0;line-height:1;transition:color .15s;display:inline-flex}.ca-copy[data-v-08c3420b]:hover{color:var(--text,#f0f0f0)}.ca-copy--copied[data-v-08c3420b]{color:var(--accent,#6366f1)}.ca-icon[data-v-08c3420b]{display:block}.el-root[data-v-7a44607e]{font-family:monospace;font-size:inherit;color:var(--accent,#6366f1);text-underline-offset:2px;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;transition:color .15s}.el-root[data-v-7a44607e]:hover{color:var(--text,#f0f0f0);text-decoration-style:solid}.status-widget[data-v-20197983]{color:inherit;opacity:.85;align-items:center;gap:.4rem;font-size:.8rem;text-decoration:none;display:inline-flex}.status-widget[data-v-20197983]:hover{opacity:1}.status-widget__dot[data-v-20197983]{border-radius:50%;flex-shrink:0;width:.55rem;height:.55rem}.status-widget--ok .status-widget__dot[data-v-20197983]{background:var(--mw-ok-500)}.status-widget--degraded .status-widget__dot[data-v-20197983]{background:var(--mw-gold-500)}.status-widget--error .status-widget__dot[data-v-20197983]{background:var(--mw-danger-500)}
1
+ *,:before,:after{box-sizing:border-box}html,body{margin:0;padding:0}body{background:var(--bg);min-height:100vh;color:var(--text);font-family:var(--mw-font-sans);font-size:var(--font-size-base);line-height:var(--leading-normal);letter-spacing:var(--tracking-normal);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility}:where(ul,ol):where([role=list],[role=listitem]){margin:0;padding:0;list-style:none}:where(img,svg,video,canvas){max-width:100%;display:block}:where(fieldset){border:0;margin:0;padding:0}a{color:var(--accent);transition:color var(--transition-base);text-decoration:none}a:hover{text-decoration:underline}:where(button,input,select,textarea){font-family:inherit}:where(a,button,input,select,textarea):focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.mw-mono{font-family:var(--mw-font-mono)}.mw-noise{isolation:isolate;position:relative}.mw-noise:before{content:"";z-index:-1;background-image:var(--noise-overlay);opacity:var(--noise-opacity);pointer-events:none;background-repeat:repeat;position:absolute;inset:0}.mw-hand-drawn{display:inline-flex;position:relative}.mw-spinner{width:var(--space-sm);height:var(--space-sm);border:2px solid var(--border);border-top-color:var(--accent);animation:mw-spin var(--transition-slow) linear infinite;border-radius:50%;display:inline-block}@keyframes mw-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:.001ms!important;animation-duration:.001ms!important}.mw-spinner{border-top-color:var(--accent);animation:none}}.mw-header[data-v-9b9826d5]{align-items:center;gap:var(--space-sm);height:var(--mw-header-height,56px);padding:0 var(--space-sm);background:var(--_bg);color:var(--_text);border-bottom:1px solid var(--_border);font-family:var(--mw-font-sans);display:flex}.mw-header--sticky[data-v-9b9826d5]{z-index:50;position:sticky;top:0}.mw-header__brand[data-v-9b9826d5]{align-items:center;gap:var(--space-2xs);letter-spacing:var(--tracking-wide);white-space:nowrap;font-weight:650;display:flex}.mw-header__center[data-v-9b9826d5]{flex:auto;min-width:0}.mw-header__actions[data-v-9b9826d5]{align-items:center;gap:var(--space-2xs);margin-left:auto;display:flex}.mw-sidebar[data-v-a2b8405c]{height:100%;width:var(--mw-sidebar-width,240px);background:var(--_bg);color:var(--_text);border-right:1px solid var(--_border);font-family:var(--mw-font-sans);flex-direction:column;display:flex}.mw-sidebar__head[data-v-a2b8405c]{padding:var(--space-sm);border-bottom:1px solid var(--_border)}.mw-sidebar__nav[data-v-a2b8405c]{gap:var(--space-3xs);padding:var(--space-xs);flex-direction:column;flex:auto;display:flex;overflow-y:auto}.mw-sidebar__body[data-v-a2b8405c]{flex-shrink:0}.mw-sidebar__foot[data-v-a2b8405c]{padding:var(--space-xs) var(--space-sm);border-top:1px solid var(--_border);color:var(--_muted);font-size:var(--font-size-sm)}.mw-footer[data-v-979579a9]{align-items:center;gap:var(--space-sm);padding:var(--space-xs) var(--space-sm);background:var(--_bg);color:var(--_muted);border-top:1px solid var(--_border);font-family:var(--mw-font-sans);font-size:var(--font-size-sm);flex-wrap:wrap;display:flex}.mw-footer--transparent[data-v-979579a9]{border-top:1px solid var(--border)}.mw-footer__center[data-v-979579a9]{flex:auto;min-width:0}.mw-footer__end[data-v-979579a9]{align-items:center;gap:var(--space-sm);margin-left:auto;display:flex}.mw-footer__docs-link[data-v-979579a9]{color:inherit;text-underline-offset:2px;opacity:.7;text-decoration:underline}.mw-footer__docs-link[data-v-979579a9]:hover{opacity:1}.mw-mode[data-v-e2ea0d79]{border:1px solid var(--_border,var(--border));border-radius:var(--radius);background:color-mix(in srgb, var(--_text,var(--text)) 6%, transparent);gap:2px;padding:2px;display:inline-flex}.mw-mode__btn[data-v-e2ea0d79]{align-items:center;gap:var(--space-3xs);padding:var(--space-3xs) var(--space-2xs);border-radius:calc(var(--radius) - 3px);color:var(--_text,var(--text));font:inherit;font-size:var(--font-size-sm);cursor:pointer;opacity:.7;transition:opacity var(--transition-base), background var(--transition-base);background:0 0;border:0;display:inline-flex}.mw-mode__btn[data-v-e2ea0d79]:hover{opacity:1}.mw-mode__btn.is-active[data-v-e2ea0d79]{opacity:1;background:color-mix(in srgb, var(--_text,var(--text)) 14%, transparent)}.mw-mode__btn[data-v-e2ea0d79]:focus-visible{outline:2px solid var(--focus-ring);outline-offset:1px}@media (width<=640px){.mw-mode__label[data-v-e2ea0d79]{display:none}}.mw-btn[data-v-bc33cc23]{justify-content:center;align-items:center;gap:var(--space-2xs);padding:var(--space-2xs) var(--space-xs);border-radius:var(--radius);font:inherit;cursor:pointer;transition:filter var(--transition-base), background var(--transition-base);border:1px solid #0000;font-weight:550;display:inline-flex}.mw-btn[data-v-bc33cc23]:disabled{opacity:.55;cursor:not-allowed}.mw-btn[data-v-bc33cc23]:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.mw-btn--primary[data-v-bc33cc23]{background:var(--accent);color:var(--accent-contrast)}.mw-btn--primary[data-v-bc33cc23]:not(:disabled):hover{filter:brightness(1.08)}.mw-btn--secondary[data-v-bc33cc23]{background:var(--secondary);color:var(--secondary-contrast)}.mw-btn--secondary[data-v-bc33cc23]:not(:disabled):hover{filter:brightness(1.08)}.mw-btn--ghost[data-v-bc33cc23]{color:var(--text);border-color:var(--border);background:0 0}.mw-btn--ghost[data-v-bc33cc23]:not(:disabled):hover{background:var(--lift)}.mw-btn--danger[data-v-bc33cc23]{background:var(--danger);color:var(--accent-contrast)}.mw-btn--danger[data-v-bc33cc23]:not(:disabled):hover{filter:brightness(1.08)}.mw-card[data-v-883f87f5]{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);overflow:hidden}.mw-card__header[data-v-883f87f5]{padding:var(--space-xs) var(--space-sm);border-bottom:1px solid var(--border)}.mw-card__title[data-v-883f87f5]{font-size:var(--font-size-base);margin:0;font-weight:600}.mw-card__body[data-v-883f87f5]{padding:var(--space-sm)}.mw-card__footer[data-v-883f87f5]{padding:var(--space-xs) var(--space-sm);border-top:1px solid var(--border);background:var(--lift)}.mw-select[data-v-7e206184]{position:relative}.mw-select select[data-v-7e206184]{appearance:none;width:100%;padding:var(--space-2xs) calc(var(--space-md) + var(--space-2xs)) var(--space-2xs) var(--space-xs);background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font:inherit}.mw-select select[data-v-7e206184]:focus-visible{outline:2px solid var(--focus-ring);outline-offset:1px}.mw-select__chevron[data-v-7e206184]{right:var(--space-xs);pointer-events:none;stroke:var(--muted);stroke-width:1.5px;fill:none;position:absolute;top:50%;transform:translateY(-50%)}.mw-notice[data-v-ee7fffb1]{padding:var(--space-2xs) var(--space-xs);border:1px solid var(--border);border-radius:var(--radius);background:var(--lift);color:var(--text);font-size:var(--font-size-sm);border-left-width:3px;margin:0}.mw-notice--error[data-v-ee7fffb1]{border-left-color:var(--danger);color:var(--danger);background:color-mix(in srgb, var(--danger) 8%, transparent)}.mw-notice--ok[data-v-ee7fffb1]{border-left-color:var(--ok);color:var(--ok);background:color-mix(in srgb, var(--ok) 8%, transparent)}.sidebar-item[data-v-8da2d583]{align-items:center;gap:var(--space-2xs);width:100%;padding:var(--space-2xs) var(--space-xs);border-radius:var(--radius);color:inherit;font:inherit;text-align:left;cursor:pointer;opacity:.85;transition:background var(--transition-base), opacity var(--transition-base);background:0 0;border:0;display:flex}.sidebar-item[data-v-8da2d583]:hover:not(:disabled){background:color-mix(in srgb, currentColor 10%, transparent);opacity:1}.sidebar-item.is-active[data-v-8da2d583]{background:color-mix(in srgb, var(--accent) 26%, transparent);opacity:1}.sidebar-item[data-v-8da2d583]:disabled{cursor:default}.sidebar-item[data-v-8da2d583]:focus-visible{outline:2px solid var(--focus-ring);outline-offset:1px}.sidebar-item__icon[data-v-8da2d583]{text-align:center;width:1.1rem}.mw-tab-nav[data-v-97d31503]{gap:var(--space-3xs);border-bottom:2px solid var(--border);display:flex}.mw-tab-nav__tab[data-v-97d31503]{appearance:none;padding:var(--space-2xs) var(--space-sm);cursor:pointer;font-size:var(--font-size-base);color:var(--muted);transition:color var(--transition-base), border-color var(--transition-base);background:0 0;border:none;border-bottom:2px solid #0000;margin-bottom:-2px}.mw-tab-nav__tab[data-v-97d31503]:hover{color:var(--text)}.mw-tab-nav__tab--active[data-v-97d31503]{border-bottom-color:var(--accent);color:var(--text);font-weight:600}.ca-root[data-v-b43dabdd]{align-items:center;gap:.35em;display:inline-flex}.ca-value[data-v-b43dabdd]{font-family:var(--mw-font-mono,monospace);font-size:inherit}.ca-copy[data-v-b43dabdd]{color:var(--muted,#888);cursor:pointer;transition:color var(--transition-base);background:0 0;border:none;justify-content:center;align-items:center;padding:0;line-height:1;display:inline-flex}.ca-copy[data-v-b43dabdd]:hover{color:var(--text,#f0f0f0)}.ca-copy--copied[data-v-b43dabdd]{color:var(--accent,#6366f1)}.ca-icon[data-v-b43dabdd]{display:block}.el-root[data-v-073d1bbe]{font-family:monospace;font-size:inherit;color:var(--accent,#6366f1);text-underline-offset:2px;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;transition:color .15s}.el-root[data-v-073d1bbe]:hover{color:var(--text,#f0f0f0);text-decoration-style:solid}.status-widget[data-v-6436368e]{align-items:center;gap:var(--space-2xs);color:inherit;font-size:var(--font-size-sm);opacity:.85;transition:opacity var(--transition-base);text-decoration:none;display:inline-flex}.status-widget[data-v-6436368e]:hover{opacity:1}.status-widget__dot[data-v-6436368e]{width:var(--space-2xs);height:var(--space-2xs);border-radius:50%;flex-shrink:0}.status-widget--ok .status-widget__dot[data-v-6436368e]{background:var(--ok)}.status-widget--degraded .status-widget__dot[data-v-6436368e]{background:var(--warning)}.status-widget--error .status-widget__dot[data-v-6436368e]{background:var(--danger)}
2
2
  /*$vite$:1*/
@@ -4,6 +4,16 @@ type __VLS_Props = {
4
4
  variant?: PanelVariant;
5
5
  /** Per-instance colour overrides; token values are used by default. */
6
6
  colors?: PanelColors;
7
+ /**
8
+ * When set, renders a "Documentation" link pointing at this URL.
9
+ * Use `VITE_DOCS_URL` in the consuming app to make this configurable per deployment.
10
+ */
11
+ docsUrl?: string;
12
+ /**
13
+ * When set, renders a "Developer docs" link pointing at this URL.
14
+ * Use `VITE_DEV_URL` in the consuming app to make this configurable per deployment.
15
+ */
16
+ devUrl?: string;
7
17
  };
8
18
  declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {};
9
19
  type __VLS_Slots = {} & {
package/dist/index.d.ts CHANGED
@@ -19,4 +19,5 @@ export { useColorMode } from './composables/useColorMode.js';
19
19
  export type { ColorMode } from './composables/useColorMode.js';
20
20
  export type { PanelVariant, PanelColors } from './internal/panel.js';
21
21
  export { isStatusLevel, parseSnapshot } from './status.js';
22
+ export { safeHref } from './safe-href.js';
22
23
  export type { StatusLevel, StatusComponent, StatusGroup, StatusSnapshot } from './status.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Validate a consumer-supplied URL before binding it to an `href` attribute.
3
+ *
4
+ * Allows `https:` (all hosts) and `http:` on `localhost`/`127.0.0.1` (dev only).
5
+ * Returns `undefined` for `javascript:`, `data:`, or any other disallowed scheme so Vue
6
+ * renders no `href` attribute rather than a potentially-executable link.
7
+ */
8
+ export declare function safeHref(url: string | undefined | null): string | undefined;
package/dist/status.d.ts CHANGED
@@ -21,7 +21,9 @@ export interface StatusSnapshot {
21
21
  export declare function isStatusLevel(s: unknown): s is StatusLevel;
22
22
  /**
23
23
  * Parse and validate a raw API response as a `StatusSnapshot`.
24
- * Returns `null` if the required fields are absent or have unexpected types,
25
- * so callers can treat an invalid response the same as a network error.
24
+ * Returns `null` if the required top-level fields are absent or have unexpected types.
25
+ * Per-group and per-component fields are sanitized: unknown status values are coerced to
26
+ * `'unknown'`; name fields are stripped of HTML-sensitive characters. Groups or components
27
+ * with empty/missing names are dropped.
26
28
  */
27
29
  export declare function parseSnapshot(raw: unknown): StatusSnapshot | null;
package/dist/ui.js CHANGED
@@ -1,6 +1,6 @@
1
- import { Fragment as e, computed as t, createCommentVNode as n, createElementBlock as r, createElementVNode as i, createTextVNode as a, defineComponent as o, mergeProps as s, normalizeClass as c, normalizeStyle as l, onMounted as ee, onUnmounted as u, openBlock as d, ref as f, renderList as p, renderSlot as m, toDisplayString as h, useModel as te, vModelSelect as ne, watch as re, withDirectives as ie } from "vue";
1
+ import { Fragment as e, computed as t, createCommentVNode as n, createElementBlock as r, createElementVNode as i, createTextVNode as a, defineComponent as o, mergeProps as s, normalizeClass as c, normalizeStyle as l, onMounted as ee, onUnmounted as te, openBlock as u, ref as d, renderList as f, renderSlot as p, toDisplayString as m, unref as h, useModel as g, vModelSelect as ne, watch as re, withDirectives as ie } from "vue";
2
2
  //#region src/internal/panel.ts
3
- function g(e, t) {
3
+ function _(e, t) {
4
4
  let n;
5
5
  return n = e === "transparent" ? {
6
6
  "--_bg": "transparent",
@@ -35,33 +35,33 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
35
35
  }
36
36
  },
37
37
  setup(e) {
38
- let n = e, a = t(() => g(n.variant, n.colors));
39
- return (t, n) => (d(), r("header", {
38
+ let n = e, a = t(() => _(n.variant, n.colors));
39
+ return (t, n) => (u(), r("header", {
40
40
  class: c(["mw-header", [`mw-header--${e.variant}`, { "mw-header--sticky": e.sticky }]]),
41
41
  style: l(a.value)
42
42
  }, [
43
- i("div", ae, [m(t.$slots, "brand", {}, void 0, !0)]),
44
- i("div", oe, [m(t.$slots, "default", {}, void 0, !0)]),
45
- i("div", se, [m(t.$slots, "actions", {}, void 0, !0)])
43
+ i("div", ae, [p(t.$slots, "brand", {}, void 0, !0)]),
44
+ i("div", oe, [p(t.$slots, "default", {}, void 0, !0)]),
45
+ i("div", se, [p(t.$slots, "actions", {}, void 0, !0)])
46
46
  ], 6));
47
47
  }
48
- }), _ = (e, t) => {
48
+ }), v = (e, t) => {
49
49
  let n = e.__vccOpts || e;
50
50
  for (let [e, r] of t) n[e] = r;
51
51
  return n;
52
- }, v = /*#__PURE__*/ _(ce, [["__scopeId", "data-v-ad7cd1c5"]]), y = {
52
+ }, le = /*#__PURE__*/ v(ce, [["__scopeId", "data-v-9b9826d5"]]), ue = {
53
53
  key: 0,
54
54
  class: "mw-sidebar__head"
55
- }, b = {
55
+ }, de = {
56
56
  class: "mw-sidebar__nav",
57
57
  "aria-label": "Primary"
58
- }, x = {
58
+ }, y = {
59
59
  key: 1,
60
60
  class: "mw-sidebar__body"
61
- }, S = {
61
+ }, b = {
62
62
  key: 2,
63
63
  class: "mw-sidebar__foot"
64
- }, C = /*#__PURE__*/ _(/* @__PURE__ */ o({
64
+ }, x = /*#__PURE__*/ v(/* @__PURE__ */ o({
65
65
  __name: "AppSidebar",
66
66
  props: {
67
67
  variant: { default: "dark" },
@@ -70,45 +70,75 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
70
70
  },
71
71
  setup(e) {
72
72
  let a = e, o = t(() => {
73
- let e = g(a.variant, a.colors);
73
+ let e = _(a.variant, a.colors);
74
74
  return a.width && (e["--mw-sidebar-width"] = a.width), e;
75
75
  });
76
- return (t, a) => (d(), r("aside", {
76
+ return (t, a) => (u(), r("aside", {
77
77
  class: c(["mw-sidebar", `mw-sidebar--${e.variant}`]),
78
78
  style: l(o.value)
79
79
  }, [
80
- t.$slots.head ? (d(), r("div", y, [m(t.$slots, "head", {}, void 0, !0)])) : n("", !0),
81
- i("nav", b, [m(t.$slots, "default", {}, void 0, !0)]),
82
- t.$slots.body ? (d(), r("div", x, [m(t.$slots, "body", {}, void 0, !0)])) : n("", !0),
83
- t.$slots.foot ? (d(), r("div", S, [m(t.$slots, "foot", {}, void 0, !0)])) : n("", !0)
80
+ t.$slots.head ? (u(), r("div", ue, [p(t.$slots, "head", {}, void 0, !0)])) : n("", !0),
81
+ i("nav", de, [p(t.$slots, "default", {}, void 0, !0)]),
82
+ t.$slots.body ? (u(), r("div", y, [p(t.$slots, "body", {}, void 0, !0)])) : n("", !0),
83
+ t.$slots.foot ? (u(), r("div", b, [p(t.$slots, "foot", {}, void 0, !0)])) : n("", !0)
84
84
  ], 6));
85
85
  }
86
- }), [["__scopeId", "data-v-23c3c7f3"]]), w = { class: "mw-footer__start" }, T = { class: "mw-footer__center" }, E = { class: "mw-footer__end" }, D = /*#__PURE__*/ _(/* @__PURE__ */ o({
86
+ }), [["__scopeId", "data-v-a2b8405c"]]);
87
+ //#endregion
88
+ //#region src/safe-href.ts
89
+ function S(e) {
90
+ if (e) try {
91
+ let t = new URL(e);
92
+ if (t.protocol === "https:" || t.protocol === "http:" && (t.hostname === "localhost" || t.hostname === "127.0.0.1")) return e;
93
+ } catch {}
94
+ }
95
+ //#endregion
96
+ //#region src/components/AppFooter.vue?vue&type=script&setup=true&lang.ts
97
+ var C = { class: "mw-footer__start" }, w = { class: "mw-footer__center" }, T = { class: "mw-footer__end" }, E = ["href"], D = ["href"], O = /*#__PURE__*/ v(/* @__PURE__ */ o({
87
98
  __name: "AppFooter",
88
99
  props: {
89
100
  variant: { default: "transparent" },
90
- colors: {}
101
+ colors: {},
102
+ docsUrl: {},
103
+ devUrl: {}
91
104
  },
92
105
  setup(e) {
93
- let n = e, a = t(() => g(n.variant, n.colors));
94
- return (t, n) => (d(), r("footer", {
106
+ let a = e, o = t(() => _(a.variant, a.colors));
107
+ return (t, a) => (u(), r("footer", {
95
108
  class: c(["mw-footer", `mw-footer--${e.variant}`]),
96
- style: l(a.value)
109
+ style: l(o.value)
97
110
  }, [
98
- i("div", w, [m(t.$slots, "start", {}, void 0, !0)]),
99
- i("div", T, [m(t.$slots, "default", {}, void 0, !0)]),
100
- i("div", E, [m(t.$slots, "end", {}, void 0, !0)])
111
+ i("div", C, [p(t.$slots, "start", {}, void 0, !0)]),
112
+ i("div", w, [p(t.$slots, "default", {}, void 0, !0)]),
113
+ i("div", T, [
114
+ p(t.$slots, "end", {}, void 0, !0),
115
+ e.docsUrl ? (u(), r("a", {
116
+ key: 0,
117
+ href: h(S)(e.docsUrl),
118
+ target: "_blank",
119
+ rel: "noopener noreferrer",
120
+ class: "mw-footer__docs-link"
121
+ }, "Documentation", 8, E)) : n("", !0),
122
+ e.devUrl ? (u(), r("a", {
123
+ key: 1,
124
+ href: h(S)(e.devUrl),
125
+ target: "_blank",
126
+ rel: "noopener noreferrer",
127
+ "aria-label": "Developer documentation",
128
+ class: "mw-footer__docs-link"
129
+ }, "Developer docs", 8, D)) : n("", !0)
130
+ ])
101
131
  ], 6));
102
132
  }
103
- }), [["__scopeId", "data-v-c646b8e1"]]), O = {
133
+ }), [["__scopeId", "data-v-979579a9"]]), k = {
104
134
  class: "mw-mode",
105
135
  role: "group",
106
136
  "aria-label": "Colour mode"
107
- }, k = [
137
+ }, A = [
108
138
  "aria-pressed",
109
139
  "title",
110
140
  "onClick"
111
- ], A = { "aria-hidden": "true" }, j = { class: "mw-mode__label" }, M = /*#__PURE__*/ _(/* @__PURE__ */ o({
141
+ ], j = { "aria-hidden": "true" }, M = { class: "mw-mode__label" }, N = /*#__PURE__*/ v(/* @__PURE__ */ o({
112
142
  __name: "ColorModeControl",
113
143
  props: {
114
144
  modelValue: {},
@@ -129,16 +159,16 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
129
159
  dark: "☾",
130
160
  system: "🖵"
131
161
  };
132
- return (o, s) => (d(), r("div", O, [(d(!0), r(e, null, p(t.modes, (e) => (d(), r("button", {
162
+ return (o, s) => (u(), r("div", k, [(u(!0), r(e, null, f(t.modes, (e) => (u(), r("button", {
133
163
  key: e,
134
164
  type: "button",
135
165
  class: c(["mw-mode__btn", { "is-active": t.modelValue === e }]),
136
166
  "aria-pressed": t.modelValue === e,
137
167
  title: n[e],
138
168
  onClick: (t) => o.$emit("update:modelValue", e)
139
- }, [i("span", A, h(a[e]), 1), i("span", j, h(n[e]), 1)], 10, k))), 128))]));
169
+ }, [i("span", j, m(a[e]), 1), i("span", M, m(n[e]), 1)], 10, A))), 128))]));
140
170
  }
141
- }), [["__scopeId", "data-v-186f8312"]]), N = ["type", "disabled"], P = /*#__PURE__*/ _(/* @__PURE__ */ o({
171
+ }), [["__scopeId", "data-v-e2ea0d79"]]), P = ["type", "disabled"], F = /*#__PURE__*/ v(/* @__PURE__ */ o({
142
172
  __name: "UiButton",
143
173
  props: {
144
174
  variant: { default: "primary" },
@@ -149,29 +179,29 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
149
179
  }
150
180
  },
151
181
  setup(e) {
152
- return (t, n) => (d(), r("button", {
182
+ return (t, n) => (u(), r("button", {
153
183
  type: e.type,
154
184
  disabled: e.disabled,
155
185
  class: c(["mw-btn", `mw-btn--${e.variant}`])
156
- }, [m(t.$slots, "default", {}, void 0, !0)], 10, N));
186
+ }, [p(t.$slots, "default", {}, void 0, !0)], 10, P));
157
187
  }
158
- }), [["__scopeId", "data-v-299de2c3"]]), F = { class: "mw-card" }, I = {
188
+ }), [["__scopeId", "data-v-bc33cc23"]]), I = { class: "mw-card" }, L = {
159
189
  key: 0,
160
190
  class: "mw-card__header"
161
- }, L = { class: "mw-card__title" }, R = { class: "mw-card__body" }, z = {
191
+ }, R = { class: "mw-card__title" }, z = { class: "mw-card__body" }, B = {
162
192
  key: 1,
163
193
  class: "mw-card__footer"
164
- }, B = /*#__PURE__*/ _(/* @__PURE__ */ o({
194
+ }, V = /*#__PURE__*/ v(/* @__PURE__ */ o({
165
195
  __name: "UiCard",
166
196
  props: { title: {} },
167
197
  setup(e) {
168
- return (t, a) => (d(), r("section", F, [
169
- e.title || t.$slots.header ? (d(), r("header", I, [m(t.$slots, "header", {}, () => [i("h3", L, h(e.title), 1)], !0)])) : n("", !0),
170
- i("div", R, [m(t.$slots, "default", {}, void 0, !0)]),
171
- t.$slots.footer ? (d(), r("footer", z, [m(t.$slots, "footer", {}, void 0, !0)])) : n("", !0)
198
+ return (t, a) => (u(), r("section", I, [
199
+ e.title || t.$slots.header ? (u(), r("header", L, [p(t.$slots, "header", {}, () => [i("h3", R, m(e.title), 1)], !0)])) : n("", !0),
200
+ i("div", z, [p(t.$slots, "default", {}, void 0, !0)]),
201
+ t.$slots.footer ? (u(), r("footer", B, [p(t.$slots, "footer", {}, void 0, !0)])) : n("", !0)
172
202
  ]));
173
203
  }
174
- }), [["__scopeId", "data-v-fd2fd7ab"]]), V = { class: "mw-select" }, H = /*#__PURE__*/ _(/* @__PURE__ */ o({
204
+ }), [["__scopeId", "data-v-883f87f5"]]), H = { class: "mw-select" }, fe = /*#__PURE__*/ v(/* @__PURE__ */ o({
175
205
  inheritAttrs: !1,
176
206
  __name: "UiSelect",
177
207
  props: {
@@ -180,8 +210,8 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
180
210
  },
181
211
  emits: ["update:modelValue"],
182
212
  setup(e) {
183
- let t = te(e, "modelValue");
184
- return (e, n) => (d(), r("div", V, [ie(i("select", s(e.$attrs, { "onUpdate:modelValue": n[0] ||= (e) => t.value = e }), [m(e.$slots, "default", {}, void 0, !0)], 16), [[ne, t.value]]), n[1] ||= i("svg", {
213
+ let t = g(e, "modelValue");
214
+ return (e, n) => (u(), r("div", H, [ie(i("select", s(e.$attrs, { "onUpdate:modelValue": n[0] ||= (e) => t.value = e }), [p(e.$slots, "default", {}, void 0, !0)], 16), [[ne, t.value]]), n[1] ||= i("svg", {
185
215
  class: "mw-select__chevron",
186
216
  xmlns: "http://www.w3.org/2000/svg",
187
217
  width: "12",
@@ -190,7 +220,7 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
190
220
  "aria-hidden": "true"
191
221
  }, [i("path", { d: "M1 1l5 5 5-5" })], -1)]));
192
222
  }
193
- }), [["__scopeId", "data-v-b9468387"]]), U = ["role"], W = /*#__PURE__*/ _(/* @__PURE__ */ o({
223
+ }), [["__scopeId", "data-v-7e206184"]]), pe = ["role"], me = /*#__PURE__*/ v(/* @__PURE__ */ o({
194
224
  __name: "UiNotice",
195
225
  props: { type: {} },
196
226
  setup(e) {
@@ -198,16 +228,16 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
198
228
  if (n.type === "error") return "alert";
199
229
  if (n.type === "ok") return "status";
200
230
  });
201
- return (t, n) => (d(), r("p", {
231
+ return (t, n) => (u(), r("p", {
202
232
  class: c(["mw-notice", e.type && e.type !== "info" ? `mw-notice--${e.type}` : ""]),
203
233
  role: i.value
204
- }, [m(t.$slots, "default", {}, void 0, !0)], 10, U));
234
+ }, [p(t.$slots, "default", {}, void 0, !0)], 10, pe));
205
235
  }
206
- }), [["__scopeId", "data-v-6f53d0be"]]), le = ["disabled", "aria-current"], ue = {
236
+ }), [["__scopeId", "data-v-ee7fffb1"]]), he = ["disabled", "aria-current"], ge = {
207
237
  key: 0,
208
238
  class: "sidebar-item__icon",
209
239
  "aria-hidden": "true"
210
- }, G = { class: "sidebar-item__label" }, de = /*#__PURE__*/ _(/* @__PURE__ */ o({
240
+ }, _e = { class: "sidebar-item__label" }, ve = /*#__PURE__*/ v(/* @__PURE__ */ o({
211
241
  __name: "SidebarItem",
212
242
  props: {
213
243
  label: {},
@@ -222,14 +252,14 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
222
252
  }
223
253
  },
224
254
  setup(e) {
225
- return (t, a) => (d(), r("button", {
255
+ return (t, a) => (u(), r("button", {
226
256
  type: "button",
227
257
  class: c(["sidebar-item", { "is-active": e.active }]),
228
258
  disabled: e.disabled,
229
259
  "aria-current": e.active ? "page" : void 0
230
- }, [e.icon ? (d(), r("span", ue, h(e.icon), 1)) : n("", !0), i("span", G, h(e.label), 1)], 10, le));
260
+ }, [e.icon ? (u(), r("span", ge, m(e.icon), 1)) : n("", !0), i("span", _e, m(e.label), 1)], 10, he));
231
261
  }
232
- }), [["__scopeId", "data-v-e15afdf7"]]), fe = ["aria-label"], pe = ["aria-current", "onClick"], me = /*#__PURE__*/ _(/* @__PURE__ */ o({
262
+ }), [["__scopeId", "data-v-8da2d583"]]), ye = ["aria-label"], be = ["aria-current", "onClick"], xe = /*#__PURE__*/ v(/* @__PURE__ */ o({
233
263
  __name: "AppTabNav",
234
264
  props: {
235
265
  tabs: {},
@@ -239,18 +269,18 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
239
269
  emits: ["update:modelValue"],
240
270
  setup(t, { emit: n }) {
241
271
  let i = n;
242
- return (n, a) => (d(), r("nav", {
272
+ return (n, a) => (u(), r("nav", {
243
273
  class: "mw-tab-nav",
244
274
  "aria-label": t.ariaLabel
245
- }, [(d(!0), r(e, null, p(t.tabs, (e) => (d(), r("button", {
275
+ }, [(u(!0), r(e, null, f(t.tabs, (e) => (u(), r("button", {
246
276
  key: e.id,
247
277
  type: "button",
248
278
  class: c(["mw-tab-nav__tab", { "mw-tab-nav__tab--active": t.modelValue === e.id }]),
249
279
  "aria-current": t.modelValue === e.id ? "page" : void 0,
250
280
  onClick: (t) => i("update:modelValue", e.id)
251
- }, h(e.label), 11, pe))), 128))], 8, fe));
281
+ }, m(e.label), 11, be))), 128))], 8, ye));
252
282
  }
253
- }), [["__scopeId", "data-v-88758ede"]]), he = { class: "ca-root" }, ge = ["title"], _e = ["title", "aria-label"], ve = {
283
+ }), [["__scopeId", "data-v-97d31503"]]), Se = { class: "ca-root" }, Ce = ["title"], we = ["title", "aria-label"], Te = {
254
284
  key: 0,
255
285
  class: "ca-icon",
256
286
  viewBox: "0 0 24 24",
@@ -262,7 +292,7 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
262
292
  "stroke-linecap": "round",
263
293
  "stroke-linejoin": "round",
264
294
  "aria-hidden": "true"
265
- }, ye = {
295
+ }, Ee = {
266
296
  key: 1,
267
297
  class: "ca-icon",
268
298
  viewBox: "0 0 24 24",
@@ -274,7 +304,7 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
274
304
  "stroke-linecap": "round",
275
305
  "stroke-linejoin": "round",
276
306
  "aria-hidden": "true"
277
- }, be = /*#__PURE__*/ _(/* @__PURE__ */ o({
307
+ }, De = /*#__PURE__*/ v(/* @__PURE__ */ o({
278
308
  __name: "CopyableAddress",
279
309
  props: {
280
310
  address: {},
@@ -286,7 +316,7 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
286
316
  label: { default: "Copy" }
287
317
  },
288
318
  setup(e) {
289
- let n = e, o = f(!1), s = t(() => {
319
+ let n = e, o = d(!1), s = t(() => {
290
320
  if (!n.truncate) return n.address;
291
321
  let [e, t] = n.chars;
292
322
  return `${n.address.slice(0, e)}…${n.address.slice(-t)}`;
@@ -296,25 +326,25 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
296
326
  o.value = !1;
297
327
  }, 1500);
298
328
  }
299
- return (t, n) => (d(), r("span", he, [i("span", {
329
+ return (t, n) => (u(), r("span", Se, [i("span", {
300
330
  class: "ca-value",
301
331
  title: e.address
302
- }, [m(t.$slots, "default", {}, () => [a(h(s.value), 1)], !0)], 8, ge), i("button", {
332
+ }, [p(t.$slots, "default", {}, () => [a(m(s.value), 1)], !0)], 8, Ce), i("button", {
303
333
  type: "button",
304
334
  class: c(["ca-copy", { "ca-copy--copied": o.value }]),
305
335
  title: o.value ? "Copied!" : e.label,
306
336
  "aria-label": o.value ? "Copied" : e.label,
307
337
  onClick: l
308
- }, [o.value ? (d(), r("svg", ve, [...n[0] ||= [i("polyline", { points: "20 6 9 17 4 12" }, null, -1)]])) : (d(), r("svg", ye, [...n[1] ||= [i("rect", {
338
+ }, [o.value ? (u(), r("svg", Te, [...n[0] ||= [i("polyline", { points: "20 6 9 17 4 12" }, null, -1)]])) : (u(), r("svg", Ee, [...n[1] ||= [i("rect", {
309
339
  x: "9",
310
340
  y: "9",
311
341
  width: "13",
312
342
  height: "13",
313
343
  rx: "2",
314
344
  ry: "2"
315
- }, null, -1), i("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" }, null, -1)]]))], 10, _e)]));
345
+ }, null, -1), i("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" }, null, -1)]]))], 10, we)]));
316
346
  }
317
- }), [["__scopeId", "data-v-08c3420b"]]), xe = ["href", "title"], Se = /*#__PURE__*/ _(/* @__PURE__ */ o({
347
+ }), [["__scopeId", "data-v-b43dabdd"]]), Oe = ["href", "title"], ke = /*#__PURE__*/ v(/* @__PURE__ */ o({
318
348
  __name: "ExplorerLink",
319
349
  props: {
320
350
  href: {},
@@ -332,31 +362,62 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
332
362
  let [e, t] = n.chars;
333
363
  return `${n.value.slice(0, e)}…${n.value.slice(-t)}`;
334
364
  });
335
- return (t, n) => (d(), r("a", {
365
+ return (t, n) => (u(), r("a", {
336
366
  class: "el-root",
337
- href: e.href,
367
+ href: h(S)(e.href),
338
368
  target: "_blank",
339
369
  rel: "noopener noreferrer",
340
370
  title: e.value
341
- }, [m(t.$slots, "default", {}, () => [a(h(i.value), 1)], !0)], 8, xe));
371
+ }, [p(t.$slots, "default", {}, () => [a(m(i.value), 1)], !0)], 8, Oe));
342
372
  }
343
- }), [["__scopeId", "data-v-7a44607e"]]), Ce = /* @__PURE__ */ new Set([
373
+ }), [["__scopeId", "data-v-073d1bbe"]]), Ae = /* @__PURE__ */ new Set([
344
374
  "operational",
345
375
  "degraded",
346
376
  "down",
347
377
  "unknown"
348
378
  ]);
349
- function K(e) {
350
- return typeof e == "string" && Ce.has(e);
379
+ function U(e) {
380
+ return typeof e == "string" && Ae.has(e);
381
+ }
382
+ function W(e) {
383
+ return U(e) ? e : "unknown";
384
+ }
385
+ function G(e) {
386
+ return typeof e != "string" || e.trim() === "" ? "" : e.replace(/[<>&"]/g, "");
351
387
  }
352
- function q(e) {
388
+ function je(e) {
389
+ if (typeof e != "object" || !e) return null;
390
+ let t = e, n = G(t.name);
391
+ return n ? {
392
+ name: n,
393
+ status: W(t.status)
394
+ } : null;
395
+ }
396
+ function Me(e) {
397
+ if (typeof e != "object" || !e) return null;
398
+ let t = e, n = G(t.name);
399
+ if (!n) return null;
400
+ let r = Array.isArray(t.components) ? t.components.map(je).filter((e) => e !== null) : [];
401
+ return {
402
+ name: n,
403
+ status: W(t.status),
404
+ components: r
405
+ };
406
+ }
407
+ function K(e) {
353
408
  if (typeof e != "object" || !e) return null;
354
409
  let t = e;
355
- return !K(t.overall) || typeof t.generated_at != "string" || !Array.isArray(t.groups) ? null : e;
410
+ if (!U(t.overall) || typeof t.generated_at != "string" || !Array.isArray(t.groups)) return null;
411
+ let n = t.groups.map(Me).filter((e) => e !== null);
412
+ return {
413
+ generated_at: t.generated_at,
414
+ overall: t.overall,
415
+ groups: n
416
+ };
356
417
  }
357
418
  //#endregion
358
419
  //#region src/components/StatusWidget.vue?vue&type=script&setup=true&lang.ts
359
- var we = ["href", "title"], Te = { class: "status-widget__label" }, Ee = /*#__PURE__*/ _(/* @__PURE__ */ o({
420
+ var Ne = ["href", "title"], q = { class: "status-widget__label" }, Pe = /*#__PURE__*/ v(/* @__PURE__ */ o({
360
421
  __name: "StatusWidget",
361
422
  props: {
362
423
  apiUrl: { default: "https://status.meddleware.co.uk/api/status" },
@@ -374,12 +435,12 @@ var we = ["href", "title"], Te = { class: "status-widget__label" }, Ee = /*#__PU
374
435
  degraded: "Degraded",
375
436
  down: "Major outage",
376
437
  unknown: "Status unknown"
377
- }, o = f("ok"), s = f("All systems operational"), l = null;
378
- async function p() {
438
+ }, o = d("ok"), s = d("All systems operational"), l = null;
439
+ async function f() {
379
440
  try {
380
441
  let e = await fetch(t.apiUrl);
381
442
  if (!e.ok) throw Error(`HTTP ${e.status}`);
382
- let r = q(await e.json());
443
+ let r = K(await e.json());
383
444
  if (!r) throw Error("unexpected response shape");
384
445
  o.value = n[r.overall], s.value = a[r.overall];
385
446
  } catch {
@@ -387,41 +448,41 @@ var we = ["href", "title"], Te = { class: "status-widget__label" }, Ee = /*#__PU
387
448
  }
388
449
  }
389
450
  return ee(() => {
390
- p(), l = setInterval(p, t.pollInterval);
391
- }), u(() => {
451
+ f(), l = setInterval(f, t.pollInterval);
452
+ }), te(() => {
392
453
  l !== null && clearInterval(l);
393
- }), (t, n) => (d(), r("a", {
394
- href: e.href,
454
+ }), (t, n) => (u(), r("a", {
455
+ href: h(S)(e.href),
395
456
  target: "_blank",
396
- rel: "noopener",
457
+ rel: "noopener noreferrer",
397
458
  class: c(["status-widget", `status-widget--${o.value}`]),
398
459
  title: `Platform status: ${s.value}`
399
460
  }, [n[0] ||= i("span", {
400
461
  class: "status-widget__dot",
401
462
  "aria-hidden": "true"
402
- }, null, -1), i("span", Te, h(s.value), 1)], 10, we));
463
+ }, null, -1), i("span", q, m(s.value), 1)], 10, Ne));
403
464
  }
404
- }), [["__scopeId", "data-v-20197983"]]), J = {
465
+ }), [["__scopeId", "data-v-6436368e"]]), J = {
405
466
  mainnet: "https://suivision.xyz",
406
467
  testnet: "https://testnet.suivision.xyz",
407
468
  devnet: "https://devnet.suivision.xyz"
408
469
  };
409
- function De(e, t, n = "testnet") {
470
+ function Fe(e, t, n = "testnet") {
410
471
  return `${J[n] ?? J.testnet}/${e}/${t}`;
411
472
  }
412
473
  //#endregion
413
474
  //#region src/composables/useColorMode.ts
414
- var Y = "mw-color-mode", X = f("system"), Z = !1;
415
- function Oe() {
475
+ var Y = "mw-color-mode", X = d("system"), Z = !1;
476
+ function Ie() {
416
477
  return typeof window < "u" && typeof window.matchMedia == "function" && window.matchMedia("(prefers-color-scheme: dark)").matches;
417
478
  }
418
479
  function Q(e) {
419
- return e === "dark" || e === "system" && Oe();
480
+ return e === "dark" || e === "system" && Ie();
420
481
  }
421
482
  function $(e) {
422
483
  typeof document > "u" || document.documentElement.setAttribute("data-theme", Q(e) ? "dark" : "light");
423
484
  }
424
- function ke(e = "system") {
485
+ function Le(e = "system") {
425
486
  if (!Z) {
426
487
  Z = !0;
427
488
  let t = null;
@@ -451,4 +512,4 @@ function ke(e = "system") {
451
512
  };
452
513
  }
453
514
  //#endregion
454
- export { D as AppFooter, v as AppHeader, C as AppSidebar, me as AppTabNav, M as ColorModeControl, be as CopyableAddress, Se as ExplorerLink, de as SidebarItem, Ee as StatusWidget, P as UiButton, B as UiCard, W as UiNotice, H as UiSelect, K as isStatusLevel, q as parseSnapshot, De as suiExplorerUrl, ke as useColorMode };
515
+ export { O as AppFooter, le as AppHeader, x as AppSidebar, xe as AppTabNav, N as ColorModeControl, De as CopyableAddress, ke as ExplorerLink, ve as SidebarItem, Pe as StatusWidget, F as UiButton, V as UiCard, me as UiNotice, fe as UiSelect, U as isStatusLevel, K as parseSnapshot, S as safeHref, Fe as suiExplorerUrl, Le as useColorMode };
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@meddleware/ui",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Shared Vue 3 component library for @meddleware UIs \u2014 layout shell (header/sidebar/footer), colour-mode control, and primitives. Built on @meddleware/design-tokens.",
5
+ "homepage": "https://docs.meddleware.co.uk/",
5
6
  "license": "0BSD",
6
7
  "type": "module",
7
8
  "sideEffects": [
@@ -30,13 +31,13 @@
30
31
  "vue": "^3.5.0"
31
32
  },
32
33
  "dependencies": {
33
- "@meddleware/design-tokens": "^0.1.2"
34
+ "@meddleware/design-tokens": "^0.1.3"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@vitejs/plugin-vue": "^6.0.8",
37
38
  "typescript": "^6.0.0",
38
39
  "vite": "^8.2.2",
39
- "vitest": "~4.1.11",
40
+ "vitest": "~5.0.1",
40
41
  "vue": "^3.5.42",
41
42
  "vue-tsc": "~3.3.11"
42
43
  },