@vialiq/web-components 0.18.0 → 0.19.0

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,4 +1,4 @@
1
- # @vialiq/web-components
1
+ ,# @vialiq/web-components
2
2
 
3
3
  Buildable and publishable Lit web component library for the Vi design system.
4
4
 
@@ -20,6 +20,7 @@ npm install @vialiq/web-components lit
20
20
  Since these components are built using standard Custom Elements APIs, they are compatible with any web framework or vanilla web stack.
21
21
 
22
22
  ### Subpath Exports & Tree-Shaking
23
+
23
24
  To keep your bundle sizes minimal, import only the components you need:
24
25
 
25
26
  ```ts
@@ -39,9 +40,11 @@ import { registerIcons, ViButton } from '@vialiq/web-components';
39
40
  ### Framework Guides
40
41
 
41
42
  #### 1. React
43
+
42
44
  React 19 supports Custom Elements natively. If you are using React <19, you must set properties and custom events manually via `ref` or use custom wrapper packages.
43
45
 
44
46
  **React 19 Example:**
47
+
45
48
  ```tsx
46
49
  import React from 'react';
47
50
  import '@vialiq/web-components/button';
@@ -49,38 +52,43 @@ import '@vialiq/web-components/input';
49
52
 
50
53
  export function SearchForm() {
51
54
  return (
52
- <form onSubmit={(e) => { e.preventDefault(); console.log('Submitted'); }}>
53
- <vi-input
54
- name="query"
55
- placeholder="Search..."
56
- required
57
- onvialiq-input={(e: any) => console.log(e.detail.value)}
58
- />
59
- <vi-button type="submit" variant="primary">Search</vi-button>
55
+ <form
56
+ onSubmit={(e) => {
57
+ e.preventDefault();
58
+ console.log('Submitted');
59
+ }}
60
+ >
61
+ <vi-input name="query" placeholder="Search..." required onvialiq-input={(e: any) => console.log(e.detail.value)} />
62
+ <vi-button type="submit" variant="primary">
63
+ Search
64
+ </vi-button>
60
65
  </form>
61
66
  );
62
67
  }
63
68
  ```
64
69
 
65
70
  #### 2. Vue
71
+
66
72
  Vue supports custom elements seamlessly out-of-the-box. Register the tags so Vue's compiler knows not to treat them as Vue components:
67
73
 
68
74
  **vite.config.ts:**
75
+
69
76
  ```ts
70
77
  export default defineConfig({
71
78
  plugins: [
72
79
  vue({
73
80
  template: {
74
81
  compilerOptions: {
75
- isCustomElement: (tag) => tag.startsWith('vi-')
76
- }
77
- }
78
- })
79
- ]
82
+ isCustomElement: (tag) => tag.startsWith('vi-'),
83
+ },
84
+ },
85
+ }),
86
+ ],
80
87
  });
81
88
  ```
82
89
 
83
90
  **Vue Component Template:**
91
+
84
92
  ```html
85
93
  <template>
86
94
  <div>
@@ -91,9 +99,11 @@ export default defineConfig({
91
99
  ```
92
100
 
93
101
  #### 3. Angular
102
+
94
103
  To use Custom Elements in Angular, you must add the `CUSTOM_ELEMENTS_SCHEMA` to the `schemas` array of the `@Component` (for standalone components) or `@NgModule` where they are consumed.
95
104
 
96
105
  **Standalone Component Setup:**
106
+
97
107
  ```typescript
98
108
  import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
99
109
 
@@ -101,7 +111,7 @@ import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
101
111
  selector: 'app-search-form',
102
112
  standalone: true,
103
113
  templateUrl: './search-form.component.html',
104
- schemas: [CUSTOM_ELEMENTS_SCHEMA]
114
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
105
115
  })
