@meddleware/ui 0.1.12 → 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-88269247]{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-88269247]{border-top:1px solid var(--border)}.mw-footer__center[data-v-88269247]{flex:auto;min-width:0}.mw-footer__end[data-v-88269247]{align-items:center;gap:1rem;margin-left:auto;display:flex}.mw-footer__docs-link[data-v-88269247]{color:inherit;text-underline-offset:2px;opacity:.7;text-decoration:underline}.mw-footer__docs-link[data-v-88269247]:hover{opacity:1}.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-cc70da78]{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-cc70da78]:hover:not(:disabled){background:color-mix(in srgb, currentColor 10%, transparent);opacity:1}.sidebar-item.is-active[data-v-cc70da78]{background:color-mix(in srgb, var(--accent) 26%, transparent);opacity:1}.sidebar-item[data-v-cc70da78]:disabled{cursor:default}.sidebar-item[data-v-cc70da78]:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.sidebar-item__icon[data-v-cc70da78]{text-align:center;width:1.1rem}.mw-tab-nav[data-v-7c93ad15]{border-bottom:2px solid var(--border);gap:.25rem;display:flex}.mw-tab-nav__tab[data-v-7c93ad15]{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-7c93ad15]:hover{color:var(--text)}.mw-tab-nav__tab--active[data-v-7c93ad15]{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*/
@@ -9,6 +9,11 @@ type __VLS_Props = {
9
9
  * Use `VITE_DOCS_URL` in the consuming app to make this configurable per deployment.
10
10
  */
11
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;
12
17
  };
13
18
  declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {};
14
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 u, onUnmounted as ee, 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,44 +70,67 @@ 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 = ["href"], O = /*#__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
101
  colors: {},
91
- docsUrl: {}
102
+ docsUrl: {},
103
+ devUrl: {}
92
104
  },
93
105
  setup(e) {
94
- let a = e, o = t(() => g(a.variant, a.colors));
95
- return (t, a) => (d(), r("footer", {
106
+ let a = e, o = t(() => _(a.variant, a.colors));
107
+ return (t, a) => (u(), r("footer", {
96
108
  class: c(["mw-footer", `mw-footer--${e.variant}`]),
97
109
  style: l(o.value)
98
110
  }, [
99
- i("div", w, [m(t.$slots, "start", {}, void 0, !0)]),
100
- i("div", T, [m(t.$slots, "default", {}, void 0, !0)]),
101
- i("div", E, [m(t.$slots, "end", {}, void 0, !0), e.docsUrl ? (d(), r("a", {
102
- key: 0,
103
- href: e.docsUrl,
104
- target: "_blank",
105
- rel: "noopener noreferrer",
106
- class: "mw-footer__docs-link"
107
- }, "Documentation", 8, D)) : n("", !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
+ ])
108
131
  ], 6));
109
132
  }
110
- }), [["__scopeId", "data-v-88269247"]]), k = {
133
+ }), [["__scopeId", "data-v-979579a9"]]), k = {
111
134
  class: "mw-mode",
112
135
  role: "group",
113
136
  "aria-label": "Colour mode"
@@ -115,7 +138,7 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
115
138
  "aria-pressed",
116
139
  "title",
117
140
  "onClick"
118
- ], j = { "aria-hidden": "true" }, M = { class: "mw-mode__label" }, N = /*#__PURE__*/ _(/* @__PURE__ */ o({
141
+ ], j = { "aria-hidden": "true" }, M = { class: "mw-mode__label" }, N = /*#__PURE__*/ v(/* @__PURE__ */ o({
119
142
  __name: "ColorModeControl",
120
143
  props: {
121
144
  modelValue: {},
@@ -136,16 +159,16 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
136
159
  dark: "☾",
137
160
  system: "🖵"
138
161
  };
139
- return (o, s) => (d(), r("div", k, [(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", {
140
163
  key: e,
141
164
  type: "button",
142
165
  class: c(["mw-mode__btn", { "is-active": t.modelValue === e }]),
143
166
  "aria-pressed": t.modelValue === e,
144
167
  title: n[e],
145
168
  onClick: (t) => o.$emit("update:modelValue", e)
146
- }, [i("span", j, h(a[e]), 1), i("span", M, h(n[e]), 1)], 10, A))), 128))]));
169
+ }, [i("span", j, m(a[e]), 1), i("span", M, m(n[e]), 1)], 10, A))), 128))]));
147
170
  }
