vira 31.18.1 → 31.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.
Files changed (28) hide show
  1. package/dist/elements/pop-up/vira-menu-trigger.element.d.ts +2 -2
  2. package/dist/elements/pop-up/vira-menu-trigger.element.js +6 -3
  3. package/dist/elements/pop-up/vira-pop-up-trigger.element.d.ts +2 -2
  4. package/dist/elements/pop-up/vira-pop-up-trigger.element.js +4 -4
  5. package/dist/elements/vira-collapsible-card.element.d.ts +2 -2
  6. package/dist/elements/vira-collapsible-card.element.js +8 -3
  7. package/dist/elements/vira-collapsible-wrapper.element.d.ts +2 -2
  8. package/dist/elements/vira-collapsible-wrapper.element.js +2 -2
  9. package/dist/elements/vira-drawer.element.d.ts +2 -2
  10. package/dist/elements/vira-drawer.element.js +4 -2
  11. package/dist/elements/vira-dropdown.element.d.ts +6 -0
  12. package/dist/elements/vira-dropdown.element.js +38 -4
  13. package/dist/elements/vira-image.element.d.ts +2 -2
  14. package/dist/elements/vira-image.element.js +4 -4
  15. package/dist/elements/vira-json-form.element.js +192 -141
  16. package/dist/elements/vira-modal.element.d.ts +2 -2
  17. package/dist/elements/vira-modal.element.js +6 -2
  18. package/dist/elements/vira-tabs.element.js +9 -2
  19. package/dist/elements/vira-text-area.element.js +1 -0
  20. package/dist/icons/icon-svgs/16/plus-16.icon.d.ts +8 -0
  21. package/dist/icons/icon-svgs/{plus-24.icon.js → 16/plus-16.icon.js} +7 -7
  22. package/dist/icons/index.d.ts +3 -0
  23. package/dist/icons/index.js +4 -0
  24. package/dist/util/define-vira-element.js +1 -1
  25. package/dist/util/overflow-observer.d.ts +8 -1
  26. package/dist/util/overflow-observer.js +10 -2
  27. package/package.json +8 -8
  28. package/dist/icons/icon-svgs/plus-24.icon.d.ts +0 -8
