From 5d40b600dbcd6075ca6091bb8a258fda611311ab Mon Sep 17 00:00:00 2001
From: Laton Vermette <1619661+latonv@users.noreply.github.com>
Date: Wed, 20 May 2026 10:49:29 -0700
Subject: [PATCH 1/7] Add 'range' and 'number' options to style prop settings
---
.../story-components/story-styles-settings.ts | 65 ++++++++++++++++---
1 file changed, 55 insertions(+), 10 deletions(-)
diff --git a/demo/story-components/story-styles-settings.ts b/demo/story-components/story-styles-settings.ts
index 253e764..3fd527f 100644
--- a/demo/story-components/story-styles-settings.ts
+++ b/demo/story-components/story-styles-settings.ts
@@ -1,6 +1,7 @@
import { css, html, LitElement, nothing, type CSSResultGroup } from 'lit';
import { property, queryAll } from 'lit/decorators.js';
import { customElement } from 'lit/decorators/custom-element.js';
+import { ifDefined } from 'lit/directives/if-defined.js';
import themeStyles from '@src/themes/theme-styles';
import { labelToId } from '../story-utils';
@@ -8,8 +9,12 @@ import { labelToId } from '../story-utils';
export type StyleInputSettings = {
label: string;
cssVariable: string;
- defaultValue?: string;
- inputType?: 'color' | 'text';
+ defaultValue?: string | number;
+ inputType?: 'color' | 'text' | 'number' | 'range';
+ min?: number;
+ max?: number;
+ step?: number;
+ unit?: string;
};
export type StyleInputData = {
@@ -32,37 +37,64 @@ export class StoryStylesSettings extends LitElement {
return html`
`;
}
+ /* Updates the live readout next to a range slider as it moves. */
+ private updateRangeReadout(e: Event): void {
+ const input = e.currentTarget as HTMLInputElement;
+ const output = this.renderRoot.querySelector(
+ `output[for="${input.id}"]`,
+ );
+ if (!output) return;
+ const unit = input.dataset.unit ?? '';
+ output.textContent = `${input.value}${unit}`;
+ }
+
/* Applies styles to demo component. */
private applyStyles(): void {
const appliedStyles: string[] = [];
this.styleInputs?.forEach((input) => {
if (!input.dataset.variable || !input.value) return;
- appliedStyles.push(`${input.dataset.variable}: ${input.value};`);
+ const unit = input.dataset.unit ?? '';
+ appliedStyles.push(`${input.dataset.variable}: ${input.value}${unit};`);
});
this.dispatchEvent(
@@ -80,6 +112,19 @@ export class StoryStylesSettings extends LitElement {
background-color: var(--primary-background-color);
padding: 1em;
}
+
+ .style-input-cell {
+ display: flex;
+ align-items: center;
+ }
+
+ .style-readout {
+ display: inline-block;
+ }
+
+ input[type="range"] {
+ margin: 5px;
+ }
`,
];
}
From 4b9f9b11fa35ac93cdb10ddde1267bd8145f531a Mon Sep 17 00:00:00 2001
From: Laton Vermette <1619661+latonv@users.noreply.github.com>
Date: Wed, 20 May 2026 10:49:46 -0700
Subject: [PATCH 2/7] Add a range style setting to the combo box story
---
src/elements/ia-combo-box/ia-combo-box-story.ts | 10 ++++++++++
src/elements/ia-combo-box/ia-combo-box.ts | 3 ++-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/elements/ia-combo-box/ia-combo-box-story.ts b/src/elements/ia-combo-box/ia-combo-box-story.ts
index 5dd283c..5aa8060 100644
--- a/src/elements/ia-combo-box/ia-combo-box-story.ts
+++ b/src/elements/ia-combo-box/ia-combo-box-story.ts
@@ -40,6 +40,16 @@ const styleInputSettings: StyleInputSettings[] = [
defaultValue: '250px',
inputType: 'text',
},
+ {
+ label: 'Dropdown fade duration',
+ cssVariable: '--combo-box-list-fade-duration',
+ defaultValue: 125,
+ inputType: 'range',
+ min: 0,
+ max: 1000,
+ step: 25,
+ unit: 'ms',
+ },
];
// Option sets
diff --git a/src/elements/ia-combo-box/ia-combo-box.ts b/src/elements/ia-combo-box/ia-combo-box.ts
index 6e925c7..39c2615 100644
--- a/src/elements/ia-combo-box/ia-combo-box.ts
+++ b/src/elements/ia-combo-box/ia-combo-box.ts
@@ -1217,6 +1217,7 @@ export class IAComboBox extends LitElement {
--combo-box-padding--: var(--padding-sm);
--combo-box-list-width--: var(--combo-box-list-width, unset);
--combo-box-list-max-height--: var(--combo-box-list-max-height, 250px);
+ --combo-box-list-fade-duration--: var(--combo-box-list-fade-duration, 125ms);
}
#container {
@@ -1317,7 +1318,7 @@ export class IAComboBox extends LitElement {
max-height: 400px;
box-shadow: 0 0 1px 1px #ddd;
opacity: 0;
- transition: opacity 0.125s ease;
+ transition: opacity var(--combo-box-list-fade-duration--) ease;
}
#options-list.visible {
From 5bbe900eb00fe629ebc073a5f7e9530aa3add22e Mon Sep 17 00:00:00 2001
From: Laton Vermette <1619661+latonv@users.noreply.github.com>
Date: Wed, 20 May 2026 10:50:01 -0700
Subject: [PATCH 3/7] Add a numeric style setting to the search bar story
---
.../ia-dropdown-search-bar-story.ts | 8 ++++++++
.../ia-dropdown-search-bar/ia-dropdown-search-bar.ts | 4 +++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/elements/ia-dropdown-search-bar/ia-dropdown-search-bar-story.ts b/src/elements/ia-dropdown-search-bar/ia-dropdown-search-bar-story.ts
index db1618c..4748b8b 100644
--- a/src/elements/ia-dropdown-search-bar/ia-dropdown-search-bar-story.ts
+++ b/src/elements/ia-dropdown-search-bar/ia-dropdown-search-bar-story.ts
@@ -28,6 +28,14 @@ const styleInputSettings: StyleInputSettings[] = [
defaultValue: '5px',
inputType: 'text',
},
+ {
+ label: 'Dropdown z-index',
+ cssVariable: '--dropdown-z-index',
+ defaultValue: 2,
+ inputType: 'number',
+ min: 0,
+ step: 1,
+ },
];
// Component defaults
diff --git a/src/elements/ia-dropdown-search-bar/ia-dropdown-search-bar.ts b/src/elements/ia-dropdown-search-bar/ia-dropdown-search-bar.ts
index 0ee51c3..426d800 100644
--- a/src/elements/ia-dropdown-search-bar/ia-dropdown-search-bar.ts
+++ b/src/elements/ia-dropdown-search-bar/ia-dropdown-search-bar.ts
@@ -75,7 +75,7 @@ export class IADropdownSearchBar extends LitElement {
`;
}
-
+
willUpdate(changed: PropertyValues) {
// Push new categories down to the inner dropdown immediately, since ia-dropdown
// mutates its own selected option on interaction which can cause Lit's
@@ -235,6 +235,7 @@ export class IADropdownSearchBar extends LitElement {
--search-bar-width--: var(--search-bar-width, 300px);
--search-bar-internal-padding--: var(--padding-sm, 5px);
--clear-button-offset--: var(--clear-button-offset, 0);
+ --dropdown-z-index--: var(--dropdown-z-index, initial);
}
#container {
@@ -276,6 +277,7 @@ export class IADropdownSearchBar extends LitElement {
--dropdownBorderRadius: 4px;
--buttonSlotPaddingRight: 0;
--dropdownTextAlign: left;
+ --dropdownListZIndex: var(--dropdown-z-index--);
}
#category-dropdown [slot='dropdown-label'] {
From 3eaaf62d2d33b17da8f86030122213157efea7d9 Mon Sep 17 00:00:00 2001
From: Laton Vermette <1619661+latonv@users.noreply.github.com>
Date: Wed, 20 May 2026 12:17:37 -0700
Subject: [PATCH 4/7] Extract helpers and other minor cleanup
---
.../story-components/story-styles-settings.ts | 93 +++++++++++--------
src/elements/ia-combo-box/ia-combo-box.ts | 5 +-
2 files changed, 58 insertions(+), 40 deletions(-)
diff --git a/demo/story-components/story-styles-settings.ts b/demo/story-components/story-styles-settings.ts
index 3fd527f..20c1f6d 100644
--- a/demo/story-components/story-styles-settings.ts
+++ b/demo/story-components/story-styles-settings.ts
@@ -1,4 +1,11 @@
-import { css, html, LitElement, nothing, type CSSResultGroup } from 'lit';
+import {
+ css,
+ html,
+ LitElement,
+ nothing,
+ type CSSResultGroup,
+ type TemplateResult,
+} from 'lit';
import { property, queryAll } from 'lit/decorators.js';
import { customElement } from 'lit/decorators/custom-element.js';
import { ifDefined } from 'lit/directives/if-defined.js';
@@ -6,11 +13,13 @@ import { ifDefined } from 'lit/directives/if-defined.js';
import themeStyles from '@src/themes/theme-styles';
import { labelToId } from '../story-utils';
+export type StyleInputType = 'color' | 'text' | 'number' | 'range';
+
export type StyleInputSettings = {
label: string;
cssVariable: string;
- defaultValue?: string | number;
- inputType?: 'color' | 'text' | 'number' | 'range';
+ defaultValue: string | number;
+ inputType?: StyleInputType;
min?: number;
max?: number;
step?: number;
@@ -37,50 +46,55 @@ export class StoryStylesSettings extends LitElement {
return html`
`;
}
+ /* Renders one row of the settings table for the given style input. */
+ private renderStyleRow(input: StyleInputSettings): TemplateResult {
+ const inputId = labelToId(input.label);
+ const isNumeric =
+ input.inputType === 'number' || input.inputType === 'range';
+ return html`
+
+ |
+
+ |
+
+
+ ${input.inputType === 'range'
+ ? html``
+ : nothing}
+ |
+
+ `;
+ }
+
/* Updates the live readout next to a range slider as it moves. */
private updateRangeReadout(e: Event): void {
const input = e.currentTarget as HTMLInputElement;
const output = this.renderRoot.querySelector(
- `output[for="${input.id}"]`,
+ `output[for="${CSS.escape(input.id)}"]`,
);
if (!output) return;
const unit = input.dataset.unit ?? '';
@@ -119,10 +133,11 @@ export class StoryStylesSettings extends LitElement {
}
.style-readout {
- display: inline-block;
+ min-width: 3.5em;
+ text-align: right;
}
- input[type="range"] {
+ input[type='range'] {
margin: 5px;
}
`,
diff --git a/src/elements/ia-combo-box/ia-combo-box.ts b/src/elements/ia-combo-box/ia-combo-box.ts
index 39c2615..44f2187 100644
--- a/src/elements/ia-combo-box/ia-combo-box.ts
+++ b/src/elements/ia-combo-box/ia-combo-box.ts
@@ -1217,7 +1217,10 @@ export class IAComboBox extends LitElement {
--combo-box-padding--: var(--padding-sm);
--combo-box-list-width--: var(--combo-box-list-width, unset);
--combo-box-list-max-height--: var(--combo-box-list-max-height, 250px);
- --combo-box-list-fade-duration--: var(--combo-box-list-fade-duration, 125ms);
+ --combo-box-list-fade-duration--: var(
+ --combo-box-list-fade-duration,
+ 125ms
+ );
}
#container {
From a187c83019be546e43510c91c13002562b6284a1 Mon Sep 17 00:00:00 2001
From: Laton Vermette <1619661+latonv@users.noreply.github.com>
Date: Wed, 20 May 2026 15:22:48 -0700
Subject: [PATCH 5/7] Add unit tests for story style settings
---
.../story-styles-settings.test.ts | 174 ++++++++++++++++++
1 file changed, 174 insertions(+)
create mode 100644 demo/story-components/story-styles-settings.test.ts
diff --git a/demo/story-components/story-styles-settings.test.ts b/demo/story-components/story-styles-settings.test.ts
new file mode 100644
index 0000000..fa2a9eb
--- /dev/null
+++ b/demo/story-components/story-styles-settings.test.ts
@@ -0,0 +1,174 @@
+import { fixture, oneEvent } from '@open-wc/testing-helpers';
+import { describe, expect, test } from 'vitest';
+import { html } from 'lit';
+
+import type {
+ StoryStylesSettings,
+ StyleInputData,
+} from './story-styles-settings';
+import './story-styles-settings';
+
+async function makeSettings(
+ data: StyleInputData,
+): Promise {
+ const el = await fixture(html`
+
+ `);
+ await el.updateComplete;
+ return el;
+}
+
+function getInput(el: StoryStylesSettings, id: string): HTMLInputElement {
+ const input = el.shadowRoot?.querySelector(`#${id}`);
+ expect(input, `input #${id} should exist`).to.exist;
+ return input as HTMLInputElement;
+}
+
+describe('StoryStylesSettings', () => {
+ describe('range inputs', () => {
+ const rangeData: StyleInputData = {
+ settings: [
+ {
+ label: 'Foos',
+ cssVariable: '--foo',
+ defaultValue: 50,
+ inputType: 'range',
+ min: 0,
+ max: 250,
+ step: 10,
+ unit: 'px',
+ },
+ ],
+ };
+
+ test('renders an input of type range with min/max/step set', async () => {
+ const el = await makeSettings(rangeData);
+ const input = getInput(el, 'foos');
+
+ expect(input.type).to.equal('range');
+ expect(input.min).to.equal('0');
+ expect(input.max).to.equal('250');
+ expect(input.step).to.equal('10');
+ expect(input.value).to.equal('50');
+ expect(input.dataset.unit).to.equal('px');
+ });
+
+ test('renders a readout linked to the input', async () => {
+ const el = await makeSettings(rangeData);
+
+ const readout = el.shadowRoot?.querySelector(
+ 'output.style-readout',
+ );
+ expect(readout).to.exist;
+ expect(readout?.getAttribute('for')).to.equal('foos');
+ expect(readout?.textContent).to.equal('50px');
+ });
+
+ test('readout updates on input event with value + unit', async () => {
+ const el = await makeSettings(rangeData);
+ const input = getInput(el, 'foos');
+
+ input.value = '200';
+ input.dispatchEvent(new Event('input'));
+ await el.updateComplete;
+
+ const readout = el.shadowRoot?.querySelector(
+ 'output.style-readout',
+ );
+ expect(readout?.textContent).to.equal('200px');
+ });
+
+ test('readout omits unit when unit is not provided', async () => {
+ const el = await makeSettings({
+ settings: [
+ {
+ label: 'Foos',
+ cssVariable: '--foo',
+ defaultValue: 0.5,
+ inputType: 'range',
+ min: 0,
+ max: 1,
+ step: 0.1,
+ },
+ ],
+ });
+ const input = getInput(el, 'foos');
+
+ const readout = el.shadowRoot?.querySelector(
+ 'output.style-readout',
+ );
+ expect(readout?.textContent).to.equal('0.5');
+
+ input.value = '0.8';
+ input.dispatchEvent(new Event('input'));
+ await el.updateComplete;
+
+ expect(readout?.textContent).to.equal('0.8'); // No unit included
+ });
+ });
+
+ describe('number inputs', () => {
+ const numberData: StyleInputData = {
+ settings: [
+ {
+ label: 'Foos',
+ cssVariable: '--foo',
+ defaultValue: 1,
+ inputType: 'number',
+ min: 0,
+ step: 1,
+ },
+ ],
+ };
+
+ test('renders an input of type number with min/step set', async () => {
+ const el = await makeSettings(numberData);
+ const input = getInput(el, 'foos');
+
+ expect(input.type).to.equal('number');
+ expect(input.min).to.equal('0');
+ expect(input.step).to.equal('1');
+ expect(input.value).to.equal('1');
+ });
+ });
+
+ describe('non-numeric inputs', () => {
+ test('text input ignores min/max/step even when set on the settings object', async () => {
+ const el = await makeSettings({
+ settings: [
+ {
+ label: 'Foos',
+ cssVariable: '--foos',
+ defaultValue: '200px',
+ inputType: 'text',
+ // These should be ignored for non-numeric input types
+ min: 0,
+ max: 100,
+ step: 1,
+ },
+ ],
+ });
+ const input = getInput(el, 'foos');
+
+ expect(input.type).to.equal('text');
+ expect(input.hasAttribute('min')).to.be.false;
+ expect(input.hasAttribute('max')).to.be.false;
+ expect(input.hasAttribute('step')).to.be.false;
+ });
+
+ test('input without inputType defaults to type=text', async () => {
+ const el = await makeSettings({
+ settings: [
+ {
+ label: 'Untyped',
+ cssVariable: '--untyped',
+ defaultValue: 'foo',
+ },
+ ],
+ });
+ const input = getInput(el, 'untyped');
+
+ expect(input.type).to.equal('text');
+ });
+ });
+});
From 8e6fd13e41c4b07ba530beb11ce2ec2357c00c72 Mon Sep 17 00:00:00 2001
From: Laton Vermette <1619661+latonv@users.noreply.github.com>
Date: Wed, 20 May 2026 15:25:15 -0700
Subject: [PATCH 6/7] Fix doc comments
---
demo/story-components/story-styles-settings.ts | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/demo/story-components/story-styles-settings.ts b/demo/story-components/story-styles-settings.ts
index 20c1f6d..15bd285 100644
--- a/demo/story-components/story-styles-settings.ts
+++ b/demo/story-components/story-styles-settings.ts
@@ -55,7 +55,9 @@ export class StoryStylesSettings extends LitElement {
`;
}
- /* Renders one row of the settings table for the given style input. */
+ /**
+ * Renders one row of the settings table for the given style input.
+ */
private renderStyleRow(input: StyleInputSettings): TemplateResult {
const inputId = labelToId(input.label);
const isNumeric =
@@ -90,7 +92,9 @@ export class StoryStylesSettings extends LitElement {
`;
}
- /* Updates the live readout next to a range slider as it moves. */
+ /**
+ * Updates the live readout next to a range slider as it moves.
+ */
private updateRangeReadout(e: Event): void {
const input = e.currentTarget as HTMLInputElement;
const output = this.renderRoot.querySelector(
@@ -101,7 +105,9 @@ export class StoryStylesSettings extends LitElement {
output.textContent = `${input.value}${unit}`;
}
- /* Applies styles to demo component. */
+ /**
+ * Applies styles to demo component.
+ */
private applyStyles(): void {
const appliedStyles: string[] = [];
From 5a79cbbf3027d7faa0ce57cd4894b524fd2b3de0 Mon Sep 17 00:00:00 2001
From: Laton Vermette <1619661+latonv@users.noreply.github.com>
Date: Wed, 20 May 2026 15:27:06 -0700
Subject: [PATCH 7/7] Remove unused import
---
demo/story-components/story-styles-settings.test.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/demo/story-components/story-styles-settings.test.ts b/demo/story-components/story-styles-settings.test.ts
index fa2a9eb..cb7b943 100644
--- a/demo/story-components/story-styles-settings.test.ts
+++ b/demo/story-components/story-styles-settings.test.ts
@@ -1,4 +1,4 @@
-import { fixture, oneEvent } from '@open-wc/testing-helpers';
+import { fixture } from '@open-wc/testing-helpers';
import { describe, expect, test } from 'vitest';
import { html } from 'lit';