148
- }), [["__scopeId", "data-v-186f8312"]]), P = ["type", "disabled"], F = /*#__PURE__*/ _(/* @__PURE__ */ o({
171
+ }), [["__scopeId", "data-v-e2ea0d79"]]), P = ["type", "disabled"], F = /*#__PURE__*/ v(/* @__PURE__ */ o({
149
172
  __name: "UiButton",
150
173
  props: {
151
174
  variant: { default: "primary" },
@@ -156,29 +179,29 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
156
179
  }
157
180
  },
158
181
  setup(e) {
159
- return (t, n) => (d(), r("button", {
182
+ return (t, n) => (u(), r("button", {
160
183
  type: e.type,
161
184
  disabled: e.disabled,
162
185
  class: c(["mw-btn", `mw-btn--${e.variant}`])
163
- }, [m(t.$slots, "default", {}, void 0, !0)], 10, P));
186
+ }, [p(t.$slots, "default", {}, void 0, !0)], 10, P));
164
187
  }
165
- }), [["__scopeId", "data-v-299de2c3"]]), I = { class: "mw-card" }, L = {
188
+ }), [["__scopeId", "data-v-bc33cc23"]]), I = { class: "mw-card" }, L = {
166
189
  key: 0,
167
190
  class: "mw-card__header"
168
191
  }, R = { class: "mw-card__title" }, z = { class: "mw-card__body" }, B = {
169
192
  key: 1,
170
193
  class: "mw-card__footer"
171
- }, V = /*#__PURE__*/ _(/* @__PURE__ */ o({
194
+ }, V = /*#__PURE__*/ v(/* @__PURE__ */ o({
172
195
  __name: "UiCard",
173
196
  props: { title: {} },
174
197
  setup(e) {
175
- return (t, a) => (d(), r("section", I, [
176
- e.title || t.$slots.header ? (d(), r("header", L, [m(t.$slots, "header", {}, () => [i("h3", R, h(e.title), 1)], !0)])) : n("", !0),
177
- i("div", z, [m(t.$slots, "default", {}, void 0, !0)]),
178
- t.$slots.footer ? (d(), r("footer", B, [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)
179
202
  ]));
180
203
  }
181
- }), [["__scopeId", "data-v-fd2fd7ab"]]), H = { class: "mw-select" }, U = /*#__PURE__*/ _(/* @__PURE__ */ o({
204
+ }), [["__scopeId", "data-v-883f87f5"]]), H = { class: "mw-select" }, fe = /*#__PURE__*/ v(/* @__PURE__ */ o({
182
205
  inheritAttrs: !1,
183
206
  __name: "UiSelect",
184
207
  props: {
@@ -187,8 +210,8 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
187
210
  },
188
211
  emits: ["update:modelValue"],
189
212
  setup(e) {
190
- let t = te(e, "modelValue");
191
- return (e, n) => (d(), r("div", H, [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", {
192
215
  class: "mw-select__chevron",
193
216
  xmlns: "http://www.w3.org/2000/svg",
194
217
  width: "12",
@@ -197,7 +220,7 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
197
220
  "aria-hidden": "true"
198
221
  }, [i("path", { d: "M1 1l5 5 5-5" })], -1)]));
199
222
  }
200
- }), [["__scopeId", "data-v-b9468387"]]), W = ["role"], le = /*#__PURE__*/ _(/* @__PURE__ */ o({
223
+ }), [["__scopeId", "data-v-7e206184"]]), pe = ["role"], me = /*#__PURE__*/ v(/* @__PURE__ */ o({
201
224
  __name: "UiNotice",
202
225
  props: { type: {} },
203
226
  setup(e) {
@@ -205,16 +228,16 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
205
228
  if (n.type === "error") return "alert";
206
229
  if (n.type === "ok") return "status";
207
230
  });
208
- return (t, n) => (d(), r("p", {
231
+ return (t, n) => (u(), r("p", {
209
232
  class: c(["mw-notice", e.type && e.type !== "info" ? `mw-notice--${e.type}` : ""]),
210
233
  role: i.value
211
- }, [m(t.$slots, "default", {}, void 0, !0)], 10, W));
234
+ }, [p(t.$slots, "default", {}, void 0, !0)], 10, pe));
212
235
  }
213
- }), [["__scopeId", "data-v-6f53d0be"]]), ue = ["disabled", "aria-current"], de = {
236
+ }), [["__scopeId", "data-v-ee7fffb1"]]), he = ["disabled", "aria-current"], ge = {
214
237
  key: 0,
215
238
  class: "sidebar-item__icon",
216
239
  "aria-hidden": "true"
217
- }, fe = { class: "sidebar-item__label" }, pe = /*#__PURE__*/ _(/* @__PURE__ */ o({
240
+ }, _e = { class: "sidebar-item__label" }, ve = /*#__PURE__*/ v(/* @__PURE__ */ o({
218
241
  __name: "SidebarItem",
219
242
  props: {
220
243
  label: {},
@@ -229,14 +252,14 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
229
252
  }
230
253
  },
231
254
  setup(e) {
232
- return (t, a) => (d(), r("button", {
255
+ return (t, a) => (u(), r("button", {
233
256
  type: "button",
234
257
  class: c(["sidebar-item", { "is-active": e.active }]),
235
258
  disabled: e.disabled,
236
259
  "aria-current": e.active ? "page" : void 0
237
- }, [e.icon ? (d(), r("span", de, h(e.icon), 1)) : n("", !0), i("span", fe, h(e.label), 1)], 10, ue));
260
+ }, [e.icon ? (u(), r("span", ge, m(e.icon), 1)) : n("", !0), i("span", _e, m(e.label), 1)], 10, he));
238
261
  }
239
- }), [["__scopeId", "data-v-cc70da78"]]), me = ["aria-label"], he = ["aria-current", "onClick"], ge = /*#__PURE__*/ _(/* @__PURE__ */ o({
262
+ }), [["__scopeId", "data-v-8da2d583"]]), ye = ["aria-label"], be = ["aria-current", "onClick"], xe = /*#__PURE__*/ v(/* @__PURE__ */ o({
240
263
  __name: "AppTabNav",
241
264
  props: {
242
265
  tabs: {},
@@ -246,18 +269,18 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
246
269
  emits: ["update:modelValue"],
247
270
  setup(t, { emit: n }) {
248
271
  let i = n;
249
- return (n, a) => (d(), r("nav", {
272
+ return (n, a) => (u(), r("nav", {
250
273
  class: "mw-tab-nav",
251
274
  "aria-label": t.ariaLabel
252
- }, [(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", {
253
276
  key: e.id,
254
277
  type: "button",
255
278
  class: c(["mw-tab-nav__tab", { "mw-tab-nav__tab--active": t.modelValue === e.id }]),
256
279
  "aria-current": t.modelValue === e.id ? "page" : void 0,
257
280
  onClick: (t) => i("update:modelValue", e.id)
258
- }, h(e.label), 11, he))), 128))], 8, me));
281
+ }, m(e.label), 11, be))), 128))], 8, ye));
259
282
  }