@@ -17,7 +17,7 @@ export declare const viraMenuTriggerTestIds: {
17
17
  * @category PopUp
18
18
  * @category Elements
19
19
  */
20
- export declare const ViraMenuTrigger: import("element-vir").DeclarativeElementDefinition<"vira-menu-trigger", PartialWithUndefined<{
20
+ export declare const ViraMenuTrigger: import("element-vir").DeclarativeElementDefinition<`vira-${string}`, PartialWithUndefined<{
21
21
  isDisabled: boolean;
22
22
  z_debug_forceOpenState: boolean;
23
23
  popUpOffset: PopUpOffset;
@@ -42,4 +42,4 @@ export declare const ViraMenuTrigger: import("element-vir").DeclarativeElementDe
42
42
  showPopUpResult: ShowPopUpResult | undefined;
43
43
  }, {
44
44
  openChange: import("element-vir").DefineEvent<ShowPopUpResult | undefined>;
45
- }, "vira-menu-trigger-", "vira-menu-trigger-", readonly ["trigger"], readonly []>;
45
+ }, `vira-${string}-`, `vira-${string}-`, readonly ["vira-menu-trigger-trigger"], readonly []>;
@@ -19,7 +19,7 @@ export const viraMenuTriggerTestIds = {
19
19
  export const ViraMenuTrigger = defineViraElement()({
20
20
  tagName: 'vira-menu-trigger',
21
21
  slotNames: [
22
- 'trigger',
22
+ 'vira-menu-trigger-trigger',
23
23
  ],
24
24
  styles: css `
25
25
  :host {
@@ -71,7 +71,10 @@ export const ViraMenuTrigger = defineViraElement()({
71
71
  });
72
72
  })}
73
73
  >
74
- <slot name=${slotNames.trigger} slot=${ViraPopUpTrigger.slotNames.trigger}></slot>
74
+ <slot
75
+ name=${slotNames['vira-menu-trigger-trigger']}
76
+ slot=${ViraPopUpTrigger.slotNames['vira-pop-up-trigger-trigger']}
77
+ ></slot>
75
78
  ${state.navController && state.showPopUpResult
76
79
  ? html `
77
80
  <${ViraMenu.assign({
@@ -80,7 +83,7 @@ export const ViraMenuTrigger = defineViraElement()({
80
83
  : ViraMenuPopUpDirection.Upwards,
81
84
  cornerStyle: inputs.menuCornerStyle,
82
85
  })}
83
- slot=${ViraPopUpTrigger.slotNames.popUp}
86
+ slot=${ViraPopUpTrigger.slotNames['vira-pop-up-trigger-pop-up']}
84
87
  class=${classMap({
85
88
  'full-width-menu': inputs.horizontalAnchor === HorizontalAnchor.Both,
86
89
  })}
@@ -79,7 +79,7 @@ export type PopUpTriggerPosition = {
79
79
  * @category Elements
80
80
  * @see https://electrovir.github.io/vira/book/elements/vira-pop-up-trigger
81
81
  */
82
- export declare const ViraPopUpTrigger: import("element-vir").DeclarativeElementDefinition<"vira-pop-up-trigger", PartialWithUndefined<PopUpTriggerPosition & {
82
+ export declare const ViraPopUpTrigger: import("element-vir").DeclarativeElementDefinition<`vira-${string}`, PartialWithUndefined<PopUpTriggerPosition & {
83
83
  isDisabled: boolean;
84
84
  /** For debugging purposes only. Very bad for actual production code use. */
85
85
  z_debug_forceOpenState: boolean;
@@ -106,4 +106,4 @@ export declare const ViraPopUpTrigger: import("element-vir").DeclarativeElementD
106
106
  navController: NavController;
107
107
  popUpManager: PopUpManager;
108
108
  }>;
109
- }, "vira-pop-up-trigger-disabled" | "vira-pop-up-trigger-inside-focus" | "vira-pop-up-trigger-outside-focus", "vira-pop-up-trigger-", readonly ["trigger", "popUp"], readonly []>;
109
+ }, "vira-pop-up-trigger-disabled" | "vira-pop-up-trigger-inside-focus" | "vira-pop-up-trigger-outside-focus", `vira-${string}-`, readonly ["vira-pop-up-trigger-trigger", "vira-pop-up-trigger-pop-up"], readonly []>;
@@ -53,8 +53,8 @@ export const ViraPopUpTrigger = defineViraElement()({
53
53
  };
54
54
  },
55
55
  slotNames: [
56
- 'trigger',
57
- 'popUp',
56
+ 'vira-pop-up-trigger-trigger',
57
+ 'vira-pop-up-trigger-pop-up',
58
58
  ],
59
59
  hostClasses: {
60
60
  'vira-pop-up-trigger-disabled': ({ inputs }) => !!inputs.isDisabled,
@@ -362,7 +362,7 @@ export const ViraPopUpTrigger = defineViraElement()({
362
362
  })}
363
363
  >
364
364
  <div class="dropdown-trigger">
365
- <slot name=${slotNames.trigger}></slot>
365
+ <slot name=${slotNames['vira-pop-up-trigger-trigger']}></slot>
366
366
  </div>
367
367
 
368
368
  <div
@@ -372,7 +372,7 @@ export const ViraPopUpTrigger = defineViraElement()({
372
372
  style=${positionerStyles}
373
373
  >
374
374
  ${renderIf(!!state.showPopUpResult, html `
375
- <slot name=${slotNames.popUp}></slot>
375
+ <slot name=${slotNames['vira-pop-up-trigger-pop-up']}></slot>
376
376
  `)}
377
377
  </div>
378
378
  </button>
@@ -5,7 +5,7 @@ import { type PartialWithUndefined } from '@augment-vir/common';
5
5
  * @category Elements
6
6
  * @see https://electrovir.github.io/vira/book/elements/vira-checkbox
7
7
  */
8
- export declare const ViraCollapsibleCard: import("element-vir").DeclarativeElementDefinition<"vira-collapsible-card", PartialWithUndefined<{
8
+ export declare const ViraCollapsibleCard: import("element-vir").DeclarativeElementDefinition<`vira-${string}`, PartialWithUndefined<{
9
9
  /**
10
10
  * When set to `true`, the card styles are diminished so you can still use this element in
11
11
  * more flexible ways.
@@ -33,4 +33,4 @@ export declare const ViraCollapsibleCard: import("element-vir").DeclarativeEleme
33
33
  isExpanded: boolean;
34
34
  }, {
35
35
  expandToggle: import("element-vir").DefineEvent<boolean>;
36
- }, "vira-collapsible-card-expanded" | "vira-collapsible-card-expansion-blocked" | "vira-collapsible-card-card-styles", "vira-collapsible-card-content-gap", readonly ["header"], readonly ["openCaret"]>;
36
+ }, "vira-collapsible-card-expanded" | "vira-collapsible-card-expansion-blocked" | "vira-collapsible-card-card-styles", "vira-collapsible-card-content-gap", readonly ["vira-collapsible-card-header"], readonly ["openCaret"]>;
@@ -110,7 +110,7 @@ export const ViraCollapsibleCard = defineViraElement()({
110
110
  }
111
111
  `,
112
112
  slotNames: [
113
- 'header',
113
+ 'vira-collapsible-card-header',
114
114
  ],
115
115
  render({ inputs, slotNames, state, updateState, testIds, dispatch, events }) {
116
116
  if (inputs.blockExpansion) {
@@ -127,7 +127,9 @@ export const ViraCollapsibleCard = defineViraElement()({
127
127
  ? nothing
128
128
  : html `
129
129
  <div class="card-header">
130
- <slot name=${slotNames.header}><div class="header-filler"></div></slot>
130
+ <slot name=${slotNames['vira-collapsible-card-header']}>
131
+ <div class="header-filler"></div>
132
+ </slot>
131
133
 
132
134
  ${inputs.blockExpansion
133
135
  ? nothing
@@ -158,7 +160,10 @@ export const ViraCollapsibleCard = defineViraElement()({
158
160
  dispatch(new events.expandToggle(event.detail));
159
161
  })}
160
162
  >
161
- <div class="header-wrapper" slot=${ViraCollapsibleWrapper.slotNames.header}>
163
+ <div
164
+ class="header-wrapper"
165
+ slot=${ViraCollapsibleWrapper.slotNames['vira-collapsible-wrapper-header']}
166
+ >
162
167
  ${wrapperHeaderTemplate}
163
168
  </div>
164
169
  ${wrapperContentTemplate}
@@ -5,7 +5,7 @@
5
5
  * @category Elements
6
6
  * @see https://electrovir.github.io/vira/book/elements/vira-collapsible-wrapper
7
7
  */
8
- export declare const ViraCollapsibleWrapper: import("element-vir").DeclarativeElementDefinition<"vira-collapsible-wrapper", {
8
+ export declare const ViraCollapsibleWrapper: import("element-vir").DeclarativeElementDefinition<`vira-${string}`, {
9
9
  expanded: boolean;
10
10
  /** When true, forces the content to expand when printing regardless of collapsed state. */
11
11
  expandOnPrint?: boolean;
@@ -13,4 +13,4 @@ export declare const ViraCollapsibleWrapper: import("element-vir").DeclarativeEl
13
13
  contentHeight: number;
14
14
  }, {
15
15
  expandChange: import("element-vir").DefineEvent<boolean>;
16
- }, "vira-collapsible-wrapper-expand-on-print", "vira-collapsible-wrapper-", readonly ["header"], readonly []>;
16
+ }, "vira-collapsible-wrapper-expand-on-print", `vira-${string}-`, readonly ["vira-collapsible-wrapper-header"], readonly []>;
@@ -18,7 +18,7 @@ export const ViraCollapsibleWrapper = defineViraElement()({
18
18
  hostClasses: {
19
19
  'vira-collapsible-wrapper-expand-on-print': ({ inputs }) => !!inputs.expandOnPrint,
20
20
  },
21
- slotNames: ['header'],
21
+ slotNames: ['vira-collapsible-wrapper-header'],
22
22
  styles: ({ hostClasses }) => css `
23
23
  :host {
24
24
  display: flex;
@@ -75,7 +75,7 @@ export const ViraCollapsibleWrapper = defineViraElement()({
75
75
  dispatch(new events.expandChange(!inputs.expanded));
76
76
  })}
77
77
  >
78
- <slot name=${slotNames.header}>Header</slot>
78
+ <slot name=${slotNames['vira-collapsible-wrapper-header']}>Header</slot>
79
79
  </button>
80
80
 
81
81
  <div
@@ -4,7 +4,7 @@ import { type PartialWithUndefined } from '@augment-vir/common';
4
4
  *
5
5
  * @category Elements
6
6
  */
7
- export declare const ViraDrawer: import("element-vir").DeclarativeElementDefinition<"vira-drawer", {
7
+ export declare const ViraDrawer: import("element-vir").DeclarativeElementDefinition<`vira-${string}`, {
8
8
  open: boolean;
9
9
  } & PartialWithUndefined<{
10
10
  /** If this isn't set, make sure to use the drawer title slot to fill it in. */
@@ -21,4 +21,4 @@ export declare const ViraDrawer: import("element-vir").DeclarativeElementDefinit
21
21
  dragCurrentY: number;
22
22
  }, {
23
23
  drawerClose: import("element-vir").DefineEvent<void>;
24
- }, "vira-drawer-dragging" | "vira-drawer-no-content-padding", "vira-drawer-backdrop-filter" | "vira-drawer-max-height", readonly ["drawerTitle"], readonly []>;
24
+ }, "vira-drawer-dragging" | "vira-drawer-no-content-padding", "vira-drawer-backdrop-filter" | "vira-drawer-max-height", readonly ["vira-drawer-drawer-title"], readonly []>;
@@ -48,7 +48,7 @@ export const ViraDrawer = defineViraElement()({
48
48
  'vira-drawer-dragging': ({ state }) => state.isDragging,
49
49
  'vira-drawer-no-content-padding': ({ inputs }) => !!inputs.noContentPadding,
50
50
  },
51
- slotNames: ['drawerTitle'],
51
+ slotNames: ['vira-drawer-drawer-title'],
52
52
  cssVars: {
53
53
  'vira-drawer-backdrop-filter': 'blur(3px)',
54
54
  'vira-drawer-max-height': '80dvh',
@@ -275,7 +275,9 @@ export const ViraDrawer = defineViraElement()({
275
275
  <div class="header">
276
276
  <div class="header-text-wrapper">
277
277
  <h1>
278
- <slot name=${slotNames.drawerTitle}>${inputs.drawerTitle}</slot>
278
+ <slot name=${slotNames['vira-drawer-drawer-title']}>
279
+ ${inputs.drawerTitle}
280
+ </slot>
279
281
  </h1>
280
282
  </div>
281
283
  <button
@@ -25,11 +25,17 @@ export declare const ViraDropdown: import("element-vir").DeclarativeElementDefin
25
25
  icon: ViraIconSvg;
26
26
  selectionPrefix: string;
27
27
  isDisabled: boolean;
28
+ label: string;
28
29
  /** For debugging purposes only. Very bad for actual production code use. */
29
30
  z_debug_forceOpenState: boolean;
30
31
  } & PopUpTriggerPosition>, {
31
32
  /** `undefined` means the pop up is not currently showing. */
32
33
  showPopUpResult: ShowPopUpResult | undefined;
34
+ /**
35
+ * Used to couple the label and trigger together. This is not applied if no label is
36
+ * provided.
37
+ */
38
+ randomId: string;
33
39
  }, {
34
40
  selectedChange: import("element-vir").DefineEvent<string[]>;
35
41
  openChange: import("element-vir").DefineEvent<ShowPopUpResult | undefined>;
@@ -1,5 +1,5 @@
1
1
  import { check } from '@augment-vir/assert';
2
- import { filterMap } from '@augment-vir/common';
2
+ import { filterMap, randomString } from '@augment-vir/common';
3
3
  import { classMap, css, defineElementEvent, html, ifDefined, listen, nothing, testId, } from 'element-vir';
4
4
  import { ChevronUp16Icon } from '../icons/index.js';
5
5
  import { viraFormCssVars } from '../styles/form-styles.js';
@@ -92,6 +92,22 @@ export const ViraDropdown = defineViraElement()({
92
92
  .using-placeholder {
93
93
  opacity: 0.4;
94
94
  }
95
+
96
+ label {
97
+ display: flex;
98
+ flex-direction: column;
99
+ justify-content: flex-start;
100
+ gap: 2px;
101
+ width: 100%;
102
+ max-width: 100%;
103
+
104
+ & .dropdown-label {
105
+ font-weight: ${viraFormCssVars['vira-form-label-font-weight'].value};
106
+ text-align: left;
107
+ flex-shrink: 0;
108
+ flex-wrap: wrap;
109
+ }
110
+ }
95
111
  `,
96
112
  events: {
97
113
  selectedChange: defineElementEvent(),
@@ -101,6 +117,11 @@ export const ViraDropdown = defineViraElement()({
101
117
  return {
102
118
  /** `undefined` means the pop up is not currently showing. */
103
119
  showPopUpResult: undefined,
120
+ /**
121
+ * Used to couple the label and trigger together. This is not applied if no label is
122
+ * provided.
123
+ */
124
+ randomId: randomString(32),
104
125
  };
105
126
  },
106
127
  render({ state, inputs, dispatch, events, updateState, testIds }) {
@@ -133,7 +154,7 @@ export const ViraDropdown = defineViraElement()({
133
154
  ? ViraMenuPopUpDirection.Downwards
134
155
  : ViraMenuPopUpDirection.Upwards,
135
156
  })}
136
- slot=${ViraPopUpTrigger.slotNames.popUp}
157
+ slot=${ViraPopUpTrigger.slotNames['vira-pop-up-trigger-pop-up']}
137
158
  >
138
159
  ${renderMenuItemEntries(inputs.options.map((option) => {
139
160
  return {
@@ -147,7 +168,7 @@ export const ViraDropdown = defineViraElement()({
147
168
  }))}
148
169
  </${ViraMenu}>
149
170
  `;
150
- return html `
171
+ const triggerTemplate = html `
151
172
  <${ViraPopUpTrigger.assign({
152
173
  ...inputs,
153
174
  focusOnClose: true,
@@ -171,7 +192,9 @@ export const ViraDropdown = defineViraElement()({
171
192
  open: !!state.showPopUpResult,
172
193
  'open-upwards': !state.showPopUpResult?.popDown,
173
194
  })}"
174
- slot=${ViraPopUpTrigger.slotNames.trigger}
195
+ slot=${ViraPopUpTrigger.slotNames['vira-pop-up-trigger-trigger']}
196
+ id=${ifDefined(inputs.label ? state.randomId : undefined)}
197
+ aria-label=${ifDefined(inputs.label || undefined)}
175
198
  ${testId(testIds.trigger)}
176
199
  >
177
200
  ${leadingIconTemplate}
@@ -195,5 +218,16 @@ export const ViraDropdown = defineViraElement()({
195
218
  ${state.showPopUpResult ? menuTemplate : nothing}
196
219
  </${ViraPopUpTrigger}>
197
220
  `;
221
+ if (inputs.label) {
222
+ return html `
223
+ <label for=${state.randomId}>
224
+ <span class="dropdown-label">${inputs.label}</span>
225
+ ${triggerTemplate}
226
+ </label>
227
+ `;
228
+ }
229
+ else {
230
+ return triggerTemplate;
231
+ }
198
232
  },
199
233
  });
@@ -11,7 +11,7 @@ import { type Duration, type DurationUnit } from 'date-vir';
11
11
  * @category Elements
12
12
  * @see https://electrovir.github.io/vira/book/elements/vira-image
13
13
  */
14
- export declare const ViraImage: import("element-vir").DeclarativeElementDefinition<"vira-image", {
14
+ export declare const ViraImage: import("element-vir").DeclarativeElementDefinition<`vira-${string}`, {
15
15
  /** The URL of the image to load. This is passed directly to the `<img>` element. */
16
16
  imageUrl: string;
17
17
  /**
@@ -42,4 +42,4 @@ export declare const ViraImage: import("element-vir").DeclarativeElementDefiniti
42
42
  }, {
43
43
  imageLoad: import("element-vir").DefineEvent<void>;
44
44
  imageError: import("element-vir").DefineEvent<unknown>;
45
- }, "vira-image-height-constrained", "vira-image-", readonly ["loading", "error"], readonly []>;
45
+ }, "vira-image-height-constrained", `vira-${string}-`, readonly ["vira-image-loading", "vira-image-error"], readonly []>;
@@ -34,8 +34,8 @@ export const ViraImage = defineViraElement()({
34
34
  'vira-image-height-constrained': ({ inputs }) => inputs.dominantDimension === 'height',
35
35
  },
36
36
  slotNames: [
37
- 'loading',
38
- 'error',
37
+ 'vira-image-loading',
38
+ 'vira-image-error',
39
39
  ],
40
40
  events: {
41
41
  imageLoad: defineElementEvent(),
@@ -96,7 +96,7 @@ export const ViraImage = defineViraElement()({
96
96
  const imageUrl = inputs.imageUrl;
97
97
  const statusTemplate = state.erroredUrls[imageUrl]
98
98
  ? html `
99
- <slot class="status-wrapper" name=${slotNames.error}>
99
+ <slot class="status-wrapper" name=${slotNames['vira-image-error']}>
100
100
  <${ViraIcon.assign({
101
101
  icon: StatusFailure24Icon,
102
102
  })}
@@ -107,7 +107,7 @@ export const ViraImage = defineViraElement()({
107
107
  : state.loadedUrls[imageUrl]
108
108
  ? undefined
109
109
  : html `
110
- <slot class="status-wrapper" name=${slotNames.loading}>
110
+ <slot class="status-wrapper" name=${slotNames['vira-image-loading']}>
111
111
  <${ViraIcon.assign({
112
112
  icon: LoaderAnimated24Icon,
113
113
  })}></${ViraIcon}>
@@ -1,8 +1,7 @@
1
1
  import { assertWrap, check } from '@augment-vir/assert';
2
2
  import { omitObjectKeys, wrapInTry } from '@augment-vir/common';
3
- import { extractEventTarget } from '@augment-vir/web';
4
3
  import { css, defineElementEvent, html, listen, nothing } from 'element-vir';
5
- import { lucideIcons, X16Icon } from '../icons/index.js';
4
+ import { Plus16Icon, X16Icon } from '../icons/index.js';
6
5
  import { viraFontCssVars } from '../styles/font.js';
7
6
  import { viraFormCssVars } from '../styles/form-styles.js';
8
7
  import { ViraColorVariant, ViraEmphasis, ViraSize } from '../styles/form-variants.js';
@@ -13,6 +12,7 @@ import { ViraCheckbox } from './vira-checkbox.element.js';
13
12
  import { ViraError } from './vira-error.element.js';
14
13
  import { ViraInput, ViraInputType } from './vira-input.element.js';
15
14
  import { ViraSelect } from './vira-select.element.js';
15
+ import { ViraTextArea } from './vira-text-area.element.js';
16
16
  /**
17
17
  * An editor for arbitrary JSON values, optionally constrained by a standard JSON Schema
18
18
  * ({@link ViraJsonSchema}).
@@ -48,24 +48,10 @@ export const ViraJsonForm = defineViraElement()({
48
48
  justify-content: flex-end;
49
49
  }
50
50
 
51
- .json-raw-textarea {
52
- margin: 0;
53
- padding: 10px 12px;
54
- border: 1px solid ${viraFormCssVars['vira-form-border-color'].value};
55
- border-radius: ${viraFormCssVars['vira-form-wrapper-radius'].value};
56
- background-color: ${viraFormCssVars['vira-form-background-color'].value};
57
- color: ${viraFormCssVars['vira-form-foreground-color'].value};
51
+ ${ViraTextArea}.json-raw-textarea {
52
+ width: 100%;
58
53
  font-family: ${viraFontCssVars['vira-monospace'].value};
59
54
  font-size: ${viraFormCssVars['vira-form-small-text-size'].value};
60
- box-sizing: border-box;
61
- width: 100%;
62
- min-height: 240px;
63
- resize: vertical;
64
- }
65
-
66
- .json-raw-textarea:focus {
67
- outline: none;
68
- border-color: ${viraFormCssVars['vira-form-focus-outline-color'].value};
69
55
  }
70
56
 
71
57
  .json-validation-errors {
@@ -80,68 +66,71 @@ export const ViraJsonForm = defineViraElement()({
80
66
  }
81
67
 
82
68
  .json-group {
83
- display: flex;
84
- flex-direction: column;
85
- gap: 8px;
86
- padding: 10px 12px;
87
- border: 1px solid ${viraFormCssVars['vira-form-border-color'].value};
88
- border-radius: ${viraFormCssVars['vira-form-wrapper-radius'].value};
89
- background-color: ${viraFormCssVars['vira-form-background-color'].value};
69
+ border-collapse: separate;
70
+ border-spacing: 0;
71
+ width: 100%;
90
72
  box-sizing: border-box;
91
73
  }
92
74
 
93
- .json-group-header {
94
- display: flex;
95
- align-items: center;
96
- gap: 8px;
75
+ .json-group-nested {
76
+ margin-top: 4px;
77
+ padding-left: 12px;
78
+ padding-bottom: 8px;
79
+ border-left: 2px solid ${viraFormCssVars['vira-form-border-color'].value};
80
+ border-bottom: 2px solid ${viraFormCssVars['vira-form-border-color'].value};
97
81
  }
98
82
 
99
- .json-group-header-title {
100
- flex-grow: 1;
101
- font-weight: ${viraFormCssVars['vira-form-label-font-weight'].value};
102
- color: ${viraFormCssVars['vira-form-secondary-body-foreground'].value};
103
- font-size: ${viraFormCssVars['vira-form-small-text-size'].value};
83
+ .json-row-primitive > td {
84
+ padding: 4px 8px 4px 0;
85
+ vertical-align: middle;
104
86
  }
105
87
 
106
- .json-row {
107
- display: flex;
108
- gap: 8px;
109
- }
110
-
111
- .json-row-primitive {
112
- align-items: center;
113
- }
114
-
115
- .json-row-nested {
116
- flex-direction: column;
117
- align-items: stretch;
118
- gap: 4px;
88
+ .json-row-primitive > td:last-child {
89
+ padding-right: 0;
119
90
  }
120
91
 
121
- .json-row-label {
122
- flex-shrink: 0;
92
+ td.json-row-label {
93
+ text-align: left;
123
94
  min-width: 80px;
95
+ white-space: nowrap;
124
96
  font-weight: ${viraFormCssVars['vira-form-label-font-weight'].value};
125
- word-break: break-word;
126
97
  }
127
98
 
128
- .json-row-editor {
129
- flex-grow: 1;
130
- min-width: 0;
131
- display: flex;
132
- flex-direction: column;
99
+ td.json-row-editor {
100
+ width: 100%;
133
101
  }
134
102
 
135
- .json-row-editor > * {
103
+ td.json-row-editor > * {
136
104
  width: 100%;
137
105
  max-width: 100%;
106
+ box-sizing: border-box;
138
107
  }
139
108
 
140
- .json-row-delete {
141
- flex-shrink: 0;
109
+ td.json-row-delete {
142
110
  width: 24px;
111
+ text-align: center;
112
+ }
113
+
114
+ .json-row-nested > td {
115
+ padding: 4px 0;
116
+ }
117
+
118
+ .json-row-nested-header {
143
119
  display: flex;
144
- justify-content: center;
120
+ align-items: center;
121
+ gap: 8px;
122
+ margin-bottom: 4px;
123
+ }
124
+
125
+ .json-row-nested-header > .json-row-label {
126
+ flex-grow: 1;
127
+ font-weight: ${viraFormCssVars['vira-form-label-font-weight'].value};
128
+ word-break: break-word;
129
+ }
130
+
131
+ .json-type-tag {
132
+ font-weight: normal;
133
+ color: ${viraFormCssVars['vira-form-secondary-body-foreground'].value};
145
134
  }
146
135
 
147
136
  .json-value-with-switcher {
@@ -169,8 +158,10 @@ export const ViraJsonForm = defineViraElement()({
169
158
  .json-add-row {
170
159
  display: flex;
171
160
  align-items: center;
161
+ justify-content: flex-end;
172
162
  gap: 6px;
173
163
  flex-wrap: wrap;
164
+ padding: 8px 0;
174
165
  }
175
166
 
176
167
  .json-add-row ${ViraInput}, .json-add-row ${ViraSelect} {
@@ -190,6 +181,8 @@ export const ViraJsonForm = defineViraElement()({
190
181
  }
191
182
 
192
183
  .json-empty-note {
184
+ display: block;
185
+ padding: 4px 0;
193
186
  color: ${viraFormCssVars['vira-form-placeholder-color'].value};
194
187
  font-style: italic;
195
188
  font-size: ${viraFormCssVars['vira-form-small-text-size'].value};
@@ -392,8 +385,9 @@ export const ViraJsonForm = defineViraElement()({
392
385
  function renderPlusButton({ isAddDisabled, tooltip, onClick, }) {
393
386
  return html `
394
387
  <${ViraButton.assign({
395
- icon: lucideIcons.Plus,
396
- color: ViraColorVariant.Plain,
388
+ icon: Plus16Icon,
389
+ color: ViraColorVariant.Positive,
390
+ buttonSize: ViraSize.Small,
397
391
  isDisabled: isAddDisabled,
398
392
  })}
399
393
  title=${tooltip}
@@ -406,7 +400,7 @@ export const ViraJsonForm = defineViraElement()({
406
400
  ></${ViraButton}>
407
401
  `;
408
402
  }
409
- function renderObjectAddControl({ pathKey, allowedTypes, canAdd, onAdd, }) {
403
+ function renderObjectAddControl({ pathKey, allowedTypes, canAdd, disabledReason, onAdd, }) {
410
404
  if (allowedTypes.length === 0) {
411
405
  return nothing;
412
406
  }
@@ -415,7 +409,7 @@ export const ViraJsonForm = defineViraElement()({
415
409
  const onlyType = assertWrap.isDefined(allowedTypes[0]);
416
410
  return renderPlusButton({
417
411
  isAddDisabled,
418
- tooltip: `Add ${viraJsonTypeLabels[onlyType]}`,
412
+ tooltip: isAddDisabled ? disabledReason : `Add ${viraJsonTypeLabels[onlyType]}`,
419
413
  onClick: () => onAdd(onlyType),
420
414
  });
421
415
  }
@@ -437,7 +431,9 @@ export const ViraJsonForm = defineViraElement()({
437
431
  ></${ViraSelect}>
438
432
  ${renderPlusButton({
439
433
  isAddDisabled,
440
- tooltip: `Add ${viraJsonTypeLabels[selectedType]}`,
434
+ tooltip: isAddDisabled
435
+ ? disabledReason
436
+ : `Add ${viraJsonTypeLabels[selectedType]}`,
441
437
  onClick: () => onAdd(selectedType),
442
438
  })}
443
439
  `;
@@ -497,8 +493,14 @@ export const ViraJsonForm = defineViraElement()({
497
493
  })}
498
494
  `;
499
495
  }
500
- function renderObjectGroup(path, value, schema, onDelete) {
496
+ function getNestedTypeLabel(schema, value) {
497
+ const concreteType = getJsonType(value);
498
+ const narrowedSchema = pickBranchForType(schema, concreteType, resolveContext);
499
+ return (getSchemaTitle(narrowedSchema, resolveContext) || viraJsonTypeLabels[concreteType]);
500
+ }
501
+ function renderObjectGroup(path, value, schema) {
501
502
  const pathKey = pathToKey(path);
503
+ const isRoot = path.length === 0;
502
504
  const requiredKeys = new Set(getRequiredProperties(schema, resolveContext));
503
505
  const definedProperties = getDefinedProperties(schema, resolveContext);
504
506
  const definedKeys = new Set(Object.keys(definedProperties));
@@ -524,20 +526,34 @@ export const ViraJsonForm = defineViraElement()({
524
526
  }
525
527
  }
526
528
  : undefined;
529
+ if (isChildNested) {
530
+ return html `
531
+ <tr class="json-row-nested">
532
+ <td colspan="3">
533
+ <div class="json-row-nested-header">
534
+ <span class="json-row-label">
535
+ ${key}${isRequired ? '*' : ''}
536
+ <span class="json-type-tag">
537
+ : ${getNestedTypeLabel(childSchema, childValue)}
538
+ </span>
539
+ </span>
540
+ ${childOnDelete ? renderDeleteButton(childOnDelete) : nothing}
541
+ </div>
542
+ ${renderValue(childPath, childValue, childSchema)}
543
+ </td>
544
+ </tr>
545
+ `;
546
+ }
527
547
  return html `
528
- <div
529
- class="json-row ${isChildNested ? 'json-row-nested' : 'json-row-primitive'}"
530
- >
531
- <span class="json-row-label">${key}${isRequired ? '*' : ''}</span>
532
- <span class="json-row-editor">
533
- ${renderValue(childPath, childValue, childSchema, isChildNested ? childOnDelete : undefined)}
534
- </span>
535
- <span class="json-row-delete">
536
- ${!isChildNested && childOnDelete
537
- ? renderDeleteButton(childOnDelete)
538
- : nothing}
539
- </span>
540
- </div>
548
+ <tr class="json-row-primitive">
549
+ <td class="json-row-label">${key}${isRequired ? '*' : ''}</td>
550
+ <td class="json-row-editor">
551
+ ${renderValue(childPath, childValue, childSchema)}
552
+ </td>
553
+ <td class="json-row-delete">
554
+ ${childOnDelete ? renderDeleteButton(childOnDelete) : nothing}
555
+ </td>
556
+ </tr>
541
557
  `;
542
558
  });
543
559
  const missingDefinedKeys = [...definedKeys].filter((key) => !(key in value));
@@ -550,8 +566,9 @@ export const ViraJsonForm = defineViraElement()({
550
566
  return html `
551
567
  <${ViraButton.assign({
552
568
  text: `"${key}"`,
553
- icon: lucideIcons.Plus,
554
- color: ViraColorVariant.Plain,
569
+ icon: Plus16Icon,
570
+ color: ViraColorVariant.Positive,
571
+ buttonSize: ViraSize.Small,
555
572
  })}
556
573
  ${listen('click', () => {
557
574
  emitReplaceAt([
@@ -568,21 +585,27 @@ export const ViraJsonForm = defineViraElement()({
568
585
  const additionalAllowedTypes = additional.allowed
569
586
  ? getAllowedJsonTypes(additional.schema, resolveContext)
570
587
  : [];
588
+ const arbitraryAddDisabledReason = trimmedPendingKey
589
+ ? `Field "${trimmedPendingKey}" already exists.`
590
+ : 'Enter a field name to add.';
571
591
  const arbitraryAddRow = additional.allowed && !isDisabled
572
592
  ? html `
573
- <div class="json-add-row">
574
- <${ViraInput.assign({
593
+ <tr>
594
+ <td colspan="3">
595
+ <div class="json-add-row">
596
+ <${ViraInput.assign({
575
597
  value: pendingKey,
576
598
  placeholder: 'new field name',
577
599
  })}
578
- ${listen(ViraInput.events.valueChange, (event) => {
600
+ ${listen(ViraInput.events.valueChange, (event) => {
579
601
  setPendingKey(pathKey, event.detail);
580
602
  })}
581
- ></${ViraInput}>
582
- ${renderObjectAddControl({
603
+ ></${ViraInput}>
604
+ ${renderObjectAddControl({
583
605
  pathKey,
584
606
  allowedTypes: additionalAllowedTypes,
585
607
  canAdd: canAddArbitraryField,
608
+ disabledReason: arbitraryAddDisabledReason,
586
609
  onAdd: (type) => {
587
610
  if (!canAddArbitraryField) {
588
611
  return;
@@ -594,33 +617,41 @@ export const ViraJsonForm = defineViraElement()({
594
617
  clearPending(pathKey);
595
618
  },
596
619
  })}
597
- </div>
620
+ </div>
621
+ </td>
622
+ </tr>
598
623
  `
599
624
  : nothing;
600
- const title = getSchemaTitle(schema, resolveContext) || 'object';
601
625
  return html `
602
- <div class="json-group">
603
- <div class="json-group-header">
604
- <span class="json-group-header-title">${title}</span>
605
- ${onDelete ? renderDeleteButton(onDelete) : nothing}
606
- </div>
607
- ${rowTemplates.length === 0 && suggestedKeyButtons.length === 0
626
+ <table class="json-group ${isRoot ? '' : 'json-group-nested'}">
627
+ <tbody>
628
+ ${rowTemplates.length === 0 && suggestedKeyButtons.length === 0
608
629
  ? html `
609
- <span class="json-empty-note">(empty object)</span>
610
- `
630
+ <tr>
631
+ <td colspan="3">
632
+ <span class="json-empty-note">(empty object)</span>
633
+ </td>
634
+ </tr>
635
+ `
611
636
  : nothing}
612
- ${rowTemplates}
613
- ${suggestedKeyButtons.length > 0
637
+ ${rowTemplates}
638
+ ${suggestedKeyButtons.length > 0
614
639
  ? html `
615
- <div class="json-add-row">${suggestedKeyButtons}</div>
616
- `
640
+ <tr>
641
+ <td colspan="3">
642
+ <div class="json-add-row">${suggestedKeyButtons}</div>
643
+ </td>
644
+ </tr>
645
+ `
617
646
  : nothing}
618
- ${arbitraryAddRow}
619
- </div>
647
+ ${arbitraryAddRow}
648
+ </tbody>
649
+ </table>
620
650
  `;
621
651
  }
622
- function renderArrayGroup(path, value, schema, onDelete) {
652
+ function renderArrayGroup(path, value, schema) {
623
653
  const pathKey = pathToKey(path);
654
+ const isRoot = path.length === 0;
624
655
  const newItemSchema = getNewItemSchema(schema, value.length, resolveContext);
625
656
  const allowedItemTypes = getAllowedJsonTypes(newItemSchema, resolveContext);
626
657
  const rowTemplates = value.map((item, index) => {
@@ -636,28 +667,43 @@ export const ViraJsonForm = defineViraElement()({
636
667
  : () => {
637
668
  emitDeleteAt(childPath);
638
669
  };
670
+ if (isChildNested) {
671
+ return html `
672
+ <tr class="json-row-nested">
673
+ <td colspan="3">
674
+ <div class="json-row-nested-header">
675
+ <span class="json-row-label">
676
+ [${index}]
677
+ <span class="json-type-tag">
678
+ : ${getNestedTypeLabel(childSchema, item)}
679
+ </span>
680
+ </span>
681
+ ${childOnDelete ? renderDeleteButton(childOnDelete) : nothing}
682
+ </div>
683
+ ${renderValue(childPath, item, childSchema)}
684
+ </td>
685
+ </tr>
686
+ `;
687
+ }
639
688
  return html `
640
- <div
641
- class="json-row ${isChildNested ? 'json-row-nested' : 'json-row-primitive'}"
642
- >
643
- <span class="json-row-label">[${index}]</span>
644
- <span class="json-row-editor">
645
- ${renderValue(childPath, item, childSchema, isChildNested ? childOnDelete : undefined)}
646
- </span>
647
- <span class="json-row-delete">
648
- ${!isChildNested && childOnDelete
649
- ? renderDeleteButton(childOnDelete)
650
- : nothing}
651
- </span>
652
- </div>
689
+ <tr class="json-row-primitive">
690
+ <td class="json-row-label">[${index}]</td>
691
+ <td class="json-row-editor">
692
+ ${renderValue(childPath, item, childSchema)}
693
+ </td>
694
+ <td class="json-row-delete">
695
+ ${childOnDelete ? renderDeleteButton(childOnDelete) : nothing}
696
+ </td>
697
+ </tr>
653
698
  `;
654
699
  });
655
- const title = getSchemaTitle(schema, resolveContext) || 'array';
656
700
  const addRow = isDisabled
657
701
  ? nothing
658
702
  : html `
659
- <div class="json-add-row">
660
- ${renderArrayAddControl({
703
+ <tr>
704
+ <td colspan="3">
705
+ <div class="json-add-row">
706
+ ${renderArrayAddControl({
661
707
  pathKey,
662
708
  allowedTypes: allowedItemTypes,
663
709
  onAdd: (newValue) => {
@@ -667,32 +713,36 @@ export const ViraJsonForm = defineViraElement()({
667
713
  ], newValue);
668
714
  },
669
715
  })}
670
- </div>
716
+ </div>
717
+ </td>
718
+ </tr>
671
719
  `;
672
720
  return html `
673
- <div class="json-group">
674
- <div class="json-group-header">
675
- <span class="json-group-header-title">${title}</span>
676
- ${onDelete ? renderDeleteButton(onDelete) : nothing}
677
- </div>
678
- ${rowTemplates.length === 0
721
+ <table class="json-group ${isRoot ? '' : 'json-group-nested'}">
722
+ <tbody>
723
+ ${rowTemplates.length === 0
679
724
  ? html `
680
- <span class="json-empty-note">(empty array)</span>
681
- `
725
+ <tr>
726
+ <td colspan="3">
727
+ <span class="json-empty-note">(empty array)</span>
728
+ </td>
729
+ </tr>
730
+ `
682
731
  : nothing}
683
- ${rowTemplates} ${addRow}
684
- </div>
732
+ ${rowTemplates} ${addRow}
733
+ </tbody>
734
+ </table>
685
735
  `;
686
736
  }
687
- function renderValue(path, value, schema, onDelete) {
737
+ function renderValue(path, value, schema) {
688
738
  const concreteType = getJsonType(value);
689
739
  const allowedTypes = getAllowedJsonTypes(schema, resolveContext);
690
740
  const narrowedSchema = pickBranchForType(schema, concreteType, resolveContext);
691
741
  if (check.isArray(value)) {
692
- return renderArrayGroup(path, value, narrowedSchema, onDelete);
742
+ return renderArrayGroup(path, value, narrowedSchema);
693
743
  }
694
744
  else if (check.isObject(value)) {
695
- return renderObjectGroup(path, value, narrowedSchema, onDelete);
745
+ return renderObjectGroup(path, value, narrowedSchema);
696
746
  }
697
747
  const editor = renderPrimitive(path, value, narrowedSchema);
698
748
  const showSwitcher = !isDisabled &&
@@ -752,14 +802,15 @@ export const ViraJsonForm = defineViraElement()({
752
802
  const rawText = state.rawDraft ?? JSON.stringify(inputs.value, undefined, 4);
753
803
  return html `
754
804
  ${toolbarTemplate}
755
- <textarea
805
+ <${ViraTextArea.assign({
806
+ value: rawText,
807
+ disabled: isDisabled,
808
+ disableBrowserHelps: true,
809
+ rows: 12,
810
+ })}
756
811
  class="json-raw-textarea"
757
- spellcheck="false"
758
- ?disabled=${isDisabled}
759
- .value=${rawText}
760
- ${listen('input', (event) => {
761
- const textarea = extractEventTarget(event, HTMLTextAreaElement);
762
- const text = textarea.value;
812
+ ${listen(ViraTextArea.events.valueChange, (event) => {
813
+ const text = event.detail;
763
814
  const parsed = wrapInTry(() => JSON.parse(text));
764
815
  if (parsed instanceof Error) {
765
816
  updateState({
@@ -775,7 +826,7 @@ export const ViraJsonForm = defineViraElement()({
775
826
  emitRoot(parsed);
776
827
  }
777
828
  })}
778
- ></textarea>
829
+ ></${ViraTextArea}>
779
830
  ${state.rawError
780
831
  ? html `
781
832
  <${ViraError}>${state.rawError}</${ViraError}>
@@ -800,7 +851,7 @@ export const ViraJsonForm = defineViraElement()({
800
851
  `;
801
852
  }
802
853
  return html `
803
- ${toolbarTemplate} ${renderValue([], inputs.value, inputs.schema, undefined)}
854
+ ${toolbarTemplate} ${renderValue([], inputs.value, inputs.schema)}
804
855
  `;
805
856
  },
806
857
  });
@@ -5,7 +5,7 @@ import { type PartialWithUndefined } from '@augment-vir/common';
5
5
  * @category Elements
6
6
  * @see https://electrovir.github.io/vira/book/elements/vira-modal
7
7
  */
8
- export declare const ViraModal: import("element-vir").DeclarativeElementDefinition<"vira-modal", {
8
+ export declare const ViraModal: import("element-vir").DeclarativeElementDefinition<`vira-${string}`, {
9
9
  open: boolean;
10
10
  } & PartialWithUndefined<{
11
11
  /** If this isn't set, make sure to use the modal title slot to fill it in. */
@@ -37,4 +37,4 @@ export declare const ViraModal: import("element-vir").DeclarativeElementDefiniti
37
37
  cleanupListeners: undefined | (() => void);
38
38
  }, {
39
39
  modalClose: import("element-vir").DefineEvent<void>;
40
- }, "vira-modal-phone-size" | "vira-modal-no-content-padding", "vira-modal-backdrop-filter", readonly ["modalTitle"], readonly []>;
40
+ }, "vira-modal-phone-size" | "vira-modal-no-content-padding", "vira-modal-backdrop-filter", readonly ["vira-modal-modal-title"], readonly []>;
@@ -42,7 +42,7 @@ export const ViraModal = defineViraElement()({
42
42
  'vira-modal-phone-size': ({ inputs }) => !!inputs.isMobileSize,
43
43
  'vira-modal-no-content-padding': ({ inputs }) => !!inputs.noContentPadding,
44
44
  },
45
- slotNames: ['modalTitle'],
45
+ slotNames: ['vira-modal-modal-title'],
46
46
  cssVars: {
47
47
  'vira-modal-backdrop-filter': 'blur(3px)',
48
48
  },
@@ -205,7 +205,11 @@ export const ViraModal = defineViraElement()({
205
205
  >
206
206
  <div class="header">
207
207
  <div class="header-text-wrapper">
208
- <h1><slot name=${slotNames.modalTitle}>${inputs.modalTitle}</slot></h1>
208
+ <h1>
209
+ <slot name=${slotNames['vira-modal-modal-title']}>
210
+ ${inputs.modalTitle}
211
+ </slot>
212
+ </h1>
209
213
  ${inputs.modalSubtitle
210
214
  ? html `
211
215
  <sub>${inputs.modalSubtitle}</sub>
@@ -13,6 +13,7 @@ import { createOverflowObserver } from '../util/overflow-observer.js';
13
13
  import { renderMenuItemEntries } from '../util/pop-up-helpers.js';
14
14
  import { ViraMenuTrigger } from './pop-up/vira-menu-trigger.element.js';
15
15
  import { ViraMenuCornerStyle } from './pop-up/vira-menu.element.js';
16
+ import { ViraBoldText } from './vira-bold-text.element.js';
16
17
  import { ViraButton } from './vira-button.element.js';
17
18
  import { ViraIcon } from './vira-icon.element.js';
18
19
  import { ViraLink } from './vira-link.element.js';
@@ -387,7 +388,12 @@ export const ViraTabs = defineViraElement()({
387
388
  })}>
388
389
  <span class="tab-content">
389
390
  ${iconTemplate}
390
- <span class="tab-label">${tab.label}</span>
391
+ <${ViraBoldText.assign({
392
+ text: tab.label,
393
+ bold: isSelected,
394
+ })}
395
+ class="tab-label"
396
+ ></${ViraBoldText}>
391
397
  </span>
392
398
  </${ViraLink}>
393
399
  </li>
@@ -441,7 +447,7 @@ export const ViraTabs = defineViraElement()({
441
447
  showMenuCaret: true,
442
448
  color: ViraColorVariant.Neutral,
443
449
  })}
444
- slot=${ViraMenuTrigger.slotNames.trigger}
450
+ slot=${ViraMenuTrigger.slotNames['vira-menu-trigger-trigger']}
445
451
  ></${ViraButton}>
446
452
  ${menuItems}
447
453
  </${ViraMenuTrigger}>
@@ -454,6 +460,7 @@ export const ViraTabs = defineViraElement()({
454
460
  cleanupObserver: createOverflowObserver({
455
461
  element: tabsElement,
456
462
  widthElement: host,
463
+ hysteresisPx: 16,
457
464
  onChange(isOverflowing) {
458
465
  updateState({
459
466
  isOverflowing,
@@ -56,6 +56,7 @@ export const ViraTextArea = defineViraElement()({
56
56
 
57
57
  textarea {
58
58
  ${noNativeFormStyles};
59
+ overscroll-behavior: contain;
59
60
  font: inherit;
60
61
  cursor: text;
61
62
  width: 100%;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * A plus icon.
3
+ *
4
+ * @category Icon
5
+ * @category SVG
6
+ * @see https://electrovir.github.io/vira/book/icons/plus16icon
7
+ */
8
+ export declare const Plus16Icon: import("../../icon-svg.js").ViraIconSvg;
@@ -1,19 +1,19 @@
1
1
  import { html } from 'element-vir';
2
- import { viraIconCssVars } from '../icon-css-vars.js';
3
- import { defineIcon } from '../icon-svg.js';
2
+ import { viraIconCssVars } from '../../icon-css-vars.js';
3
+ import { defineIcon } from '../../icon-svg.js';
4
4
  /**
5
5
  * A plus icon.
6
6
  *
7
7
  * @category Icon
8
8
  * @category SVG
9
- * @see https://electrovir.github.io/vira/book/icons/plus24icon
9
+ * @see https://electrovir.github.io/vira/book/icons/plus16icon
10
10
  */
11
- export const Plus24Icon = defineIcon({
12
- name: 'Plus24Icon',
11
+ export const Plus16Icon = defineIcon({
12
+ name: 'Plus16Icon',
13
13
  svgTemplate: html `
14
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
14
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
15
15
  <path
16
- d="M12 5v14M5 12h14"
16
+ d="M8 3v10M3 8h10"
17
17
  fill="none"
18
18
  stroke=${viraIconCssVars['vira-icon-stroke-color'].value}
19
19
  stroke-width=${viraIconCssVars['vira-icon-stroke-width'].value}
@@ -8,6 +8,7 @@ export * from './icon-svgs/16/chevron-down-16.icon.js';
8
8
  export * from './icon-svgs/16/chevron-up-16.icon.js';
9
9
  export * from './icon-svgs/16/dash-16.icon.js';
10
10
  export * from './icon-svgs/16/element-16.icon.js';
11
+ export * from './icon-svgs/16/plus-16.icon.js';
11
12
  export * from './icon-svgs/16/upload-16.icon.js';
12
13
  export * from './icon-svgs/16/x-16.icon.js';
13
14
  export * from './icon-svgs/24/arrow-down-24.icon.js';
@@ -100,6 +101,7 @@ export declare const allIconsByName: {
100
101
  readonly Moon24Icon: import("./icon-svg.js").ViraIconSvg;
101
102
  readonly Options24Icon: import("./icon-svg.js").ViraIconSvg;
102
103
  readonly Pencil24Icon: import("./icon-svg.js").ViraIconSvg;
104
+ readonly Plus16Icon: import("./icon-svg.js").ViraIconSvg;
103
105
  readonly Plus24Icon: import("./icon-svg.js").ViraIconSvg;
104
106
  readonly Printer24Icon: import("./icon-svg.js").ViraIconSvg;
105
107
  readonly Shield24Icon: import("./icon-svg.js").ViraIconSvg;
@@ -189,6 +191,7 @@ export declare const all16IconsByName: {
189
191
  readonly ChevronUp16Icon: import("./icon-svg.js").ViraIconSvg;
190
192
  readonly Dash16Icon: import("./icon-svg.js").ViraIconSvg;
191
193
  readonly Element16Icon: import("./icon-svg.js").ViraIconSvg;
194
+ readonly Plus16Icon: import("./icon-svg.js").ViraIconSvg;
192
195
  readonly Upload16Icon: import("./icon-svg.js").ViraIconSvg;
193
196
  readonly X16Icon: import("./icon-svg.js").ViraIconSvg;
194
197
  };
@@ -3,6 +3,7 @@ import { ChevronDown16Icon } from './icon-svgs/16/chevron-down-16.icon.js';
3
3
  import { ChevronUp16Icon } from './icon-svgs/16/chevron-up-16.icon.js';
4
4
  import { Dash16Icon } from './icon-svgs/16/dash-16.icon.js';
5
5
  import { Element16Icon } from './icon-svgs/16/element-16.icon.js';
6
+ import { Plus16Icon } from './icon-svgs/16/plus-16.icon.js';
6
7
  import { Upload16Icon } from './icon-svgs/16/upload-16.icon.js';
7
8
  import { X16Icon } from './icon-svgs/16/x-16.icon.js';
8
9
  import { ArrowDown24Icon } from './icon-svgs/24/arrow-down-24.icon.js';
@@ -64,6 +65,7 @@ export * from './icon-svgs/16/chevron-down-16.icon.js';
64
65
  export * from './icon-svgs/16/chevron-up-16.icon.js';
65
66
  export * from './icon-svgs/16/dash-16.icon.js';
66
67
  export * from './icon-svgs/16/element-16.icon.js';
68
+ export * from './icon-svgs/16/plus-16.icon.js';
67
69
  export * from './icon-svgs/16/upload-16.icon.js';
68
70
  export * from './icon-svgs/16/x-16.icon.js';
69
71
  export * from './icon-svgs/24/arrow-down-24.icon.js';
@@ -156,6 +158,7 @@ export const allIconsByName = {
156
158
  Moon24Icon,
157
159
  Options24Icon,
158
160
  Pencil24Icon,
161
+ Plus16Icon,
159
162
  Plus24Icon,
160
163
  Printer24Icon,
161
164
  Shield24Icon,
@@ -245,6 +248,7 @@ export const all16IconsByName = {
245
248
  ChevronUp16Icon,
246
249
  Dash16Icon,
247
250
  Element16Icon,
251
+ Plus16Icon,
248
252
  Upload16Icon,
249
253
  X16Icon,
250
254
  };
@@ -4,7 +4,7 @@ import { wrapDefineElement } from 'element-vir';
4
4
  *
5
5
  * @category Internal
6
6
  */
7
- export const ViraTagNamePrefix = `vira-`;
7
+ export const ViraTagNamePrefix = 'vira-';
8
8
  /**
9
9
  * Define a vira element with custom requirements (like the `vira-` element tag prefix).
10
10
  *
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * @returns A cleanup function that disconnects all observers.
6
6
  */
7
- export declare function createOverflowObserver({ element, widthElement, onChange, }: Readonly<{
7
+ export declare function createOverflowObserver({ element, widthElement, onChange, hysteresisPx, }: Readonly<{
8
8
  /** The element whose `scrollWidth` is measured for content size. */
9
9
  element: Element;
10
10
  /**
@@ -14,4 +14,11 @@ export declare function createOverflowObserver({ element, widthElement, onChange
14
14
  */
15
15
  widthElement?: Element | undefined;
16
16
  onChange: (isOverflowing: boolean) => void;
17
+ /**
18
+ * Pixel margin required to flip the overflow state. Once overflowing, the content must fit
19
+ * within `availableWidth - hysteresisPx` to flip back; once not overflowing, the content must
20
+ * exceed `availableWidth + hysteresisPx` to flip on. Prevents rapid toggling from minute size
21
+ * changes.
22
+ */
23
+ hysteresisPx?: number | undefined;
17
24
  }>): () => void;
@@ -4,10 +4,18 @@
4
4
  *
5
5
  * @returns A cleanup function that disconnects all observers.
6
6
  */
7
- export function createOverflowObserver({ element, widthElement, onChange, }) {
7
+ export function createOverflowObserver({ element, widthElement, onChange, hysteresisPx = 0, }) {
8
8
  const availableWidthElement = widthElement || element;
9
+ let isOverflowing = false;
9
10
  function checkOverflow() {
10
- onChange(element.scrollWidth > availableWidthElement.clientWidth);
11
+ const contentWidth = element.scrollWidth;
12
+ const availableWidth = availableWidthElement.clientWidth;
13
+ const threshold = isOverflowing ? -hysteresisPx : hysteresisPx;
14
+ const nextOverflowing = contentWidth > availableWidth + threshold;
15
+ if (nextOverflowing !== isOverflowing) {
16
+ isOverflowing = nextOverflowing;
17
+ onChange(isOverflowing);
18
+ }
11
19
  }
12
20
  const resizeObserver = new ResizeObserver(checkOverflow);
13
21
  resizeObserver.observe(element);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vira",
3
- "version": "31.18.1",
3
+ "version": "31.19.0",
4
4
  "description": "A simple and highly versatile design system using element-vir.",
5
5
  "keywords": [
6
6
  "design",
@@ -38,9 +38,9 @@
38
38
  "test:docs": "virmator docs check"
39
39
  },
40
40
  "dependencies": {
41
- "@augment-vir/assert": "^31.69.0",
42
- "@augment-vir/common": "^31.69.0",
43
- "@augment-vir/web": "^31.69.0",
41
+ "@augment-vir/assert": "^31.70.1",
42
+ "@augment-vir/common": "^31.70.1",
43
+ "@augment-vir/web": "^31.70.1",
44
44
  "@electrovir/color": "^1.7.9",
45
45
  "date-vir": "^8.3.2",
46
46
  "device-navigation": "^4.5.5",
@@ -48,12 +48,12 @@
48
48
  "lit-css-vars": "^3.6.2",
49
49
  "observavir": "^2.3.2",
50
50
  "page-active": "^1.0.3",
51
- "spa-router-vir": "^6.5.0",
51
+ "spa-router-vir": "^6.6.0",
52
52
  "type-fest": "^5.6.0",
53
53
  "typed-event-target": "^4.3.0"
54
54
  },
55
55
  "devDependencies": {
56
- "@augment-vir/test": "^31.69.0",
56
+ "@augment-vir/test": "^31.70.1",
57
57
  "@web/dev-server-esbuild": "^1.0.5",
58
58
  "@web/test-runner": "^0.20.2",
59
59
  "@web/test-runner-commands": "^0.9.0",
@@ -61,12 +61,12 @@
61
61
  "@web/test-runner-visual-regression": "^0.10.0",
62
62
  "esbuild": "^0.28.0",
63
63
  "istanbul-smart-text-reporter": "^1.1.5",
64
- "lucide-static": "^1.14.0",
64
+ "lucide-static": "^1.16.0",
65
65
  "markdown-code-example-inserter": "^3.0.5",
66
66
  "theme-vir": "^28.25.0",
67
67
  "typedoc": "^0.28.19",
68
68
  "typescript": "5.9.3",
69
- "vite": "^8.0.10",
69
+ "vite": "^8.0.14",
70
70
  "vite-tsconfig-paths": "^6.1.1"
71
71
  },
72
72
  "peerDependencies": {
@@ -1,8 +0,0 @@
1
- /**
2
- * A plus icon.
3
- *
4
- * @category Icon
5
- * @category SVG
6
- * @see https://electrovir.github.io/vira/book/icons/plus24icon
7
- */
8
- export declare const Plus24Icon: import("../icon-svg.js").ViraIconSvg;