106
116
  export class SearchFormComponent {
107
117
  searchQuery = '';
@@ -114,19 +124,17 @@ export class SearchFormComponent {
114
124
  ```
115
125
 
116
126
  **search-form.component.html Template:**
127
+
117
128
  ```html
118
129
  <div>
119
- <vi-input
120
- [value]="searchQuery"
121
- (vialiq-input)="onInput($event)"
122
- placeholder="Search catalog..."
123
- ></vi-input>
124
-
130
+ <vi-input [value]="searchQuery" (vialiq-input)="onInput($event)" placeholder="Search catalog..."></vi-input>
131
+
125
132
  <vi-button variant="primary">Search</vi-button>
126
133
  </div>
127
134
  ```
128
135
 
129
136
  #### 4. Next.js (SSR / React Server Components)
137
+
130
138
  Custom Elements must register on the browser's `window` object. Next.js and server-side rendering environments require lazy-loading or dynamic imports to ensure registration occurs client-side.
131
139
 
132
140
  ```tsx
@@ -144,29 +152,32 @@ export default function MyClientComponent() {
144
152
  }
145
153
  ```
146
154
 
147
-
148
155
  ---
149
156
 
150
157
  ## Component API & Detailed Examples
151
158
 
152
159
  ### Button ([vi-button](./src/button/vi-button.ts))
160
+
153
161
  The [ViButton](./src/button/vi-button.ts) is a versatile button component that wraps a native `<button>` element with keyboard interaction, focus indicators, visual variations, and slot options.
154
162
 
155
163
  #### Properties & Attributes
156
- | Attribute | Property | Type | Default | Description |
157
- | :--- | :--- | :--- | :--- | :--- |
158
- | `variant` | `variant` | `'primary'\|'secondary'\|'danger'\|'success'\|'info'\|'ghost'` | `'primary'` | Visual design style. |
159
- | `size` | `size` | `'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Sizing scale. |
160
- | `icon-placement` | `iconPlacement` | `'start'\|'end'` | `'start'` | Location of the icon relative to the label. |
161
- | `full-width` | `fullWidth` | `boolean` | `false` | Sets width to 100% of container. |
162
- | `icon-only` | `iconOnly` | `boolean` | `false` | Squares padding and matches dimensions for an icon-only layout. |
163
- | `disabled` | `disabled` | `boolean` | `false` | Disables button interactions and sets `tabindex="-1"`. |
164
+
165
+ | Attribute | Property | Type | Default | Description |
166
+ | :--------------- | :-------------- | :------------------------------------------------------------- | :---------- | :-------------------------------------------------------------- |
167
+ | `variant` | `variant` | `'primary'\|'secondary'\|'danger'\|'success'\|'info'\|'ghost'` | `'primary'` | Visual design style. |
168
+ | `size` | `size` | `'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Sizing scale. |
169
+ | `icon-placement` | `iconPlacement` | `'start'\|'end'` | `'start'` | Location of the icon relative to the label. |
170
+ | `full-width` | `fullWidth` | `boolean` | `false` | Sets width to 100% of container. |
171
+ | `icon-only` | `iconOnly` | `boolean` | `false` | Squares padding and matches dimensions for an icon-only layout. |
172
+ | `disabled` | `disabled` | `boolean` | `false` | Disables button interactions and sets `tabindex="-1"`. |
164
173
 
165
174
  #### Slots
175
+
166
176
  - **Default Slot**: Button label (text/content).
167
177
  - **`icon` Slot**: Container for standard icons.
168
178
 
169
179
  #### CSS Parts
180
+
170
181
  - `button`: The native internal `<button>` element.
171
182
  - `icon`: The icon wrapper element.
172
183
  - `label`: The text label span wrapper.
@@ -174,6 +185,7 @@ The [ViButton](./src/button/vi-button.ts) is a versatile button component that w
174
185
  #### Snippets
175
186
 
176
187
  **Standard Button Variants:**
188
+
177
189
  ```html
178
190
  <vi-button variant="primary">Primary Action</vi-button>
179
191
  <vi-button variant="secondary">Secondary Action</vi-button>
@@ -182,6 +194,7 @@ The [ViButton](./src/button/vi-button.ts) is a versatile button component that w
182
194
  ```
183
195
 
184
196
  **Sizes & Layouts:**
197
+
185
198
  ```html
186
199
  <vi-button size="xs">Extra Small</vi-button>
187
200
  <vi-button size="sm">Small</vi-button>
@@ -193,6 +206,7 @@ The [ViButton](./src/button/vi-button.ts) is a versatile button component that w
193
206
  ```
194
207
 
195
208
  **Icons Support:**
209
+
196
210
  ```html
197
211
  <!-- Icon at start (default) -->
198
212
  <vi-button>
@@ -215,34 +229,40 @@ The [ViButton](./src/button/vi-button.ts) is a versatile button component that w
215
229
  ---
216
230
 
217
231
  ### Input ([vi-input](./src/input/vi-input.ts))
232
+
218
233
  The [ViInput](./src/input/vi-input.ts) component is a form-associated custom text input control. It wraps a native single-line input field and automatically supports accessibility features, validation states, helper text slots, and custom style configuration.
219
234
 
220
235
  #### Properties & Attributes
221
- | Attribute | Property | Type | Default | Description |
222
- | :--- | :--- | :--- | :--- | :--- |
223
- | `type` | `type` | `'text'\|'email'\|'password'\|'search'\|'tel'\|'url'\|'number'` | `'text'` | Renders appropriate input format. |
224
- | `placeholder` | `placeholder` | `string` | `''` | Input placeholder text. |
225
- | `name` | `name` | `string` | `''` | Form participation field name. |
226
- | `value` | `value` | `string` | `''` | Controlled input value. |
227
- | `disabled` | `disabled` | `boolean` | `false` | Disables field interactions. |
228
- | `readonly` | `readonly` | `boolean` | `false` | Disables keyboard editing. |
229
- | `required` | `required` | `boolean` | `false` | Marks field validation as mandatory. |
230
- | `status` | `status` | `'default'\|'valid'\|'invalid'` | `'default'` | Controls validation visual presentation. |
231
- | `validity-message` | `validityMessage` | `string` | `''` | Native or custom error message to display in UI. |
232
- | `size` | `size` | `'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Controls font sizes and paddings. |
233
- | `aria-label` | `ariaLabel` | `string` | `''` | Accessibility label. |
234
- | `aria-labelledby` | `ariaLabelledby` | `string` | `''` | ID reference of accessible label. |
236
+
237
+ | Attribute | Property | Type | Default | Description |
238
+ | :----------------- | :---------------- | :-------------------------------------------------------------- | :---------- | :----------------------------------------------- |
239
+ | `type` | `type` | `'text'\|'email'\|'password'\|'search'\|'tel'\|'url'\|'number'` | `'text'` | Renders appropriate input format. |
240
+ | `placeholder` | `placeholder` | `string` | `''` | Input placeholder text. |
241
+ | `name` | `name` | `string` | `''` | Form participation field name. |
242
+ | `value` | `value` | `string` | `''` | Controlled input value. |
243
+ | `disabled` | `disabled` | `boolean` | `false` | Disables field interactions. |
244
+ | `readonly` | `readonly` | `boolean` | `false` | Disables keyboard editing. |
245
+ | `required` | `required` | `boolean` | `false` | Marks field validation as mandatory. |
246
+ | `status` | `status` | `'default'\|'valid'\|'invalid'` | `'default'` | Controls validation visual presentation. |
247
+ | `validity-message` | `validityMessage` | `string` | `''` | Native or custom error message to display in UI. |
248
+ | `size` | `size` | `'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Controls font sizes and paddings. |
249
+ | `aria-label` | `ariaLabel` | `string` | `''` | Accessibility label. |
250
+ | `aria-labelledby` | `ariaLabelledby` | `string` | `''` | ID reference of accessible label. |
235
251
 
236
252
  #### Slots
253
+
237
254
  - **`helper` Slot**: Location to insert description text below the input field.
238
255
 
239
256
  #### Events
257
+
240
258
  - `vialiq-input`: Fires on every keypress. Detail: `{ value: string }`.
241
259
  - `vialiq-change`: Fires when element loses focus (blur). Detail: `{ value: string }`.
242
260
  - `invalid`: Native HTML5 validation failed event.
243
261
 
244
262
  #### CSS Custom Properties
263
+
245
264
  Exposes variables for custom theme styling:
265
+
246
266
  ```css
247
267
  vi-input {
248
268
  --vi-input-border-color: #d1d5db;
@@ -260,6 +280,7 @@ vi-input {
260
280
  #### Snippets
261
281
 
262
282
  **Basic Text & Password Inputs:**
283
+
263
284
  ```html
264
285
  <vi-input name="username" placeholder="Enter username"></vi-input>
265
286
 
@@ -268,13 +289,9 @@ vi-input {
268
289
  ```
269
290
 
270
291
  **Required with Helper Text & Validation:**
292
+
271
293
  ```html
272
- <vi-input
273
- type="email"
274
- name="email"
275
- placeholder="you@vialiq.com"
276
- required
277
- >
294
+ <vi-input type="email" name="email" placeholder="you@vialiq.com" required>
278
295
  <span slot="helper">We will never share your email address.</span>
279
296
  </vi-input>
280
297
  ```
@@ -282,26 +299,30 @@ vi-input {
282
299
  ---
283
300
 
284
301
  ### Checkbox ([vi-checkbox](./src/checkbox/vi-checkbox.ts))
302
+
285
303
  The [ViCheckbox](./src/checkbox/vi-checkbox.ts) is a customizable form-associated checkbox control using SVG graphics for checkmarks and supporting the indeterminate (mixed) validation state.
286
304
 
287
305
  #### Properties & Attributes
288
- | Attribute | Property | Type | Default | Description |
289
- | :--- | :--- | :--- | :--- | :--- |
290
- | `checked` | `checked` | `boolean` | `false` | Checked state. |
291
- | `indeterminate` | `indeterminate` | `boolean` | `false` | Indeterminate (mixed) dash state. |
292
- | `value` | `value` | `string` | `'on'` | Submitted form value. |
293
- | `name` | `name` | `string` | `''` | Form field identifier. |
294
- | `disabled` | `disabled` | `boolean` | `false` | Disables checkbox toggles. |
295
- | `required` | `required` | `boolean` | `false` | Makes checking field mandatory. |
296
- | `status` | `status` | `'default'\|'valid'\|'invalid'` | `'default'` | Controls validation visual border colors. |
297
- | `size` | `size` | `'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Controls dimension metrics. |
306
+
307
+ | Attribute | Property | Type | Default | Description |
308
+ | :-------------- | :-------------- | :------------------------------ | :---------- | :---------------------------------------- |
309
+ | `checked` | `checked` | `boolean` | `false` | Checked state. |
310
+ | `indeterminate` | `indeterminate` | `boolean` | `false` | Indeterminate (mixed) dash state. |
311
+ | `value` | `value` | `string` | `'on'` | Submitted form value. |
312
+ | `name` | `name` | `string` | `''` | Form field identifier. |
313
+ | `disabled` | `disabled` | `boolean` | `false` | Disables checkbox toggles. |
314
+ | `required` | `required` | `boolean` | `false` | Makes checking field mandatory. |
315
+ | `status` | `status` | `'default'\|'valid'\|'invalid'` | `'default'` | Controls validation visual border colors. |
316
+ | `size` | `size` | `'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Controls dimension metrics. |
298
317
 
299
318
  #### Events
319
+
300
320
  - `vialiq-change`: Fired on user toggle. Detail: `{ checked: boolean, value: string }`.
301
321
 
302
322
  #### Snippets
303
323
 
304
324
  **Simple Configurations:**
325
+
305
326
  ```html
306
327
  <vi-checkbox name="agree" required>I accept the terms and conditions</vi-checkbox>
307
328
 
@@ -309,6 +330,7 @@ The [ViCheckbox](./src/checkbox/vi-checkbox.ts) is a customizable form-associate
309
330
  ```
310
331
 
311
332
  **Indeterminate State (Parent/Child controls):**
333
+
312
334
  ```html
313
335
  <vi-checkbox id="select-all" indeterminate>Select All Modules</vi-checkbox>
314
336
  ```
@@ -316,29 +338,33 @@ The [ViCheckbox](./src/checkbox/vi-checkbox.ts) is a customizable form-associate
316
338
  ---
317
339
 
318
340
  ### Radio Group & Radio ([vi-radio-group](./src/radio/vi-radio-group.ts) & [vi-radio](./src/radio/vi-radio.ts))
341
+
319
342
  The [ViRadioGroup](./src/radio/vi-radio-group.ts) and [ViRadio](./src/radio/vi-radio.ts) work in tandem. The group container handles form-association, propagation of attributes (`name`, `disabled`, `size`), roving tabindexes, and WAI-ARIA compliant keyboard navigation via arrow keys.
320
343
 
321
344
  #### `<vi-radio-group>` Properties & Attributes
322
- | Attribute | Property | Type | Default | Description |
323
- | :--- | :--- | :--- | :--- | :--- |
324
- | `value` | `value` | `string` | `''` | Selection value. |
325
- | `name` | `name` | `string` | `''` | Shared name propagated to children. |
326
- | `disabled` | `disabled` | `boolean` | `false` | Disables entire selection array. |
327
- | `required` | `required` | `boolean` | `false` | Marks group validation as mandatory. |
328
- | `status` | `status` | `'default'\|'valid'\|'invalid'` | `'default'` | Group visual status. |
329
- | `validity-message` | `validity-message` | `string` | `''` | Helper error label when validation triggers. |
330
- | `orientation` | `orientation` | `'vertical'\|'horizontal'` | `'vertical'` | Direction grid layout. |
331
- | `size` | `size` | `'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Shared size propagated to children. |
332
- | `allow-dblclick-clear` | `allowDblclickClear` | `boolean` | `false` | Double clicking a selected radio deselects it. |
345
+
346
+ | Attribute | Property | Type | Default | Description |
347
+ | :--------------------- | :------------------- | :------------------------------ | :----------- | :--------------------------------------------- |
348
+ | `value` | `value` | `string` | `''` | Selection value. |
349
+ | `name` | `name` | `string` | `''` | Shared name propagated to children. |
350
+ | `disabled` | `disabled` | `boolean` | `false` | Disables entire selection array. |
351
+ | `required` | `required` | `boolean` | `false` | Marks group validation as mandatory. |
352
+ | `status` | `status` | `'default'\|'valid'\|'invalid'` | `'default'` | Group visual status. |
353
+ | `validity-message` | `validity-message` | `string` | `''` | Helper error label when validation triggers. |
354
+ | `orientation` | `orientation` | `'vertical'\|'horizontal'` | `'vertical'` | Direction grid layout. |
355
+ | `size` | `size` | `'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Shared size propagated to children. |
356
+ | `allow-dblclick-clear` | `allowDblclickClear` | `boolean` | `false` | Double clicking a selected radio deselects it. |
333
357
 
334
358
  #### `<vi-radio>` Properties & Attributes
335
- | Attribute | Property | Type | Default | Description |
336
- | :--- | :--- | :--- | :--- | :--- |
337
- | `value` | `value` | `string` | `''` | Value this radio represents. |
338
- | `checked` | `checked` | `boolean` | `false` | Checked selection status. |
359
+
360
+ | Attribute | Property | Type | Default | Description |
361
+ | :--------- | :--------- | :-------- | :------ | :--------------------------- |
362
+ | `value` | `value` | `string` | `''` | Value this radio represents. |
363
+ | `checked` | `checked` | `boolean` | `false` | Checked selection status. |
339
364
  | `disabled` | `disabled` | `boolean` | `false` | Local disable flag override. |
340
365
 
341
366
  #### Slots (`<vi-radio-group>`)
367
+
342
368
  - **Default Slot**: Holds the list of `<vi-radio>` child tags.
343
369
  - **`label` Slot**: Legend label displayed above the list.
344
370
  - **`helper` Slot**: Support text shown below the components.
@@ -346,6 +372,7 @@ The [ViRadioGroup](./src/radio/vi-radio-group.ts) and [ViRadio](./src/radio/vi-r
346
372
  #### Snippets
347
373
 
348
374
  **Vertical Layout (Default):**
375
+
349
376
  ```html
350
377
  <vi-radio-group name="shipping" value="standard">
351
378
  <span slot="label">Choose Shipping Method</span>
@@ -357,13 +384,9 @@ The [ViRadioGroup](./src/radio/vi-radio-group.ts) and [ViRadio](./src/radio/vi-r
357
384
  ```
358
385
 
359
386
  **Horizontal Layout with Double-Click Clear:**
387
+
360
388
  ```html
361
- <vi-radio-group
362
- name="rating"
363
- orientation="horizontal"
364
- size="lg"
365
- allow-dblclick-clear
366
- >
389
+ <vi-radio-group name="rating" orientation="horizontal" size="lg" allow-dblclick-clear>
367
390
  <span slot="label">Score rating (Double-click to clear)</span>
368
391
  <vi-radio value="1">1 Star</vi-radio>
369
392
  <vi-radio value="2">2 Stars</vi-radio>
@@ -376,31 +399,36 @@ The [ViRadioGroup](./src/radio/vi-radio-group.ts) and [ViRadio](./src/radio/vi-r
376
399
  ---
377
400
 
378
401
  ### Tooltip ([vi-tooltip](./src/tooltip/vi-tooltip.ts))
402
+
379
403
  The [ViTooltip](./src/tooltip/vi-tooltip.ts) manages floating help text. It leverages `@floating-ui/dom` under the hood for collision-detection, auto-flipping, dynamic viewport alignments, and manual trigger controls.
380
404
 
381
405
  #### Properties & Attributes
382
- | Attribute | Property | Type | Default | Description |
383
- | :--- | :--- | :--- | :--- | :--- |
384
- | `content` | `content` | `string` | `''` | Plain text tooltip label. Overridden if the `content` slot is populated. |
385
- | `placement` | `placement` | `TooltipPlacement` | `'top'` | Direction: `top`\|`top-start`\|`top-end`\|`bottom`\|`bottom-start`\|`bottom-end`\|`left`\|`right`. |
386
- | `trigger` | `trigger` | `TooltipTrigger` | `'hover focus'` | Trigger events: `hover focus`\|`hover`\|`focus`\|`click`. |
387
- | `delay` | `delay` | `number` | `500` | Wait delay before displaying in ms. |
388
- | `hide-delay` | `hide-delay` | `number` | `100` | Hide delay after trigger lost in ms. |
389
- | `max-width` | `max-width` | `number` | `240` | Max width bounds size in pixels. |
390
- | `disabled` | `disabled` | `boolean` | `false` | Prevents rendering/open operations. |
391
- | `popper-options` | `popperOptions` | `object` | `{}` | Custom options passed directly to Floating UI's `computePosition`. |
406
+
407
+ | Attribute | Property | Type | Default | Description |
408
+ | :--------------- | :-------------- | :----------------- | :-------------- | :------------------------------------------------------------------------------------------------- |
409
+ | `content` | `content` | `string` | `''` | Plain text tooltip label. Overridden if the `content` slot is populated. |
410
+ | `placement` | `placement` | `TooltipPlacement` | `'top'` | Direction: `top`\|`top-start`\|`top-end`\|`bottom`\|`bottom-start`\|`bottom-end`\|`left`\|`right`. |
411
+ | `trigger` | `trigger` | `TooltipTrigger` | `'hover focus'` | Trigger events: `hover focus`\|`hover`\|`focus`\|`click`. |
412
+ | `delay` | `delay` | `number` | `500` | Wait delay before displaying in ms. |
413
+ | `hide-delay` | `hide-delay` | `number` | `100` | Hide delay after trigger lost in ms. |
414
+ | `max-width` | `max-width` | `number` | `240` | Max width bounds size in pixels. |
415
+ | `disabled` | `disabled` | `boolean` | `false` | Prevents rendering/open operations. |
416
+ | `popper-options` | `popperOptions` | `object` | `{}` | Custom options passed directly to Floating UI's `computePosition`. |
392
417
 
393
418
  #### Slots
419
+
394
420
  - **Default Slot**: The target anchor element (e.g. `<vi-button>`).
395
421
  - **`content` Slot**: Holds rich interactive HTML content. (When used, automatically updates ARIA parameters from `aria-describedby` to `aria-details` for screen readers).
396
422
 
397
423
  #### Methods
424
+
398
425
  - `show()`: Force displays the tooltip pane.
399
426
  - `hide(immediate = false)`: Force hides the tooltip pane.
400
427
 
401
428
  #### Snippets
402
429
 
403
430
  **Basic Text Tooltip:**
431
+
404
432
  ```html
405
433
  <vi-tooltip content="Press to permanently remove configuration" placement="right">
406
434
  <vi-button variant="danger">Delete Account</vi-button>
@@ -408,6 +436,7 @@ The [ViTooltip](./src/tooltip/vi-tooltip.ts) manages floating help text. It leve
408
436
  ```
409
437
 
410
438
  **Rich Interactive Content (Aria-Details compliant):**
439
+
411
440
  ```html
412
441
  <vi-tooltip placement="bottom-start" trigger="click">
413
442
  <vi-button>View Pricing Plan</vi-button>
@@ -434,24 +463,17 @@ If you want the tooltip to dynamically choose the absolute best side (e.g., auto
434
463
 
435
464
  ```html
436
465
  <!-- Passing custom autoPlacement middleware via popper-options property -->
437
- <vi-tooltip
438
- id="auto-placement-tooltip"
439
- content="Dynamic placement based on available viewport space"
440
- >
466
+ <vi-tooltip id="auto-placement-tooltip" content="Dynamic placement based on available viewport space">
441
467
  <vi-button>Auto placement</vi-button>
442
468
  </vi-tooltip>
443
469
 
444
470
  <script>
445
471
  import { autoPlacement, offset, shift } from '@floating-ui/dom';
446
-
472
+
447
473
  const tooltip = document.getElementById('auto-placement-tooltip');
448
474
  // Configure custom options directly to override default middleware list
449
475
  tooltip.popperOptions = {
450
- middleware: [
451
- offset(12),
452
- autoPlacement({ padding: 8 }),
453
- shift({ padding: 8 })
454
- ]
476
+ middleware: [offset(12), autoPlacement({ padding: 8 }), shift({ padding: 8 })],
455
477
  };
456
478
  </script>
457
479
  ```
@@ -459,16 +481,19 @@ If you want the tooltip to dynamically choose the absolute best side (e.g., auto
459
481
  ---
460
482
 
461
483
  ### Icon ([vi-icon](./src/icons/vi-icon.ts))
484
+
462
485
  The [ViIcon](./src/icons/vi-icon.ts) component renders named inline SVG elements. It depends on a dynamic map store, registering only the icons utilized in your project to allow bundlers to prune unused assets (tree-shaking).
463
486
 
464
487
  #### Properties & Attributes
465
- | Attribute | Property | Type | Default | Description |
466
- | :--- | :--- | :--- | :--- | :--- |
467
- | `name` | `name` | `string` | `''` | Name identifier inside the registry. |
468
- | `size` | `size` | `number` | `24` | Width/height footprint dimension in pixels. |
469
- | `label` | `label` | `string` | `''` | Accessibility label. When set, renders as an interactive image with `role="img"`. When empty, marks element as `aria-hidden="true"`. |
488
+
489
+ | Attribute | Property | Type | Default | Description |
490
+ | :-------- | :------- | :------- | :------ | :----------------------------------------------------------------------------------------------------------------------------------- |
491
+ | `name` | `name` | `string` | `''` | Name identifier inside the registry. |
492
+ | `size` | `size` | `number` | `24` | Width/height footprint dimension in pixels. |
493
+ | `label` | `label` | `string` | `''` | Accessibility label. When set, renders as an interactive image with `role="img"`. When empty, marks element as `aria-hidden="true"`. |
470
494
 
471
495
  #### Icon Registration API
496
+
472
497
  Before rendering `<vi-icon>`, you must register the required icon definitions using the [registerIcons](./src/icons/registry.ts) function:
473
498
 
474
499
  ```ts
@@ -480,11 +505,12 @@ import { settingsIcon } from '@vialiq/icons/settings';
480
505
  registerIcons([checkIcon, settingsIcon]);
481
506
  ```
482
507
 
483
- *Note: For security, `registerIcons` includes internal sanity checks that filter out inline `<script>` tags or event handlers (e.g. `onload=`) to prevent XSS exploits.*
508
+ _Note: For security, `registerIcons` includes internal sanity checks that filter out inline `<script>` tags or event handlers (e.g. `onload=`) to prevent XSS exploits._
484
509
 
485
510
  #### Snippets
486
511
 
487
512
  **Usage Example:**
513
+
488
514
  ```html
489
515
  <!-- Decorative usage (aria-hidden is true) -->
490
516
  <vi-icon name="check" size="20"></vi-icon>
@@ -493,6 +519,89 @@ registerIcons([checkIcon, settingsIcon]);
493
519
  <vi-icon name="settings" size="32" label="Open workspace settings"></vi-icon>
494
520
  ```
495
521
 
522
+ ### Modal ([vi-modal](./src/modal/vi-modal.ts))
523
+
524
+ The [ViModal](./src/modal/vi-modal.ts) is a focus-trapping dialog that dynamically teleports itself to `document.body` to guarantee stacking context above all other elements. It supports multiple layout variants (default, drawer, alert), draggable boundaries, resize handles, and stacking multiple modals automatically using an internal OverlayManager.
525
+
526
+ #### Properties & Attributes
527
+
528
+ | Attribute | Property | Type | Default | Description |
529
+ | :----------------- | :---------------- | :--------------------------------------------------------- | :---------- | :------------------------------------------------------------------------ |
530
+ | `open` | `open` | `boolean` | `false` | Controls the visibility of the modal. |
531
+ | `variant` | `variant` | `'default'\|'drawer'\|'alert'` | `'default'` | Core layout mode. |
532
+ | `size` | `size` | `'xs'\|'sm'\|'md'\|'lg'\|'xl'\|'full-width'\|'fullscreen'` | `'md'` | Dialog dimensions. |
533
+ | `position` | `position` | `'center'\|'top'\|'bottom'\|'left'\|'right'\|...` | `'center'` | Screen positioning for default variant. |
534
+ | `persistent` | `persistent` | `boolean` | `false` | Prevent closing on backdrop click or Escape key (triggers shake instead). |
535
+ | `no-backdrop` | `no-backdrop` | `boolean` | `false` | Hides the backdrop overlay and allows background interaction (modeless). |
536
+ | `draggable` | `draggable` | `boolean` | `false` | Allows dragging the modal by its header. |
537
+ | `resizable` | `resizable` | `boolean` | `false` | Adds 8-point resize handles to the modal edges. |
538
+ | `scrollable` | `scrollable` | `boolean` | `true` | Body content scrolls while header/footer stay fixed. |
539
+ | `drawer-placement` | `drawerPlacement` | `'left'\|'right'` | `'right'` | Edge attachment for the drawer variant. |
540
+ | `append-to` | `appendTo` | `string\|HTMLElement` | `'body'` | Selector or Element to teleport the modal into. |
541
+
542
+ #### Slots
543
+
544
+ - **Default Slot**: The main body content.
545
+ - **`header` Slot**: Overrides the title text.
546
+ - **`header-actions` Slot**: Insert custom buttons next to close/maximize.
547
+ - **`footer` Slot**: Bottom action bar content.
548
+ - **`icon` Slot**: (Alert variant only) Override the default status icon.
549
+
550
+ #### Events
551
+
552
+ - `vi-modal-before-open`: Fires immediately on `open=true` (cancelable).
553
+ - `vi-modal-open`: Fires as the modal begins opening.
554
+ - `vi-modal-after-open`: Fires after enter animations complete.
555
+ - `vi-modal-before-close`: Fires immediately on `open=false` (cancelable). Detail: `{ reason: string }`
556
+ - `vi-modal-close`: Fires as the modal begins closing. Detail: `{ reason: string }`
557
+ - `vi-modal-after-close`: Fires after exit animations complete and modal is removed.
558
+ - `vi-modal-request-close`: Fires when user attempts to close via backdrop/escape. Detail: `{ reason: 'escape' \| 'backdrop' }`
559
+
560
+ #### Snippets
561
+
562
+ **Standard Modal:**
563
+
564
+ ```html
565
+ <vi-modal>
566
+ <vi-modal-header slot="header" closable>Title</vi-modal-header>
567
+ <p>Content</p>
568
+ <vi-modal-footer slot="footer">
569
+ <vi-button>Save</vi-button>
570
+ </vi-modal-footer>
571
+ </vi-modal>
572
+ ```
573
+
574
+ **Draggable & Resizable Modeless Dialog:**
575
+
576
+ ```html
577
+ <vi-modal no-backdrop draggable resizable position="bottom-right">
578
+ <vi-modal-header slot="header" closable>Floating Tool</vi-modal-header>
579
+ <p>This modal doesn't block interaction with the page behind it!</p>
580
+ </vi-modal>
581
+ ```
582
+
583
+ **Right-Side Drawer:**
584
+
585
+ ```html
586
+ <vi-modal variant="drawer" drawer-placement="right" size="sm">
587
+ <vi-modal-header slot="header" closable>Settings</vi-modal-header>
588
+ <p>Drawer contents here...</p>
589
+ </vi-modal>
590
+ ```
591
+
592
+ **Destructive Alert:**
593
+
594
+ ```html
595
+ <vi-modal variant="alert" persistent>
596
+ <vi-modal-header slot="header" alert-variant="danger" closable>Delete Workspace?</vi-modal-header>
597
+ <p>This action cannot be undone. All data will be permanently lost.</p>
598
+ <vi-modal-footer slot="footer">
599
+ <vi-button variant="ghost">Cancel</vi-button>
600
+ <vi-button variant="danger">Confirm Deletion</vi-button>
601
+ </vi-modal-footer>
602
+ </vi-modal>
603
+ ```
604
+
496
605
  ---
497
606
 
498
607
  ## Form Validation & ElementInternals
@@ -500,20 +609,14 @@ registerIcons([checkIcon, settingsIcon]);
500
609
  Custom controls inside this library utilize the native browser [ValidityMixin](./src/base/validity-mixin.ts) to integrate with standard `<form>` features like `.elements`, `.checkValidity()`, and `.reportValidity()`.
501
610
 
502
611
  ### Handling Submit Validation
612
+
503
613
  ```html
504
614
  <form id="profile-form">
505
- <vi-input
506
- type="text"
507
- name="fullname"
508
- placeholder="John Doe"
509
- required
510
- >
615
+ <vi-input type="text" name="fullname" placeholder="John Doe" required>
511
616
  <span slot="helper">Enter your full name.</span>
512
617
  </vi-input>
513
618
 
514
- <vi-checkbox name="newsletter" required>
515
- Confirm subscription policy
516
- </vi-checkbox>
619
+ <vi-checkbox name="newsletter" required> Confirm subscription policy </vi-checkbox>
517
620
 
518
621
  <vi-button type="submit" variant="primary">Submit</vi-button>
519
622
  </form>
@@ -522,7 +625,7 @@ Custom controls inside this library utilize the native browser [ValidityMixin](.
522
625
  const form = document.getElementById('profile-form');
523
626
  form.addEventListener('submit', (e) => {
524
627
  e.preventDefault();
525
-
628
+
526
629
  // Checks validity for all elements in the form
527
630
  if (form.checkValidity()) {
528
631
  const formData = new FormData(form);
@@ -535,6 +638,7 @@ Custom controls inside this library utilize the native browser [ValidityMixin](.
535
638
  ```
536
639
 
537
640
  ### Custom Error Reporting
641
+
538
642
  Use `setCustomValidity` to configure custom error messages or run manual server-side validation responses:
539
643
 
540
644
  ```javascript
@@ -542,7 +646,7 @@ const emailField = document.querySelector('vi-input[name="email"]');
542
646
 
543
647
  emailField.addEventListener('vialiq-change', (e) => {
544
648
  const email = e.detail.value;
545
-
649
+
546
650
  if (email.endsWith('@forbidden-domain.com')) {
547
651
  emailField.setCustomValidity('Registrations from this domain are forbidden.');
548
652
  emailField.reportValidity(); // Displays the custom validation message tooltip
@@ -559,6 +663,7 @@ emailField.addEventListener('vialiq-change', (e) => {
559
663
  Web Components utilize CSS Shadow Roots to encapsulate logic and layout styles. To override designs safely without bleeding global configurations, use **CSS Shadow Parts** or **CSS Variables**.
560
664
 
561
665
  ### CSS Shadow Parts (`::part`)
666
+
562
667
  Elements expose internal sub-nodes through the `part="..."` syntax. Customize these nodes using the CSS `::part()` selector:
563
668
 
564
669
  ```css
@@ -580,6 +685,7 @@ vi-checkbox::part(box) {
580
685
  ```
581
686
 
582
687
  ### CSS Variables Custom Properties
688
+
583
689
  For variables used repeatedly, custom elements offer direct CSS property bindings. Customize them globally or on single layouts:
584
690
 
585
691
  ```css
@@ -617,6 +723,174 @@ npx nx run web-components:storybook
617
723
  npx nx run web-components:test-wdio
618
724
  ```
619
725
 
726
+ ---
727
+
728
+ ### Badge ([vi-badge](./src/badge/vi-badge.ts))
729
+
730
+ The `<vi-badge>` is a compact inline indicator used to communicate status, category, or count. It supports various color semantics, sizes, pill vs. square shapes, dot-only mode, and numeric count capping.
731
+
732
+ #### Properties & Attributes
733
+
734
+ | Attribute | Property | Type | Default | Description |
735
+ | :---------- | :--------- | :------------------------------------------------------------- | :---------- | :---------------------------------------------------------------------- |
736
+ | `variant` | `variant` | `'neutral'\|'primary'\|'success'\|'warning'\|'danger'\|'info'` | `'neutral'` | Color semantic. |
737
+ | `size` | `size` | `'sm'\|'md'\|'lg'` | `'md'` | Size of the badge. |
738
+ | `dot` | `dot` | `boolean` | `false` | Renders a small colored dot instead of text. |
739
+ | `pill` | `pill` | `boolean` | `true` | Renders fully rounded ends (pill) vs slightly rounded corners (square). |
740
+ | `count` | `count` | `number` | `undefined` | Numeric count to display. Overrides default slot content. |
741
+ | `show-zero` | `showZero` | `boolean` | `false` | Displays the badge even if the count is zero. |
742
+ | `max` | `max` | `number` | `99` | Max count before showing `{max}+`. |
743
+ | `outline` | `outline` | `boolean` | `false` | Renders the badge with an outline/ghost style. |
744
+
745
+ #### Slots
746
+
747
+ - **Default Slot**: Badge text content (ignored if `count` is set).
748
+ - **`icon` Slot**: Optional leading icon.
749
+
750
+ #### Snippets
751
+
752
+ **Standard Variants:**
753
+
754
+ ```html
755
+ <vi-badge variant="neutral">Draft</vi-badge>
756
+ <vi-badge variant="primary">Submitted</vi-badge>
757
+ <vi-badge variant="success">Locked</vi-badge>
758
+ <vi-badge variant="warning">In Review</vi-badge>
759
+ <vi-badge variant="danger">Query Open</vi-badge>
760
+ ```
761
+
762
+ **Outlined & Square Styles:**
763
+
764
+ ```html
765
+ <vi-badge variant="info" outline>Outline Mode</vi-badge> <vi-badge variant="primary" :pill="false">Square Badge</vi-badge>
766
+ ```
767
+
768
+ **Numeric Counts:**
769
+
770
+ ```html
771
+ <vi-badge count="5" variant="danger"></vi-badge> <vi-badge count="120" max="99" variant="danger"></vi-badge>
772
+ ```
773
+
774
+ **Dot Indicators:**
775
+
776
+ ```html
777
+ <vi-badge dot variant="success"></vi-badge>
778
+ ```
779
+
780
+ ---
781
+
782
+ ### Chip & Chip Group ([vi-chip](./src/chip/vi-chip.ts), [vi-chip-group](./src/chip/vi-chip-group.ts))
783
+
784
+ The `<vi-chip>` is an interactive pill-shaped element used for selection, filtering, or categorisation. Chips are usually managed inside a `<vi-chip-group>`, which provides multi-select or single-select logic and arrow-key navigation.
785
+
786
+ #### `<vi-chip>` Properties & Attributes
787
+
788
+ | Attribute | Property | Type | Default | Description |
789
+ | :------------------ | :---------------- | :------------------------------------------------------------- | :---------- | :--------------------------------------------------- |
790
+ | `value` | `value` | `string` | `''` | Value used for selection tracking within a group. |
791
+ | `selected` | `selected` | `boolean` | `false` | Sets the active/selected visual state and checkmark. |
792
+ | `disabled` | `disabled` | `boolean` | `false` | Makes the chip non-interactive. |
793
+ | `removable` | `removable` | `boolean` | `false` | Shows a trailing "×" remove button. |
794
+ | `variant` | `variant` | `'neutral'\|'primary'\|'success'\|'warning'\|'danger'\|'info'` | `'neutral'` | Color semantic. |
795
+ | `size` | `size` | `'sm'\|'md'\|'lg'` | `'md'` | Size of the chip. |
796
+ | `remove-aria-label` | `removeAriaLabel` | `string` | `'Remove'` | Accessible label for the remove button. |
797
+
798
+ **`<vi-chip>` Slots:**
799
+
800
+ - **Default Slot**: Label text content.
801
+ - **`avatar` Slot**: Leading avatar image or initials.
802
+ - **`icon` Slot**: Leading icon (used when no avatar).
803
+ - **`trailing-icon` Slot**: Trailing icon (separate from the remove button).
804
+
805
+ #### `<vi-chip-group>` Properties & Attributes
806
+
807
+ | Attribute | Property | Type | Default | Description |
808
+ | :--------- | :--------- | :--------- | :------ | :----------------------------------------------------------- |
809
+ | `value` | `value` | `string[]` | `[]` | Array of currently selected chip values. |
810
+ | `multi` | `multi` | `boolean` | `true` | Allows multiple selections vs. single selection. |
811
+ | `name` | `name` | `string` | `''` | Form field name for native form participation. |
812
+ | `required` | `required` | `boolean` | `false` | Requires at least one chip to be selected for form validity. |
813
+ | `disabled` | `disabled` | `boolean` | `false` | Disables all child chips. |
814
+ | `wrap` | `wrap` | `boolean` | `true` | Controls whether chips wrap to the next line. |
815
+ | `gap` | `gap` | `string` | `'8px'` | Gap spacing between chips. |
816
+
817
+ **`<vi-chip-group>` Events:**
818
+
819
+ - `vi-chip-group-change`: Fired when the selection changes (event detail contains `value: string[]`).
820
+
821
+ #### Snippets
822
+
823
+ **Multi-Select Group:**
824
+
825
+ ```html
826
+ <vi-chip-group multi name="grades" value='["grade-1"]'>
827
+ <vi-chip value="grade-1">Grade 1</vi-chip>
828
+ <vi-chip value="grade-2">Grade 2</vi-chip>
829
+ <vi-chip value="grade-3" variant="warning">Grade 3</vi-chip>
830
+ <vi-chip value="grade-4" variant="danger">Grade 4</vi-chip>
831
+ </vi-chip-group>
832
+ ```
833
+
834
+ **Single-Select Group:**
835
+
836
+ ```html
837
+ <vi-chip-group :multi="false" name="visit">
838
+ <vi-chip value="1" variant="primary">Visit 1</vi-chip>
839
+ <vi-chip value="2" variant="primary">Visit 2</vi-chip>
840
+ </vi-chip-group>
841
+ ```
842
+
843
+ ---
844
+
845
+ ---
846
+
847
+ ---
848
+
849
+ ### Switch ([vi-switch](./src/switch/vi-switch.ts))
850
+
851
+ The `<vi-switch>` component is a form-associated toggle switch used for boolean settings. It supports size variants, custom label placements, and native HTML form participation.
852
+
853
+ #### Properties & Attributes
854
+
855
+ | Attribute | Property | Type | Default | Description |
856
+ | :---------------- | :--------------- | :----------------- | :------ | :---------------------------------------------------- |
857
+ | `checked` | `checked` | `boolean` | `false` | Checked state of the switch. |
858
+ | `disabled` | `disabled` | `boolean` | `false` | Disables the switch. |
859
+ | `size` | `size` | `'sm'\|'md'\|'lg'` | `'md'` | Visual size scale. |
860
+ | `label-placement` | `labelPlacement` | `'start'\|'end'` | `'end'` | Placement of the label relative to the switch toggle. |
861
+ | `name` | `name` | `string` | `''` | Form field name. |
862
+ | `value` | `value` | `string` | `'on'` | Form submission value when checked. |
863
+
864
+ **Slots:**
865
+
866
+ - **Default Slot**: Label text/content.
867
+ - **`on-label` Slot**: Optional text displayed inside the track when checked.
868
+ - **`off-label` Slot**: Optional text displayed inside the track when unchecked.
869
+
870
+ **Events:**
871
+
872
+ - `vi-switch-change`: Fires when the user toggles the checked state. (Detail: `{ checked: boolean }`).
873
+
874
+ #### Snippets
875
+
876
+ **Basic Usage:**
877
+
878
+ ```html
879
+ <vi-switch>Enable Notifications</vi-switch>
880
+ <vi-switch checked>Marketing Emails</vi-switch>
881
+ <vi-switch disabled>Disabled Toggle</vi-switch>
882
+ ```
883
+
884
+ **Custom Label Placement & Sizes:**
885
+
886
+ ```html
887
+ <!-- Label to the left of the switch -->
888
+ <vi-switch label-placement="start" size="lg">Auto-save</vi-switch>
889
+ <vi-switch size="sm">Compact Mode</vi-switch>
890
+ ```
891
+
892
+ ---
893
+
620
894
  ## Storybook
621
895
 
622
896
  The project uses **Storybook 10** with the `@storybook/web-components-vite` framework.
@@ -1 +1 @@
1
- {"version":3,"file":"draggable-mixin.d.ts","sourceRoot":"","sources":["../../../../libs/web-components/src/base/draggable-mixin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAuB,MAAM,KAAK,CAAC;AAItD,KAAK,WAAW,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE7D,MAAM,CAAC,OAAO,OAAO,kBAAkB;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,eAAe,CAAC;IACjC,SAAS,KAAK,WAAW,IAAI,WAAW,GAAG,IAAI,CAAC;IAChD,SAAS,KAAK,WAAW,IAAI,WAAW,GAAG,IAAI,CAAC;IAChD,SAAS,CAAC,UAAU,IAAI,IAAI;IAC5B,SAAS,CAAC,SAAS,IAAI,IAAI;CAC5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,CAAC,UAAU,CAAC,EAC9D,IAAI,EAAE,CAAC,GACN,CAAC,GAAG,WAAW,CAAC,kBAAkB,CAAC,CA0SrC"}
1
+ {"version":3,"file":"draggable-mixin.d.ts","sourceRoot":"","sources":["../../../../libs/web-components/src/base/draggable-mixin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAuB,MAAM,KAAK,CAAC;AAItD,KAAK,WAAW,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE7D,MAAM,CAAC,OAAO,OAAO,kBAAkB;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,eAAe,CAAC;IACjC,SAAS,KAAK,WAAW,IAAI,WAAW,GAAG,IAAI,CAAC;IAChD,SAAS,KAAK,WAAW,IAAI,WAAW,GAAG,IAAI,CAAC;IAChD,SAAS,CAAC,UAAU,IAAI,IAAI;IAC5B,SAAS,CAAC,SAAS,IAAI,IAAI;CAC5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,CAAC,UAAU,CAAC,EAC9D,IAAI,EAAE,CAAC,GACN,CAAC,GAAG,WAAW,CAAC,kBAAkB,CAAC,CAgTrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"focus-trap-mixin.d.ts","sourceRoot":"","sources":["../../../../libs/web-components/src/base/focus-trap-mixin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAKjC,KAAK,WAAW,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAEzD;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,kBAAkB;IACrC;;;;;;;;OAQG;IACH,SAAS,CAAC,kBAAkB,CAC1B,YAAY,CAAC,EAAE,WAAW,GAAG,IAAI,EACjC,SAAS,CAAC,EAAE,OAAO,GAClB,IAAI;IAEP;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,CAAC,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI;CACvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6FG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,CAAC,UAAU,CAAC,EAC9D,IAAI,EAAE,CAAC,GACN,CAAC,GAAG,WAAW,CAAC,kBAAkB,CAAC,CAqPrC"}
1
+ {"version":3,"file":"focus-trap-mixin.d.ts","sourceRoot":"","sources":["../../../../libs/web-components/src/base/focus-trap-mixin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAKjC,KAAK,WAAW,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAUzD;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,kBAAkB;IACrC;;;;;;;;OAQG;IACH,SAAS,CAAC,kBAAkB,CAC1B,YAAY,CAAC,EAAE,WAAW,GAAG,IAAI,EACjC,SAAS,CAAC,EAAE,OAAO,GAClB,IAAI;IAEP;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,CAAC,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI;CACvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6FG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,CAAC,UAAU,CAAC,EAC9D,IAAI,EAAE,CAAC,GACN,CAAC,GAAG,WAAW,CAAC,kBAAkB,CAAC,CAkRrC"}
@@ -15,6 +15,8 @@ declare class OverlayManagerService {
15
15
  private getBaseZIndex;
16
16
  private overlays;
17
17
  private _previousOverflow;
18
+ private _previousPaddingRight;
19
+ private _inertedElements;
18
20
  /**
19
21
  * Registers an element as an active overlay.
20
22
  * Calculates and returns the appropriate z-index for this overlay.
@@ -24,7 +26,9 @@ declare class OverlayManagerService {
24
26
  * @param scrollStrategy How this overlay interacts with background scrolling.
25
27
  * @returns The calculated z-index to be applied to the element.
26
28
  */
27
- register(element: HTMLElement, type?: OverlayType, scrollStrategy?: ScrollStrategy): number;
29
+ register(element: HTMLElement, type?: OverlayType, scrollStrategy?: ScrollStrategy, options?: {
30
+ noBackdrop?: boolean;
31
+ }): number;
28
32
  /**
29
33
  * Unregisters an element, removing it from the overlay stack.
30
34
  * Should be called when the overlay is closed or disconnected from the DOM.
@@ -47,6 +51,11 @@ declare class OverlayManagerService {
47
51
  * @returns True if the element has the highest z-index in the registry.
48
52
  */
49
53
  isTopOverlay(element: HTMLElement): boolean;
54
+ /**
55
+ * Syncs the `inert` attribute on `document.body` children based on the active overlay stack.
56
+ * Modals with a backdrop trap focus globally, so everything beneath them must be `inert`.
57
+ */
58
+ private _syncInertState;
50
59
  /**
51
60
  * Locks or unlocks the document.body scroll based on the active overlays.
52
61
  * Modals (and other overlays with scrollStrategy='block') require the body
@@ -1 +1 @@
1
- {"version":3,"file":"overlay-manager.d.ts","sourceRoot":"","sources":["../../../../libs/web-components/src/base/overlay-manager.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC;AACrE,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,MAAM,CAAC;AAS9C;;;;;;;;;;GAUG;AACH,cAAM,qBAAqB;IACzB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,iBAAiB,CAAuB;IAEhD;;;;;;;;OAQG;IACI,QAAQ,CACb,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE,WAAwB,EAC9B,cAAc,CAAC,EAAE,cAAc,GAC9B,MAAM;IAkBT;;;;;OAKG;IACI,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAK7C;;;;;OAKG;IACI,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI;IAKrD;;;;;;OAMG;IACI,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO;IAQlD;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;CAsB1B;AAGD,eAAO,MAAM,cAAc,uBAA8B,CAAC"}
1
+ {"version":3,"file":"overlay-manager.d.ts","sourceRoot":"","sources":["../../../../libs/web-components/src/base/overlay-manager.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC;AACrE,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,MAAM,CAAC;AAU9C;;;;;;;;;;GAUG;AACH,cAAM,qBAAqB;IACzB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,qBAAqB,CAAuB;IACpD,OAAO,CAAC,gBAAgB,CAAqB;IAE7C;;;;;;;;OAQG;IACI,QAAQ,CACb,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE,WAAwB,EAC9B,cAAc,CAAC,EAAE,cAAc,EAC/B,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GACjC,MAAM;IAyBT;;;;;OAKG;IACI,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAM7C;;;;;OAKG;IACI,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI;IAKrD;;;;;;OAMG;IACI,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO;IAQlD;;;OAGG;IACH,OAAO,CAAC,eAAe;IA4CvB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;CAyC1B;AAGD,eAAO,MAAM,cAAc,uBAA8B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"resizable-mixin.d.ts","sourceRoot":"","sources":["../../../../libs/web-components/src/base/resizable-mixin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA6B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAIjF,KAAK,WAAW,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAEzD,MAAM,CAAC,OAAO,OAAO,kBAAkB;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,KAAK,aAAa,IAAI,WAAW,GAAG,IAAI,CAAC;IAClD,SAAS,CAAC,oBAAoB,IAAI,cAAc;IAChD,SAAS,CAAC,YAAY,IAAI,IAAI;CAC/B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,CAAC,UAAU,CAAC,EAC9D,IAAI,EAAE,CAAC,GACN,CAAC,GAAG,WAAW,CAAC,kBAAkB,CAAC,CAgNrC"}
1
+ {"version":3,"file":"resizable-mixin.d.ts","sourceRoot":"","sources":["../../../../libs/web-components/src/base/resizable-mixin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAGV,KAAK,cAAc,EACpB,MAAM,KAAK,CAAC;AAIb,KAAK,WAAW,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAEzD,MAAM,CAAC,OAAO,OAAO,kBAAkB;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,KAAK,aAAa,IAAI,WAAW,GAAG,IAAI,CAAC;IAClD,SAAS,CAAC,oBAAoB,IAAI,cAAc;IAChD,SAAS,CAAC,YAAY,IAAI,IAAI;CAC/B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,CAAC,UAAU,CAAC,EAC9D,IAAI,EAAE,CAAC,GACN,CAAC,GAAG,WAAW,CAAC,kBAAkB,CAAC,CA2NrC"}
package/index.d.ts CHANGED
@@ -25,6 +25,10 @@ export { ViBadge } from './badge/vi-badge.js';
25
25
  export type { BadgeVariant, BadgeSize } from './badge/vi-badge.js';
26
26
  export { ViAlert } from './alert/vi-alert.js';
27
27
  export type { AlertVariant } from './alert/vi-alert.js';
28
+ export { ViModal } from './modal/vi-modal.js';
29
+ export type { ModalVariant, DrawerPlacement, ModalSize } from './modal/vi-modal.js';
30
+ export { ViModalHeader } from './modal/vi-modal-header.js';
31
+ export { ViModalFooter } from './modal/vi-modal-footer.js';
28
32
  export { ViChip } from './chip/vi-chip.js';
29
33
  export type { ChipVariant, ChipSize } from './chip/vi-chip.js';
30
34
  export { ViChipGroup } from './chip/vi-chip-group.js';
package/index.js CHANGED
@@ -12,6 +12,9 @@ export { ViAccordion } from './accordion/vi-accordion.js';
12
12
  export { ViAccordionItem } from './accordion/vi-accordion-item.js';
13
13
  export { ViBadge } from './badge/vi-badge.js';
14
14
  export { ViAlert } from './alert/vi-alert.js';
15
+ export { ViModal } from './modal/vi-modal.js';
16
+ export { ViModalHeader } from './modal/vi-modal-header.js';
17
+ export { ViModalFooter } from './modal/vi-modal-footer.js';
15
18
  export { ViChip } from './chip/vi-chip.js';
16
19
  export { ViChipGroup } from './chip/vi-chip-group.js';
17
20
  export { ViAnimation } from './animation/vi-animation.js';
@@ -22,6 +22,8 @@
22
22
  }
23
23
  overlays = [];
24
24
  _previousOverflow = null;
25
+ _previousPaddingRight = null;
26
+ _inertedElements = [];
25
27
  /**
26
28
  * Registers an element as an active overlay.
27
29
  * Calculates and returns the appropriate z-index for this overlay.
@@ -30,7 +32,7 @@
30
32
  * @param type The type of overlay, used to determine behaviors like scroll-locking.
31
33
  * @param scrollStrategy How this overlay interacts with background scrolling.
32
34
  * @returns The calculated z-index to be applied to the element.
33
- */ register(element, type = 'dropdown', scrollStrategy) {
35
+ */ register(element, type = 'dropdown', scrollStrategy, options) {
34
36
  this.unregister(element); // Ensure no duplicates
35
37
  let highestZIndex = this.getBaseZIndex(element);
36
38
  if (this.overlays.length > 0) {
@@ -43,9 +45,11 @@
43
45
  element,
44
46
  type,
45
47
  zIndex: newZIndex,
46
- scrollStrategy: finalStrategy
48
+ scrollStrategy: finalStrategy,
49
+ noBackdrop: options?.noBackdrop
47
50
  });
48
51
  this._updateBodyScroll();
52
+ this._syncInertState();
49
53
  return newZIndex;
50
54
  }
51
55
  /**
@@ -56,6 +60,7 @@
56
60
  */ unregister(element) {
57
61
  this.overlays = this.overlays.filter((o)=>o.element !== element);
58
62
  this._updateBodyScroll();
63
+ this._syncInertState();
59
64
  }
60
65
  /**
61
66
  * Gets the assigned z-index for an element if it is currently registered.
@@ -78,6 +83,46 @@
78
83
  return topOverlay.element === element;
79
84
  }
80
85
  /**
86
+ * Syncs the `inert` attribute on `document.body` children based on the active overlay stack.
87
+ * Modals with a backdrop trap focus globally, so everything beneath them must be `inert`.
88
+ */ _syncInertState() {
89
+ if (typeof document === 'undefined') return;
90
+ // Find the topmost modal that requires a backdrop
91
+ const blockingOverlays = this.overlays.filter((o)=>o.type === 'modal' && !o.noBackdrop);
92
+ const topBlocking = blockingOverlays.length > 0 ? blockingOverlays[blockingOverlays.length - 1] : null;
93
+ if (!topBlocking) {
94
+ // If no blocking overlays, clear all inert state
95
+ this._inertedElements.forEach((el)=>{
96
+ el.inert = false;
97
+ });
98
+ this._inertedElements = [];
99
+ return;
100
+ }
101
+ // Determine which overlays should NOT be inert (the top blocking one and any above it)
102
+ const activeOverlayElements = new Set();
103
+ const topBlockingIndex = this.overlays.indexOf(topBlocking);
104
+ for(let i = topBlockingIndex; i < this.overlays.length; i++){
105
+ activeOverlayElements.add(this.overlays[i].element);
106
+ }
107
+ // Mark children of body
108
+ Array.from(document.body.children).forEach((child)=>{
109
+ const el = child;
110
+ // Never make the active overlays inert
111
+ if (activeOverlayElements.has(el)) {
112
+ if (this._inertedElements.includes(el)) {
113
+ el.inert = false;
114
+ this._inertedElements = this._inertedElements.filter((e)=>e !== el);
115
+ }
116
+ return;
117
+ }
118
+ // If it's not an active overlay, and not already inert, make it inert
119
+ if (!el.inert) {
120
+ el.inert = true;
121
+ this._inertedElements.push(el);
122
+ }
123
+ });
124
+ }
125
+ /**
81
126
  * Locks or unlocks the document.body scroll based on the active overlays.
82
127
  * Modals (and other overlays with scrollStrategy='block') require the body
83
128
  * to be unscrollable. It applies a utility class `vi-scroll-locked` to the body.
@@ -86,9 +131,17 @@
86
131
  if (hasBlock) {
87
132
  // Prevent double-setting if already locked
88
133
  if (!document.body.classList.contains('vi-scroll-locked')) {
134
+ // Calculate scrollbar width before removing overflow
135
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
89
136
  document.body.classList.add('vi-scroll-locked');
90
137
  this._previousOverflow = document.body.style.getPropertyValue('overflow') || null;
138
+ this._previousPaddingRight = document.body.style.getPropertyValue('padding-right') || null;
91
139
  document.body.style.setProperty('overflow', 'hidden', 'important');
140
+ // Apply compensation padding to prevent layout shift
141
+ if (scrollbarWidth > 0) {
142
+ const currentPadding = parseFloat(window.getComputedStyle(document.body).paddingRight || '0');
143
+ document.body.style.setProperty('padding-right', `${currentPadding + scrollbarWidth}px`, 'important');
144
+ }
92
145
  }
93
146
  } else {
94
147
  if (document.body.classList.contains('vi-scroll-locked')) {
@@ -98,7 +151,13 @@
98
151
  } else {
99
152
  document.body.style.removeProperty('overflow');
100
153
  }
154
+ if (this._previousPaddingRight !== null) {
155
+ document.body.style.setProperty('padding-right', this._previousPaddingRight);
156
+ } else {
157
+ document.body.style.removeProperty('padding-right');
158
+ }
101
159
  this._previousOverflow = null;
160
+ this._previousPaddingRight = null;
102
161
  }
103
162
  }
104
163
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vialiq/web-components",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -103,7 +103,7 @@
103
103
  },
104
104
  "peerDependencies": {
105
105
  "@floating-ui/dom": ">=1.0.0",
106
- "@vialiq/flux-ui": ">=0.0.4 <0.1.0",
106
+ "@vialiq/flux-ui": "^0.15.0",
107
107
  "@vialiq/icons": ">=0.0.3 <0.1.0",
108
108
  "lit": "^3.0.0"
109
109
  },
@@ -2,7 +2,7 @@ import { unsafeCSS, css, nothing, html } from 'lit';
2
2
  import { customElement, property, state, query } from 'lit/decorators.js';
3
3
  import { V as ViElement } from '../vi-element-C6GfDPs3.js';
4
4
  import { autoUpdate, offset, flip, shift, arrow, computePosition } from '@floating-ui/dom';
5
- import { O as OverlayManager } from '../overlay-manager-xco6S92C.js';
5
+ import { O as OverlayManager } from '../overlay-manager-Ch_6fr_D.js';
6
6
 
7
7
  const tooltipStyles = "@charset \"UTF-8\";@layer reset,components,utilities;.tooltip-panel{position:fixed;z-index:var(--vi-tooltip-z-index, var(--vi-tooltip-z-index, 1070));pointer-events:none;opacity:0;transform:scale(.95);transition:opacity .15s ease-out,transform .15s ease-out;margin:0;border:none;background:transparent;padding:0;overflow:visible;font-family:var(--vi-font-family-base, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif)}.tooltip-panel[popover]:popover-open{display:block;pointer-events:auto;opacity:1;transform:scale(1)}.tooltip-panel{transition-property:opacity,transform,display,overlay;transition-behavior:allow-discrete}@media(prefers-reduced-motion:reduce){.tooltip-panel{transition:none;transform:none}}.tooltip-content{background-color:var(--vi-tooltip-background, var(--vi-layer-inverse, #111827));color:var(--vi-tooltip-color, var(--vi-text-primary-inverse, #ffffff));font-size:var(--vi-tooltip-font-size, 12px);line-height:1.4;padding:var(--vi-tooltip-padding, 6px 10px);border-radius:var(--vi-tooltip-border-radius, 4px);box-shadow:var(--vi-tooltip-shadow, var(--vi-shadow-md, 0 4px 6px -1px rgba(0, 0, 0, .1)));max-width:var(--vi-tooltip-max-width, 240px);word-wrap:break-word;white-space:normal;position:relative}.tooltip-arrow{position:absolute;width:0;height:0;border-style:solid;border-color:transparent;pointer-events:none}.tooltip-panel[placement^=top] .tooltip-arrow,.tooltip-panel[data-placement^=top] .tooltip-arrow{bottom:calc(-1 * var(--vi-tooltip-arrow-size, 6px));left:50%;transform:translate(-50%);border-width:var(--vi-tooltip-arrow-size, 6px) var(--vi-tooltip-arrow-size, 6px) 0;border-top-color:var(--vi-tooltip-background, var(--vi-layer-inverse, #111827))}.tooltip-panel[placement^=bottom] .tooltip-arrow,.tooltip-panel[data-placement^=bottom] .tooltip-arrow{top:calc(-1 * var(--vi-tooltip-arrow-size, 6px));left:50%;transform:translate(-50%);border-width:0 var(--vi-tooltip-arrow-size, 6px) var(--vi-tooltip-arrow-size, 6px);border-bottom-color:var(--vi-tooltip-background, var(--vi-layer-inverse, #111827))}.tooltip-panel[placement=left] .tooltip-arrow,.tooltip-panel[data-placement=left] .tooltip-arrow{right:calc(-1 * var(--vi-tooltip-arrow-size, 6px));top:50%;transform:translateY(-50%);border-width:var(--vi-tooltip-arrow-size, 6px) 0 var(--vi-tooltip-arrow-size, 6px) var(--vi-tooltip-arrow-size, 6px);border-left-color:var(--vi-tooltip-background, var(--vi-layer-inverse, #111827))}.tooltip-panel[placement=right] .tooltip-arrow,.tooltip-panel[data-placement=right] .tooltip-arrow{left:calc(-1 * var(--vi-tooltip-arrow-size, 6px));top:50%;transform:translateY(-50%);border-width:var(--vi-tooltip-arrow-size, 6px) var(--vi-tooltip-arrow-size, 6px) var(--vi-tooltip-arrow-size, 6px) 0;border-right-color:var(--vi-tooltip-background, var(--vi-layer-inverse, #111827))}.tooltip-panel[placement$=-start] .tooltip-arrow,.tooltip-panel[data-placement$=-start] .tooltip-arrow{left:12px;transform:none}.tooltip-panel[placement$=-end] .tooltip-arrow,.tooltip-panel[data-placement$=-end] .tooltip-arrow{left:auto;right:12px;transform:none}:host{display:inline-block}";
8
8