260
- }), [["__scopeId", "data-v-7c93ad15"]]), _e = { class: "ca-root" }, ve = ["title"], ye = ["title", "aria-label"], be = {
283
+ }), [["__scopeId", "data-v-97d31503"]]), Se = { class: "ca-root" }, Ce = ["title"], we = ["title", "aria-label"], Te = {
261
284
  key: 0,
262
285
  class: "ca-icon",
263
286
  viewBox: "0 0 24 24",
@@ -269,7 +292,7 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
269
292
  "stroke-linecap": "round",
270
293
  "stroke-linejoin": "round",
271
294
  "aria-hidden": "true"
272
- }, xe = {
295
+ }, Ee = {
273
296
  key: 1,
274
297
  class: "ca-icon",
275
298
  viewBox: "0 0 24 24",
@@ -281,7 +304,7 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
281
304
  "stroke-linecap": "round",
282
305
  "stroke-linejoin": "round",
283
306
  "aria-hidden": "true"
284
- }, Se = /*#__PURE__*/ _(/* @__PURE__ */ o({
307
+ }, De = /*#__PURE__*/ v(/* @__PURE__ */ o({
285
308
  __name: "CopyableAddress",
286
309
  props: {
287
310
  address: {},
@@ -293,7 +316,7 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
293
316
  label: { default: "Copy" }
294
317
  },
295
318
  setup(e) {
296
- let n = e, o = f(!1), s = t(() => {
319
+ let n = e, o = d(!1), s = t(() => {
297
320
  if (!n.truncate) return n.address;
298
321
  let [e, t] = n.chars;
299
322
  return `${n.address.slice(0, e)}…${n.address.slice(-t)}`;
@@ -303,25 +326,25 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
303
326
  o.value = !1;
304
327
  }, 1500);
305
328
  }
306
- return (t, n) => (d(), r("span", _e, [i("span", {
329
+ return (t, n) => (u(), r("span", Se, [i("span", {
307
330
  class: "ca-value",
308
331
  title: e.address
309
- }, [m(t.$slots, "default", {}, () => [a(h(s.value), 1)], !0)], 8, ve), i("button", {
332
+ }, [p(t.$slots, "default", {}, () => [a(m(s.value), 1)], !0)], 8, Ce), i("button", {
310
333
  type: "button",
311
334
  class: c(["ca-copy", { "ca-copy--copied": o.value }]),
312
335
  title: o.value ? "Copied!" : e.label,
313
336
  "aria-label": o.value ? "Copied" : e.label,
314
337
  onClick: l
315
- }, [o.value ? (d(), r("svg", be, [...n[0] ||= [i("polyline", { points: "20 6 9 17 4 12" }, null, -1)]])) : (d(), r("svg", xe, [...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", {
316
339
  x: "9",
317
340
  y: "9",
318
341
  width: "13",
319
342
  height: "13",
320
343
  rx: "2",
321
344
  ry: "2"
322
- }, 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, ye)]));
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)]));
323
346
  }
324
- }), [["__scopeId", "data-v-08c3420b"]]), Ce = ["href", "title"], we = /*#__PURE__*/ _(/* @__PURE__ */ o({
347
+ }), [["__scopeId", "data-v-b43dabdd"]]), Oe = ["href", "title"], ke = /*#__PURE__*/ v(/* @__PURE__ */ o({
325
348
  __name: "ExplorerLink",
326
349
  props: {
327
350
  href: {},
@@ -339,31 +362,62 @@ var ae = { class: "mw-header__brand" }, oe = { class: "mw-header__center" }, se
339
362
  let [e, t] = n.chars;
340
363
  return `${n.value.slice(0, e)}…${n.value.slice(-t)}`;
341
364
  });
342
- return (t, n) => (d(), r("a", {
365
+ return (t, n) => (u(), r("a", {
343
366
  class: "el-root",
344
- href: e.href,
367
+ href: h(S)(e.href),
345
368
  target: "_blank",
346
369
  rel: "noopener noreferrer",
347
370
  title: e.value
348
- }, [m(t.$slots, "default", {}, () => [a(h(i.value), 1)], !0)], 8, Ce));
371
+ }, [p(t.$slots, "default", {}, () => [a(m(i.value), 1)], !0)], 8, Oe));
349
372
  }
350
- }), [["__scopeId", "data-v-7a44607e"]]), Te = /* @__PURE__ */ new Set([
373
+ }), [["__scopeId", "data-v-073d1bbe"]]), Ae = /* @__PURE__ */ new Set([
351
374
  "operational",
352
375
  "degraded",
353
376
  "down",
354
377
  "unknown"
355
378
  ]);
379
+ function U(e) {
380
+ return typeof e == "string" && Ae.has(e);
381
+ }
382
+ function W(e) {
383
+ return U(e) ? e : "unknown";
384
+ }
356
385
  function G(e) {
357
- return typeof e == "string" && Te.has(e);
386
+ return typeof e != "string" || e.trim() === "" ? "" : e.replace(/[<>&"]/g, "");
387
+ }
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
+ };
358
406
  }
359
407
  function K(e) {
360
408
  if (typeof e != "object" || !e) return null;
361
409
  let t = e;
362
- return !G(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
+ };
363
417
  }
364
418
  //#endregion
365
419
  //#region src/components/StatusWidget.vue?vue&type=script&setup=true&lang.ts
366
- var Ee = ["href", "title"], q = { class: "status-widget__label" }, De = /*#__PURE__*/ _(/* @__PURE__ */ o({
420
+ var Ne = ["href", "title"], q = { class: "status-widget__label" }, Pe = /*#__PURE__*/ v(/* @__PURE__ */ o({
367
421
  __name: "StatusWidget",
368
422
  props: {
369
423
  apiUrl: { default: "https://status.meddleware.co.uk/api/status" },
@@ -381,8 +435,8 @@ var Ee = ["href", "title"], q = { class: "status-widget__label" }, De = /*#__PUR
381
435
  degraded: "Degraded",
382
436
  down: "Major outage",
383
437
  unknown: "Status unknown"
384
- }, o = f("ok"), s = f("All systems operational"), l = null;
385
- async function p() {
438
+ }, o = d("ok"), s = d("All systems operational"), l = null;
439
+ async function f() {
386
440
  try {
387
441
  let e = await fetch(t.apiUrl);
388
442
  if (!e.ok) throw Error(`HTTP ${e.status}`);
@@ -393,42 +447,42 @@ var Ee = ["href", "title"], q = { class: "status-widget__label" }, De = /*#__PUR
393
447
  o.value = "error", s.value = "Status unavailable";
394
448
  }
395
449
  }
396
- return u(() => {
397
- p(), l = setInterval(p, t.pollInterval);
398
- }), ee(() => {
450
+ return ee(() => {
451
+ f(), l = setInterval(f, t.pollInterval);
452
+ }), te(() => {
399
453
  l !== null && clearInterval(l);
400
- }), (t, n) => (d(), r("a", {
401
- href: e.href,
454
+ }), (t, n) => (u(), r("a", {
455
+ href: h(S)(e.href),
402
456
  target: "_blank",
403
- rel: "noopener",
457
+ rel: "noopener noreferrer",
404
458
  class: c(["status-widget", `status-widget--${o.value}`]),
405
459
  title: `Platform status: ${s.value}`
406
460
  }, [n[0] ||= i("span", {
407
461
  class: "status-widget__dot",
408
462
  "aria-hidden": "true"
409
- }, null, -1), i("span", q, h(s.value), 1)], 10, Ee));
463
+ }, null, -1), i("span", q, m(s.value), 1)], 10, Ne));
410
464
  }
411
- }), [["__scopeId", "data-v-20197983"]]), J = {
465
+ }), [["__scopeId", "data-v-6436368e"]]), J = {
412
466
  mainnet: "https://suivision.xyz",
413
467
  testnet: "https://testnet.suivision.xyz",
414
468
  devnet: "https://devnet.suivision.xyz"
415
469
  };
416
- function Oe(e, t, n = "testnet") {
470
+ function Fe(e, t, n = "testnet") {
417
471
  return `${J[n] ?? J.testnet}/${e}/${t}`;
418
472
  }
419
473
  //#endregion
420
474
  //#region src/composables/useColorMode.ts
421
- var Y = "mw-color-mode", X = f("system"), Z = !1;
422
- function ke() {
475
+ var Y = "mw-color-mode", X = d("system"), Z = !1;
476
+ function Ie() {
423
477
  return typeof window < "u" && typeof window.matchMedia == "function" && window.matchMedia("(prefers-color-scheme: dark)").matches;
424
478
  }
425
479
  function Q(e) {
426
- return e === "dark" || e === "system" && ke();
480
+ return e === "dark" || e === "system" && Ie();
427
481
  }
428
482
  function $(e) {
429
483
  typeof document > "u" || document.documentElement.setAttribute("data-theme", Q(e) ? "dark" : "light");
430
484
  }
431
- function Ae(e = "system") {
485
+ function Le(e = "system") {
432
486
  if (!Z) {
433
487
  Z = !0;
434
488
  let t = null;
@@ -458,4 +512,4 @@ function Ae(e = "system") {
458
512
  };
459
513
  }
460
514
  //#endregion
461
- export { O as AppFooter, v as AppHeader, C as AppSidebar, ge as AppTabNav, N as ColorModeControl, Se as CopyableAddress, we as ExplorerLink, pe as SidebarItem, De as StatusWidget, F as UiButton, V as UiCard, le as UiNotice, U as UiSelect, G as isStatusLevel, K as parseSnapshot, Oe as suiExplorerUrl, Ae 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,6 +1,6 @@
1
1
  {
2
2
  "name": "@meddleware/ui",
3
- "version": "0.1.12",
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
5
  "homepage": "https://docs.meddleware.co.uk/",
6
6
  "license": "0BSD",
@@ -31,13 +31,13 @@
31
31
  "vue": "^3.5.0"
32
32
  },
33
33
  "dependencies": {
34
- "@meddleware/design-tokens": "^0.1.2"
34
+ "@meddleware/design-tokens": "^0.1.3"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@vitejs/plugin-vue": "^6.0.8",
38
38
  "typescript": "^6.0.0",
39
39
  "vite": "^8.2.2",
40
- "vitest": "~4.1.11",
40
+ "vitest": "~5.0.1",
41
41
  "vue": "^3.5.42",
42
42
  "vue-tsc": "~3.3.11"
43
43
  },