@ponchia/ui 0.3.3 → 0.3.4

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
@@ -3,7 +3,7 @@
3
3
  [![npm](https://img.shields.io/npm/v/@ponchia/ui?logo=npm)](https://www.npmjs.com/package/@ponchia/ui)
4
4
  [![npm provenance](https://img.shields.io/badge/npm-provenance-blue?logo=npm)](https://www.npmjs.com/package/@ponchia/ui#provenance)
5
5
  [![runtime deps](https://img.shields.io/badge/runtime%20deps-0-brightgreen)](package.json)
6
- [![bundle](https://img.shields.io/badge/dist-~54kB%20%2F%20~10kB%20gzip-informational)](scripts/check-dist.mjs)
6
+ [![bundle](https://img.shields.io/badge/dist-~64kB%20%2F%20~11kB%20gzip-informational)](scripts/check-dist.mjs)
7
7
  [![CI](https://github.com/Ponchia/bronto-ui/actions/workflows/ci.yml/badge.svg)](https://github.com/Ponchia/bronto-ui/actions/workflows/ci.yml)
8
8
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
9
9
 
@@ -12,7 +12,8 @@ monochrome surfaces, a single red accent, dot-matrix display type (Doto),
12
12
  flat hairline borders, restrained motion. CSS-first and framework-agnostic.
13
13
 
14
14
  **New here?** → [Getting started](#getting-started) ·
15
- [Reference](docs/reference.md) · [Theming](docs/theming.md) ·
15
+ [Reference](docs/reference.md) · [Usage](docs/usage.md) ·
16
+ [Theming](docs/theming.md) · [Contrast](docs/contrast.md) ·
16
17
  [Roadmap](ROADMAP.md) · [Contributing](CONTRIBUTING.md)
17
18
 
18
19
  > **Editor IntelliSense.** The package ships a VS Code CSS Custom Data
@@ -65,7 +66,7 @@ chain:
65
66
  @import '@ponchia/ui'; /* → dist/bronto.css, the whole framework */
66
67
  ```
67
68
 
68
- ~54 kB raw / ~10 kB gzip, one request, same `@layer bronto`. (The
69
+ ~64 kB raw / ~11 kB gzip, one request, same `@layer bronto`. (The
69
70
  enforced ceiling lives in `scripts/check-dist.mjs`, not this prose —
70
71
  treat these figures as indicative.) Source CSS, tokens/classes/behaviors
71
72
  entrypoints are unchanged — use whichever fits.
@@ -108,11 +108,18 @@ export declare function initDialog(opts?: DelegateOpts): Cleanup;
108
108
 
109
109
  export interface ToastOpts {
110
110
  /** Status tone — maps to `ui-toast--<tone>`. */
111
- tone?: 'accent' | 'success' | 'warning' | 'danger';
111
+ tone?: 'accent' | 'success' | 'warning' | 'danger' | 'info';
112
112
  /** Optional uppercase label rendered above the message. */
113
113
  title?: string;
114
114
  /** Auto-dismiss delay in ms. 0 keeps it until dismissed. Default: 4000. */
115
115
  duration?: number;
116
+ /**
117
+ * Route to the assertive live region so AT interrupts immediately.
118
+ * Defaults to `true` when `tone === 'danger'`.
119
+ */
120
+ assertive?: boolean;
121
+ /** Render a dismiss button on the toast. */
122
+ closable?: boolean;
116
123
  }
117
124
 
118
125
  /**
@@ -319,7 +319,7 @@ export function initDialog({ root } = {}) {
319
319
  * that receives its first child in the same tick is not reliably
320
320
  * announced by VoiceOver/NVDA). On first creation the empty region is
321
321
  * inserted and the toast is appended on the next frame for the same
322
- * reason. `tone` is accent/success/warning/danger; `title` is an
322
+ * reason. `tone` is accent/success/warning/danger/info; `title` is an
323
323
  * optional uppercase label; `duration` ms before auto-dismiss (0 keeps
324
324
  * it until dismissed). Returns a function that dismisses the toast
325
325
  * early. SSR-safe (no-op).
@@ -607,11 +607,29 @@ export function initFormValidation({ root } = {}) {
607
607
  };
608
608
 
609
609
  return bindOnce(host, 'formValidation', () => {
610
+ // Suppress native bubbles UP FRONT for forms present at init. The
611
+ // in-handler `noValidate = true` only fires after the first
612
+ // submit/blur, so the very first invalid real-browser submit would
613
+ // otherwise show the native UA bubble instead of the Bronto
614
+ // summary — contradicting the documented contract. (Forms added
615
+ // after init are still covered by the in-handler set.)
616
+ // Feature-detect rather than `instanceof Element` — `Element` is not
617
+ // a guaranteed global (SSR / the no-DOM test env), and `host` is
618
+ // either `document` (no `.matches`) or a root Element.
619
+ const selfForm =
620
+ typeof host.matches === 'function' && host.matches('[data-bronto-validate]') ? [host] : [];
621
+ const forms = [...selfForm, ...(host.querySelectorAll?.('[data-bronto-validate]') ?? [])];
622
+ const priorNoValidate = new Map();
623
+ for (const f of forms) {
624
+ priorNoValidate.set(f, f.noValidate);
625
+ f.noValidate = true;
626
+ }
610
627
  host.addEventListener('submit', onSubmit, true);
611
628
  host.addEventListener('focusout', onBlur);
612
629
  return () => {
613
630
  host.removeEventListener('submit', onSubmit, true);
614
631
  host.removeEventListener('focusout', onBlur);
632
+ for (const [f, v] of priorNoValidate) f.noValidate = v;
615
633
  };
616
634
  });
617
635
  }
@@ -702,6 +720,12 @@ export function initCombobox({ root } = {}) {
702
720
  if (match) any = true;
703
721
  }
704
722
  if (empty) empty.hidden = any;
723
+ // The active option may have just been filtered out — drop the
724
+ // stale activedescendant so Enter can't select a hidden option.
725
+ if (active >= 0 && options[active]?.hidden) {
726
+ active = -1;
727
+ setActive(null);
728
+ }
705
729
  open();
706
730
  };
707
731
 
@@ -762,7 +786,7 @@ export function initCombobox({ root } = {}) {
762
786
  }
763
787
  break;
764
788
  case 'Enter':
765
- if (!list.hidden && active >= 0) {
789
+ if (!list.hidden && active >= 0 && !options[active].hidden) {
766
790
  e.preventDefault();
767
791
  select(options[active]);
768
792
  }
@@ -39,6 +39,7 @@ export declare const cls: {
39
39
  readonly badgeSuccess: 'ui-badge--success';
40
40
  readonly badgeWarning: 'ui-badge--warning';
41
41
  readonly badgeDanger: 'ui-badge--danger';
42
+ readonly badgeInfo: 'ui-badge--info';
42
43
  readonly badgeMuted: 'ui-badge--muted';
43
44
  readonly badgeDot: 'ui-badge--dot';
44
45
  readonly chip: 'ui-chip';
@@ -53,6 +54,7 @@ export declare const cls: {
53
54
  readonly dotSuccess: 'ui-dot--success';
54
55
  readonly dotWarning: 'ui-dot--warning';
55
56
  readonly dotDanger: 'ui-dot--danger';
57
+ readonly dotInfo: 'ui-dot--info';
56
58
  readonly dotLive: 'ui-dot--live';
57
59
  readonly dotgrid: 'ui-dotgrid';
58
60
  readonly dotgridAccent: 'ui-dotgrid--accent';
@@ -96,6 +98,7 @@ export declare const cls: {
96
98
  readonly alertSuccess: 'ui-alert--success';
97
99
  readonly alertWarning: 'ui-alert--warning';
98
100
  readonly alertDanger: 'ui-alert--danger';
101
+ readonly alertInfo: 'ui-alert--info';
99
102
  readonly toastStack: 'ui-toast-stack';
100
103
  readonly toastStackAssertive: 'ui-toast-stack--assertive';
101
104
  readonly toast: 'ui-toast';
@@ -105,6 +108,7 @@ export declare const cls: {
105
108
  readonly toastSuccess: 'ui-toast--success';
106
109
  readonly toastWarning: 'ui-toast--warning';
107
110
  readonly toastDanger: 'ui-toast--danger';
111
+ readonly toastInfo: 'ui-toast--info';
108
112
  readonly tooltip: 'ui-tooltip';
109
113
  readonly tooltipBubble: 'ui-tooltip__bubble';
110
114
  readonly popover: 'ui-popover';
@@ -255,7 +259,7 @@ export interface CardOpts {
255
259
  interactive?: boolean;
256
260
  }
257
261
  export interface BadgeOpts {
258
- tone?: 'accent' | 'success' | 'warning' | 'danger' | 'muted';
262
+ tone?: 'accent' | 'success' | 'warning' | 'danger' | 'info' | 'muted';
259
263
  dot?: boolean;
260
264
  }
261
265
  export interface NumOpts {
@@ -269,7 +273,7 @@ export interface LinkOpts {
269
273
  cta?: boolean;
270
274
  }
271
275
  export interface DotOpts {
272
- tone?: 'accent' | 'success' | 'warning' | 'danger';
276
+ tone?: 'accent' | 'success' | 'warning' | 'danger' | 'info';
273
277
  live?: boolean;
274
278
  }
275
279
  export interface DotgridOpts {
@@ -293,7 +297,7 @@ export interface ClusterOpts {
293
297
  export interface StaggerOpts {
294
298
  auto?: boolean;
295
299
  }
296
- export type Tone = 'accent' | 'success' | 'warning' | 'danger';
300
+ export type Tone = 'accent' | 'success' | 'warning' | 'danger' | 'info';
297
301
  export interface AlertOpts {
298
302
  tone?: Tone;
299
303
  }
package/classes/index.js CHANGED
@@ -43,6 +43,7 @@ export const cls = Object.freeze({
43
43
  badgeSuccess: 'ui-badge--success',
44
44
  badgeWarning: 'ui-badge--warning',
45
45
  badgeDanger: 'ui-badge--danger',
46
+ badgeInfo: 'ui-badge--info',
46
47
  badgeMuted: 'ui-badge--muted',
47
48
  badgeDot: 'ui-badge--dot',
48
49
  chip: 'ui-chip',
@@ -58,6 +59,7 @@ export const cls = Object.freeze({
58
59
  dotSuccess: 'ui-dot--success',
59
60
  dotWarning: 'ui-dot--warning',
60
61
  dotDanger: 'ui-dot--danger',
62
+ dotInfo: 'ui-dot--info',
61
63
  dotLive: 'ui-dot--live',
62
64
  dotgrid: 'ui-dotgrid',
63
65
  dotgridAccent: 'ui-dotgrid--accent',
@@ -103,6 +105,7 @@ export const cls = Object.freeze({
103
105
  alertSuccess: 'ui-alert--success',
104
106
  alertWarning: 'ui-alert--warning',
105
107
  alertDanger: 'ui-alert--danger',
108
+ alertInfo: 'ui-alert--info',
106
109
  toastStack: 'ui-toast-stack',
107
110
  toastStackAssertive: 'ui-toast-stack--assertive',
108
111
  toast: 'ui-toast',
@@ -112,6 +115,7 @@ export const cls = Object.freeze({
112
115
  toastSuccess: 'ui-toast--success',
113
116
  toastWarning: 'ui-toast--warning',
114
117
  toastDanger: 'ui-toast--danger',
118
+ toastInfo: 'ui-toast--info',
115
119
  tooltip: 'ui-tooltip',
116
120
  tooltipBubble: 'ui-tooltip__bubble',
117
121
  popover: 'ui-popover',
@@ -287,6 +291,7 @@ export const ui = {
287
291
  tone === 'success' && cls.badgeSuccess,
288
292
  tone === 'warning' && cls.badgeWarning,
289
293
  tone === 'danger' && cls.badgeDanger,
294
+ tone === 'info' && cls.badgeInfo,
290
295
  tone === 'muted' && cls.badgeMuted,
291
296
  dot && cls.badgeDot,
292
297
  ),
@@ -306,6 +311,7 @@ export const ui = {
306
311
  tone === 'success' && cls.dotSuccess,
307
312
  tone === 'warning' && cls.dotWarning,
308
313
  tone === 'danger' && cls.dotDanger,
314
+ tone === 'info' && cls.dotInfo,
309
315
  live && cls.dotLive,
310
316
  ),
311
317
  dotgrid: ({ accent, dense } = {}) =>
@@ -328,6 +334,7 @@ export const ui = {
328
334
  tone === 'success' && cls.alertSuccess,
329
335
  tone === 'warning' && cls.alertWarning,
330
336
  tone === 'danger' && cls.alertDanger,
337
+ tone === 'info' && cls.alertInfo,
331
338
  ),
332
339
  toast: ({ tone } = {}) =>
333
340
  j(
@@ -336,6 +343,7 @@ export const ui = {
336
343
  tone === 'success' && cls.toastSuccess,
337
344
  tone === 'warning' && cls.toastWarning,
338
345
  tone === 'danger' && cls.toastDanger,
346
+ tone === 'info' && cls.toastInfo,
339
347
  ),
340
348
  progress: ({ indeterminate } = {}) => j(cls.progress, indeterminate && cls.progressIndeterminate),
341
349
  dotspinner: ({ size } = {}) =>
@@ -85,6 +85,10 @@
85
85
  "name": "--bronto-color-focus",
86
86
  "description": "Global scale token. Value: `var(--focus-ring)`"
87
87
  },
88
+ {
89
+ "name": "--bronto-color-info",
90
+ "description": "Global scale token. Value: `var(--info)`"
91
+ },
88
92
  {
89
93
  "name": "--bronto-color-on-action",
90
94
  "description": "Global scale token. Value: `var(--button-text)`"
@@ -131,7 +135,7 @@
131
135
  },
132
136
  {
133
137
  "name": "--display",
134
- "description": "Global scale token. Value: `'Doto', var(--mono)`"
138
+ "description": "Global scale token. Value: `var(--dot-font)`"
135
139
  },
136
140
  {
137
141
  "name": "--dot-font",
@@ -185,6 +189,14 @@
185
189
  "name": "--focus-ring",
186
190
  "description": "Theme token. Light: `var(--accent)` · Dark: `var(--accent)`"
187
191
  },
192
+ {
193
+ "name": "--info",
194
+ "description": "Theme token. Light: `#1f63c4` · Dark: `#6fb0e6`"
195
+ },
196
+ {
197
+ "name": "--info-soft",
198
+ "description": "Theme token. Light: `rgb(31, 99, 196, 0.12)` · Dark: `rgb(111, 176, 230, 0.14)`"
199
+ },
188
200
  {
189
201
  "name": "--line",
190
202
  "description": "Theme token. Light: `#d8d8d4` · Dark: `#2a2a2a`"
package/css/dots.css CHANGED
@@ -94,6 +94,10 @@
94
94
  background: var(--danger);
95
95
  }
96
96
 
97
+ .ui-dot--info {
98
+ background: var(--info);
99
+ }
100
+
97
101
  .ui-dot--live {
98
102
  background: var(--success);
99
103
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 70%, transparent);
@@ -229,7 +233,7 @@
229
233
 
230
234
  .ui-dotbar i {
231
235
  background: var(--field-dot);
232
- border-radius: 1px;
236
+ border-radius: var(--radius-sm);
233
237
  flex: 1;
234
238
  block-size: 0.5rem;
235
239
  }
package/css/feedback.css CHANGED
@@ -95,6 +95,14 @@
95
95
  background: var(--danger);
96
96
  }
97
97
 
98
+ .ui-alert--info {
99
+ border-inline-start-color: var(--info);
100
+ }
101
+
102
+ .ui-alert--info::before {
103
+ background: var(--info);
104
+ }
105
+
98
106
  @media (hover: hover) {
99
107
  .ui-alert__dismiss:hover {
100
108
  color: var(--text);
@@ -201,6 +209,10 @@
201
209
  background: var(--danger);
202
210
  }
203
211
 
212
+ .ui-toast--info::before {
213
+ background: var(--info);
214
+ }
215
+
204
216
  /* --- Tooltip — CSS-only, hover/focus, no JS --- */
205
217
 
206
218
  .ui-tooltip {
@@ -477,6 +477,11 @@
477
477
  border-color: color-mix(in srgb, var(--danger) 40%, var(--line));
478
478
  }
479
479
 
480
+ .ui-badge--info {
481
+ background: var(--info-soft);
482
+ border-color: color-mix(in srgb, var(--info) 40%, var(--line));
483
+ }
484
+
480
485
  /* Idle / unknown — the honest "no signal yet" tone (distinct from the
481
486
  default tinted badge). Token-safe; no new hue. */
482
487
  .ui-badge--muted {
@@ -513,6 +518,10 @@
513
518
  background: var(--danger);
514
519
  }
515
520
 
521
+ .ui-badge--info.ui-badge--dot::before {
522
+ background: var(--info);
523
+ }
524
+
516
525
  /* --- Key/value list --- */
517
526
 
518
527
  .ui-key-value {
package/css/tokens.css CHANGED
@@ -29,8 +29,8 @@
29
29
  --mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', ui-monospace, monospace;
30
30
  --sans:
31
31
  'Inter', 'SF Pro Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
32
- --display: 'Doto', var(--mono);
33
32
  --dot-font: 'Doto', var(--mono);
33
+ --display: var(--dot-font);
34
34
  --text-2xs: 0.68rem;
35
35
  --text-xs: 0.76rem;
36
36
  --text-sm: 0.86rem;
@@ -99,6 +99,7 @@
99
99
  --bronto-color-success: var(--success);
100
100
  --bronto-color-warning: var(--warning);
101
101
  --bronto-color-danger: var(--danger);
102
+ --bronto-color-info: var(--info);
102
103
 
103
104
  /* Aliases kept for back-compat with existing semantic classes */
104
105
  --surface: var(--panel);
@@ -138,6 +139,8 @@
138
139
  --orange-soft: rgb(168, 95, 50, 0.13);
139
140
  --danger: #c01622;
140
141
  --danger-soft: rgb(192, 22, 34, 0.1);
142
+ --info: #1f63c4;
143
+ --info-soft: rgb(31, 99, 196, 0.12);
141
144
  --code-bg: rgb(10, 10, 10, 0.05);
142
145
  --button-text: #ffffff;
143
146
  --field-dot: rgb(10, 10, 10, 0.16);
@@ -189,6 +192,8 @@
189
192
  --orange-soft: rgb(208, 140, 91, 0.15);
190
193
  --danger: #ff4d54;
191
194
  --danger-soft: rgb(255, 77, 84, 0.15);
195
+ --info: #6fb0e6;
196
+ --info-soft: rgb(111, 176, 230, 0.14);
192
197
  --code-bg: rgb(255, 255, 255, 0.05);
193
198
  --button-text: #000000;
194
199
  --field-dot: rgb(242, 242, 242, 0.14);
@@ -226,6 +231,8 @@
226
231
  --orange-soft: rgb(208, 140, 91, 0.15);
227
232
  --danger: #ff4d54;
228
233
  --danger-soft: rgb(255, 77, 84, 0.15);
234
+ --info: #6fb0e6;
235
+ --info-soft: rgb(111, 176, 230, 0.14);
229
236
  --code-bg: rgb(255, 255, 255, 0.05);
230
237
  --button-text: #000000;
231
238
  --field-dot: rgb(242, 242, 242, 0.14);