Skip to content

Slider value unset#4173

Open
Befkadu1 wants to merge 7 commits into
mainfrom
slider-value-unset
Open

Slider value unset#4173
Befkadu1 wants to merge 7 commits into
mainfrom
slider-value-unset

Conversation

@Befkadu1

@Befkadu1 Befkadu1 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

fix #4161

Note

see the parent issue #4161 for more details and screen recordings before and after this change.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Added an “unset” slider state for non-finite values, including updated indicator and aria-valuetext.
    • Added a clear button for sliders that are neither readonly nor required.
    • Introduced a new example showing how to start in an unset state.
  • Bug Fixes
    • Improved accessible behavior and focus management after clearing/unsetting.
  • Documentation
    • Updated slider example behavior and semantics for “clear”/“value not set”.
  • Tests
    • Added tests covering unset/clear transitions, ARIA output, and change event behavior.
  • Style
    • Refined thumb/track and clear button styling for the unset state.

Review:

  • Commits are atomic
  • Commits have the correct type for the changes made
  • Commits with breaking changes are marked as such

Browsers tested:

(Check any that applies, it's ok to leave boxes unchecked if testing something didn't seem relevant.)

Windows:

  • Chrome
  • Edge
  • Firefox

Linux:

  • Chrome
  • Firefox

macOS:

  • Chrome
  • Firefox
  • Safari

Mobile:

  • Chrome on Android
  • iOS

@Befkadu1 Befkadu1 requested a review from a team as a code owner July 8, 2026 09:04
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an unset state to limel-slider, with clear-button support, localized messages, styling updates, translation fallback handling, examples, and tests.

Changes

Slider unset/clear feature

Layer / File(s) Summary
Core unset/clear logic
src/components/slider/slider.tsx
Adds isUnset state, clear handling, localized ARIA text, and unset-aware rendering and value synchronization.
Slider styling for unset/clear/readonly states
src/components/slider/slider.scss, src/components/slider/partial-styles/_thumb.scss
Adds clear-button styling, unset track/thumb styling, readonly/required spacing changes, shared track color variables, and centered thumb indicator styling.
Translation fallback lookup
src/global/translations.ts
Adds nb aliasing and English fallback lookup for missing language maps.
Localized clear/unset translation strings
src/translations/{da,de,en,fi,fr,nl,no,sv}.ts
Adds clear-value, clear-value-of, and value-not-set translation keys across supported locales.
Slider examples and test coverage for unset/required
src/components/slider/examples/slider-basic.tsx, src/components/slider/examples/slider-unset.tsx, src/components/slider/slider.spec.tsx
Adds a required toggle example, an unset slider example, and tests for unset state, clear behavior, and focus changes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant limel-slider
  participant Translations.get
  participant ChangeEvent

  User->>limel-slider: click clear-button
  limel-slider->>limel-slider: handleClear()
  limel-slider->>limel-slider: enterUnsetState()
  limel-slider->>ChangeEvent: emit change(NaN)
  limel-slider->>Translations.get: resolve value-not-set / clear-value
  limel-slider->>limel-slider: focus() input
  User->>limel-slider: type value
  limel-slider->>limel-slider: handleInput() clears isUnset
Loading

Possibly related issues

  • #4162: Adds language support to limel-slider and updates translation lookup behavior for missing language values like nb.

Possibly related PRs

Suggested labels: visual design

Suggested reviewers: LucyChyzhova

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: adding an unset state to the slider.
Linked Issues check ✅ Passed The changes implement the requested unset state, clear affordance, accessibility text, programmatic reset, and clear-event contract.
Out of Scope Changes check ✅ Passed The extra translation and registry updates support the slider feature and do not appear unrelated to the requested work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch slider-value-unset

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

Documentation has been published to https://lundalogik.github.io/lime-elements/versions/PR-4173/

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/slider/slider.tsx`:
- Around line 103-110: The slider’s new language prop is not demonstrated in the
examples, so add a `language` example to the slider documentation/demo so users
can see how `Slider` uses `language` to localize accessible labels. Update the
slider example content alongside the `language` prop in `slider.tsx` (and any
related docs/demo entry) to show a non-default language and the resulting
translated labels.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 276aaa4c-b5de-497f-9f43-9e9ec86cb3b2

📥 Commits

Reviewing files that changed from the base of the PR and between 8610768 and 00318d7.

⛔ Files ignored due to path filters (1)
  • etc/lime-elements.api.md is excluded by !etc/lime-elements.api.md
📒 Files selected for processing (14)
  • src/components/slider/examples/slider-basic.tsx
  • src/components/slider/examples/slider-unset.tsx
  • src/components/slider/partial-styles/_thumb.scss
  • src/components/slider/slider.scss
  • src/components/slider/slider.spec.tsx
  • src/components/slider/slider.tsx
  • src/translations/da.ts
  • src/translations/de.ts
  • src/translations/en.ts
  • src/translations/fi.ts
  • src/translations/fr.ts
  • src/translations/nl.ts
  • src/translations/no.ts
  • src/translations/sv.ts

Comment thread src/components/slider/slider.tsx
@Kiarokh

Kiarokh commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🤖 AI-generated review from 6 parallel agents, re-run after the review-driven fixes were applied to this branch. Treat as input, not a verdict — agents can be wrong or miss context.

Consolidated Review — limel-slider: allow the value to be unset

Summary

Gives limel-slider an unset/clearable state. When the value is non-finite (NaN/null/undefined) the thumb rests centered, the indicator shows ?, and assistive tech announces "Value not set" via aria-valuetext; the state is exposed as is-unset on the host. A trailing clear <button> (inline-SVG icon, context-aware aria-label + tooltip) is offered on optional, editable sliders and emits NaN on change, then focuses the slider. Adds a language prop, three generic translation keys across 8 locales, an example, and a behavioral spec.

Merge Readiness — MERGE WITH CAVEATS ⚠️

No blocking defects — the branch is clean (build green, 10 behavioral specs passing). The two caveats below are decisions/verifications, not bugs.

Already addressed on this branch during the review

Several findings from the run were fixed while reviewing, and are reflected below as resolved:

  • Midpoint anchoring so both arrow-key directions are live after clearing (no dead keys, no thumb jump).
  • Focus moves to the slider on clear — focus no longer falls to <body>, and AT announces the now-unset state.
  • required/readonly gate the clear button (so it isn't forced on required fields); internal naming unified to isUnset/.is-unset; is-unset exposed on the host; enterUnsetState() extracted to keep the unset assignments in one place; the example reports the real value (null); 10 behavioral specs added.
  • Guarded translate.get so an unmapped language falls back to English instead of throwing — fixing the pre-existing Language-aware components crash on a language value that has no translations (e.g. nb) #4162 crash that the composite example's runtime test surfaced; nb is also aliased to the Norwegian translations.

Dimensions

1. Backward Compatibility — ACCEPTABLE ⚠️

The public API is additive (only a new language prop; change stays EventEmitter<number>, value stays number), but the feature changes default behavior for existing sliders.

What works well: no removed/retyped API; required/readonly gating limits the new surface; a value of 0 is now handled correctly (readonly 0 shows 0, not the em-dash).

Issues:

  • 🟡 [Medium] Clear button, reserved padding, and unset-instead-of-min rendering are now default-on for every optional editable slider, with no opt-out except required/readonly; clearing emits NaN on change (slider.tsx renderClearButton/handleClear, form/widgets/slider.ts forwards event.detail). Intended behavior — see Top Recommendation feat(lime-switch): limel-switch component added #1.
  • 🟡 [Medium] That NaN reaches rjsf form state through the slider widget. See Top Recommendation feat(limel-select): limel-select component added #2.
Minor nits (1)
  • 🔵 hasValue for value === 0 flipped falsetrue (hasValue={!this.isUnset}); the only visible effect is a readonly 0 now rendering the value instead of the placeholder — a net fix, but an edge behavior change.

2. Architecture — GOOD ✅

Sound state model and interaction. The Medium (focus-on-clear stranding keyboard users) and the empty-state duplication Low from the run were both fixed on this branch.

What works well: single-source unset state via enterUnsetState(); NaN is fully contained (never reaches arithmetic — getFraction/multiplyByFactor/setPercentageClass only see finite values); focus-on-clear runs synchronously before the button self-disables (no race); midpoint anchoring keeps both arrow directions live; is-unset on the host lets the whole component (and consumers) style off it.

Issues: None.

Minor nits (1)
  • 🔵 @Prop({ reflect: true }) on value can surface a cosmetic value="NaN" attribute on the host when cleared. Harmless (nothing reads the attribute), just untidy.

3. Code Quality — GOOD ✅

The new spec is genuinely behavioral (state class, indicator text, aria, event payload, focus, gating, contextual label) — no assertion that would pass if the feature broke. The isUnset/is-unset naming drift flagged in the run is now unified.

What works well: behavioral tests; consistent "unset" vocabulary; translations follow the { label } merge-code convention.

Issues: None.

Minor nits (4)
  • 🔵 value: number is documented to accept null/undefined, but only NaN typechecks for a strict-TS consumer (value?: number would at least make undefined honest).
  • 🔵 Test gap: the contextual-label test only asserts English — one non-English case (e.g. language: 'sv') would exercise the translation plumbing end-to-end.
  • 🔵 Test gap: the clear button's || this.disabled branch is untested (only the unset path is covered).
  • 🔵 Narrow uncontrolled-use edge: after a button-clear, value stays finite locally while isUnset is set; if a consumer then re-assigns the identical value, @Watch('value') won't fire. Inherent to the deliberate NaN-emit design and very narrow.

4. Security — SAFE ✅

What works well: the consumer-controlled label reaches only text/attribute sinks — traced through translate.get (function replacer, so no $-pattern expansion; regex runs on the static template, so no ReDoS) into aria-label and the tooltip's escaped text node. The inline clear-icon SVG is a static path with no interpolation. ?/aria-valuetext carry no untrusted input.

Issues: None.

5. Observability — GOOD ✅

Appropriate for a UI component; no swallowed errors, no stray console.*, and the unset contract is documented (JSDoc, example, reflected attribute, host class, aria-valuetext).

Issues: None.

Minor nits (2)
  • 🔵 The cleared-state announcement is timing-dependent: handleClear focuses the input synchronously, but aria-valuetext="Value not set" is applied on Stencil's async re-render. Most screen readers re-read after commit, but deferring the focus to after render would make it deterministic.
  • 🔵 The focus test asserts .focus() was called, not that the unset state is actually announced.

6. Performance — GOOD ✅

No regression — the tooltip's heavy machinery (Popper, ResizeObserver, timers) stays lazy until hover.

What works well: syncStateFromValue runs only on load / @Watch('value') (not per drag frame); setPercentageClass early-returns while unset; the mounted tooltip adds no drag-time cost (its props are inequality-guarded).

Issues: None.

Minor nits (3)
  • 🔵 The inline clear-icon SVG is re-patched (setAttribute) each render, vs the old zero-cost CSS background — negligible (browsers no-op identical values).
  • 🔵 Per-render ref closure allocation on the range input.
  • 🔵 translate.get() recomputes the same label string on every render (incl. per drag frame); could be memoized. Microseconds.

Top Recommendations

  1. 🟡 [Medium — Backward Compatibility] — Confirm the "optional = clearable, emits NaN" contract

    • Where: slider.tsx renderClearButton/handleClear; forwarded via form/widgets/slider.ts.
    • What: Every optional (non-required, non-readonly) slider now shows a clear button and, when cleared, emits NaN on change — the event type is deliberately kept EventEmitter<number> (rather than number | null) to avoid a breaking type change. Decide whether that's the contract we want, or whether a clearable opt-in / migration note is warranted.
    • Why: It's the intended feature and arguably more correct than the old "silently rests at min", but it changes existing optional sliders' appearance and lets NaN flow to consumers with no opt-out. This is the core NaN-vs-null discussion (issue limel-slider: no way to unset / clear the value #4161).
  2. 🟡 [Medium — Backward Compatibility] — Verify NaN handling in lime-crm form save/validation

    • Where: form/widgets/slider.tsprops.onChange(NaN); downstream validation/serialization.
    • What: Confirm a cleared optional slider behaves acceptably downstream — NaN serializes to null via JSON.stringify, and may hit AJV type: "number".
    • Why: On main a slider could never emit a non-finite value into form state; this path is newly reachable. Mostly moot for required fields (no clear button), but worth a concrete check before shipping to form consumers.

Net: clean and clearly better than main. The headline finding from the run (focus-on-clear stranding keyboard users) is fixed via midpoint anchoring, naming/dedup nits are resolved, tests are in place, and security/perf are clean. What remains is one design decision to ratify and one downstream check.

@Kiarokh Kiarokh force-pushed the slider-value-unset branch from ad98145 to ac93ecd Compare July 8, 2026 10:26
Kiarokh and others added 7 commits July 8, 2026 14:05
…lue-not-set`

These are useful for various input-type components,
specially to be used as `aria-label` or `aria-valuetext`
The slider was the only input in the library that could not be empty: it
always resolved to a value and rendered "unset" identically to the
minimum. It now supports an explicit unset state — the thumb rests
centered with no fill, the indicator shows `?`, the label stays
unfloated, and assistive technology announces "Value not set" through
`aria-valuetext`.

A trailing clear button (with a context-aware `aria-label` and a tooltip)
lets users return to the unset state. Clearing emits `NaN` on the
`change` event, keeping its payload a `number` so consumers are not
forced into a breaking type change.

Refs #4161

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the value was cleared, focus stayed on the clear button, which
immediately disables itself — so focus fell to the document body. Move
focus to the range input instead, so keyboard users can adjust the value
right away and assistive technology announces the now-unset state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Behavioral specs for the new unset/clear functionality: a non-finite
value renders the empty state (`?` indicator, "Value not set"), a finite
value including 0 counts as set, the empty state ends on first
interaction, the clear button's contextual label and its disabled and
visibility rules (hidden for required and readonly sliders), and clearing
emits `NaN`, enters the unset state, and moves focus to the slider.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`translate.get` indexed `allTranslations[language][key]` directly, so a
`language` in the `Languages` type without translations threw a `TypeError`
and crashed the render. It now falls back to English (then the raw key) for
any unmapped language, and maps `nb` (Norwegian Bokmål) to the existing
Norwegian (`no`) translations.

Surfaced by the slider's new `language` prop via the composite example's
runtime test.

Refs #4162

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Kiarokh Kiarokh force-pushed the slider-value-unset branch from ac93ecd to 74e0ae9 Compare July 8, 2026 12:05

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/slider/slider.spec.tsx`:
- Around line 1-181: The slider spec in limel-slider lacks coverage for the new
language prop, so add tests in the existing “unset state” and “clear button”
areas of slider.spec.tsx that pass language: 'sv' through setup and assert
translated strings for rangeInput(root).getAttribute('aria-valuetext') and
clearButton(root).getAttribute('aria-label'). Use the current translation
sources to verify the expected Swedish text, and keep the assertions alongside
the existing English-language checks so the behavior of the language prop is
covered.

In `@src/components/slider/slider.tsx`:
- Around line 109-113: The JSDoc on the Slider component’s value prop describes
null and undefined as valid unset inputs, but the public type on value is only
number. Update the documentation in slider.tsx to match the actual TypeScript
contract by either removing null and undefined from the comment or widening the
value prop type if those inputs are intended; keep the guidance around NaN as
the supported programmatic unset path and ensure the comment on the value
property remains consistent with the Slider API.
- Around line 146-166: `percentageClass` is being set to `undefined` by the
unset-state flow, but it is still declared as a plain `string` in `slider.tsx`.
Update the `@State()` declaration for `percentageClass` to allow `undefined`,
and make sure the assignments in `enterUnsetState` and `setPercentageClass`
remain consistent with that type so strict null checks pass.

In `@src/global/translations.ts`:
- Around line 27-28: Update the fallback comment in translations logic so it no
longer uses nb as an unmapped example, since the Languages mapping now aliases
it. Adjust the wording near the fallback handling to reference a truly unmapped
browser locale such as en-US, or remove the example entirely, and keep the
explanation aligned with the existing translation fallback behavior in the
related mappings and lookup code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fb84f237-a839-495e-bb11-e6c8c9cbd042

📥 Commits

Reviewing files that changed from the base of the PR and between ad98145 and 74e0ae9.

⛔ Files ignored due to path filters (1)
  • etc/lime-elements.api.md is excluded by !etc/lime-elements.api.md
📒 Files selected for processing (15)
  • src/components/slider/examples/slider-basic.tsx
  • src/components/slider/examples/slider-unset.tsx
  • src/components/slider/partial-styles/_thumb.scss
  • src/components/slider/slider.scss
  • src/components/slider/slider.spec.tsx
  • src/components/slider/slider.tsx
  • src/global/translations.ts
  • src/translations/da.ts
  • src/translations/de.ts
  • src/translations/en.ts
  • src/translations/fi.ts
  • src/translations/fr.ts
  • src/translations/nl.ts
  • src/translations/no.ts
  • src/translations/sv.ts

Comment on lines +1 to +181
import { render, h } from '@stencil/vitest';
import { vi } from 'vitest';

describe('limel-slider — unset & clear', () => {
async function setup(props: Record<string, any> = {}) {
const { root, waitForChanges } = await render(
<limel-slider {...props}></limel-slider>
);
await waitForChanges();

return { root, waitForChanges };
}

const indicator = (root: any): HTMLElement =>
root.shadowRoot?.querySelector('.indicator');
const rangeInput = (root: any): HTMLInputElement =>
root.shadowRoot?.querySelector('input[type="range"]');
const clearButton = (root: any): HTMLButtonElement =>
root.shadowRoot?.querySelector('button.clear-button');

describe('unset state', () => {
it('treats a non-finite value as unset', async () => {
const { root } = await setup({
label: 'Priority',
value: Number.NaN,
valuemin: 1,
valuemax: 5,
});

expect(root.classList.contains('is-unset')).toBe(true);
expect(indicator(root).textContent).toBe('?');
expect(rangeInput(root).getAttribute('aria-valuetext')).toBe(
'Value not set'
);
});

it('rests the thumb at the midpoint while unset, not at the minimum', async () => {
const { root } = await setup({
value: Number.NaN,
valuemin: 0,
valuemax: 10,
});

// The native input sits at the midpoint (not the minimum), so both
// arrow-key directions are live and there is no jump on first use.
expect(rangeInput(root).value).toBe('5');
});

it('treats a finite value, including 0, as set', async () => {
const { root } = await setup({
value: 0,
valuemin: -10,
valuemax: 10,
});

expect(root.classList.contains('is-unset')).toBe(false);
expect(indicator(root).textContent).toBe('0');
expect(rangeInput(root).getAttribute('aria-valuetext')).toBeNull();
});

it('becomes unset again when the value is reset to a non-finite number', async () => {
const { root, waitForChanges } = await setup({
value: 3,
valuemin: 1,
valuemax: 5,
});
expect(root.classList.contains('is-unset')).toBe(false);

(root as any).value = Number.NaN;
await waitForChanges();

expect(root.classList.contains('is-unset')).toBe(true);
expect(indicator(root).textContent).toBe('?');
});

it('leaves the unset state as soon as the user changes the value', async () => {
const { root, waitForChanges } = await setup({
value: Number.NaN,
valuemin: 1,
valuemax: 5,
step: 1,
});
expect(root.classList.contains('is-unset')).toBe(true);

const input = rangeInput(root);
input.value = '3';
input.dispatchEvent(new Event('input'));
await waitForChanges();

expect(root.classList.contains('is-unset')).toBe(false);
expect(indicator(root).textContent).toBe('3');
});
});

describe('clear button', () => {
it('labels the button with the slider label, falling back to a generic label', async () => {
const withLabel = await setup({ label: 'Priority', value: 3 });
expect(clearButton(withLabel.root).getAttribute('aria-label')).toBe(
'Clear value of Priority'
);

const withoutLabel = await setup({ value: 3 });
expect(
clearButton(withoutLabel.root).getAttribute('aria-label')
).toBe('Clear value');
});

it('disables the clear button while unset, and enables it once set', async () => {
const unset = await setup({
label: 'Priority',
value: Number.NaN,
valuemin: 1,
valuemax: 5,
});
expect(clearButton(unset.root).hasAttribute('disabled')).toBe(true);

const set = await setup({
label: 'Priority',
value: 3,
valuemin: 1,
valuemax: 5,
});
expect(clearButton(set.root).hasAttribute('disabled')).toBe(false);
});

it('offers no clear button for required or readonly sliders', async () => {
const required = await setup({
label: 'Priority',
required: true,
value: 3,
});
expect(clearButton(required.root)).toBeNull();

const readonly = await setup({
label: 'Priority',
readonly: true,
value: 3,
});
expect(clearButton(readonly.root)).toBeNull();
});
});

describe('clearing', () => {
it('emits change with NaN and enters the unset state', async () => {
const { root, waitForChanges } = await setup({
label: 'Priority',
value: 3,
valuemin: 1,
valuemax: 5,
});

const details: number[] = [];
root.addEventListener('change', (event: CustomEvent<number>) => {
details.push(event.detail);
});

clearButton(root).click();
await waitForChanges();

expect(details).toHaveLength(1);
expect(Number.isNaN(details[0])).toBe(true);
expect(root.classList.contains('is-unset')).toBe(true);
});

it('moves focus to the slider input when cleared', async () => {
const { root, waitForChanges } = await setup({
label: 'Priority',
value: 3,
valuemin: 1,
valuemax: 5,
});

const focusSpy = vi.spyOn(rangeInput(root), 'focus');

clearButton(root).click();
await waitForChanges();

expect(focusSpy).toHaveBeenCalled();
});
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the language prop's effect on translations.

The suite verifies aria-valuetext and clear-button labels only with the default English language. There's no test confirming that setting language to a non-default value produces translated strings (e.g., aria-valuetext changes from "Value not set" to the Swedish equivalent when language: 'sv'). This is a gap in coverage for the new language prop.

🧪 Suggested test
it('uses translated aria-valuetext when language is set', async () => {
    const { root } = await setup({
        value: Number.NaN,
        valuemin: 1,
        valuemax: 5,
        language: 'sv',
    });

    // Replace with the actual Swedish translation from src/translations/sv.ts
    const expected = 'Värde inte angivet';
    expect(rangeInput(root).getAttribute('aria-valuetext')).toBe(expected);
});

it('uses translated clear-button label when language is set', async () => {
    const { root } = await setup({
        label: 'Priority',
        value: 3,
        language: 'sv',
    });

    // Replace with the actual Swedish translation
    const expected = 'Rensa värde av Priority';
    expect(clearButton(root).getAttribute('aria-label')).toBe(expected);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/slider/slider.spec.tsx` around lines 1 - 181, The slider spec
in limel-slider lacks coverage for the new language prop, so add tests in the
existing “unset state” and “clear button” areas of slider.spec.tsx that pass
language: 'sv' through setup and assert translated strings for
rangeInput(root).getAttribute('aria-valuetext') and
clearButton(root).getAttribute('aria-label'). Use the current translation
sources to verify the expected Swedish text, and keep the assertions alongside
the existing English-language checks so the behavior of the language prop is
covered.

Comment on lines +109 to 113
* The value of the input. When the value is `NaN`, `null`, `undefined`,
* or otherwise not a finite number, the slider is considered unset.
*/
@Prop({ reflect: true })
public value: number;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

JSDoc mentions null and undefined as valid value inputs, but the prop is typed number.

NaN is the only type-safe way for a TypeScript consumer to unset the slider programmatically. null and undefined would cause type errors in strict mode. Consider either removing null/undefined from the JSDoc (keeping NaN as the documented method) or widening the type to number | undefined.

📝 Suggested JSDoc clarification
  /**
-  * The value of the input. When the value is `NaN`, `null`, `undefined`,
-  * or otherwise not a finite number, the slider is considered unset.
+  * The value of the input. When the value is `NaN` or otherwise not a
+  * finite number, the slider is considered unset. (At runtime, `null`
+  * and `undefined` also trigger the unset state, but `NaN` is the
+  * type-safe way to unset the slider programmatically.)
   */
  `@Prop`({ reflect: true })
  public value: number;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* The value of the input. When the value is `NaN`, `null`, `undefined`,
* or otherwise not a finite number, the slider is considered unset.
*/
@Prop({ reflect: true })
public value: number;
/**
* The value of the input. When the value is `NaN` or otherwise not a
* finite number, the slider is considered unset. (At runtime, `null`
* and `undefined` also trigger the unset state, but `NaN` is the
* type-safe way to unset the slider programmatically.)
*/
`@Prop`({ reflect: true })
public value: number;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/slider/slider.tsx` around lines 109 - 113, The JSDoc on the
Slider component’s value prop describes null and undefined as valid unset
inputs, but the public type on value is only number. Update the documentation in
slider.tsx to match the actual TypeScript contract by either removing null and
undefined from the comment or widening the value prop type if those inputs are
intended; keep the guidance around NaN as the supported programmatic unset path
and ensure the comment on the value property remains consistent with the Slider
API.

Source: Path instructions

Comment on lines +146 to +166
/**
* `true` while the slider has no value set. Kept separate from the `value`
* prop so that the unset state survives the brief window during dragging
* where the value has not yet been emitted back to the consumer.
*/
@State()
private isUnset: boolean;

private labelId: string;
private helperTextId: string;
private clearButtonId: string;
private inputElement?: HTMLInputElement;

public constructor() {
this.labelId = createRandomString();
this.helperTextId = createRandomString();
this.clearButtonId = createRandomString();
}

public componentWillLoad() {
this.displayValue = this.multiplyByFactor(this.getValue());
this.setPercentageClass(this.getValue());
this.syncStateFromValue();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

percentageClass is typed string but assigned undefined in the new unset logic.

enterUnsetState (line 275) and setPercentageClass (line 429) both assign undefined to this.percentageClass, which is declared as @State() private percentageClass: string at line 141. This is a type error under strictNullChecks. Update the type to string | undefined.

🔧 Proposed type fix
  `@State`()
- private percentageClass: string;
+ private percentageClass: string | undefined;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* `true` while the slider has no value set. Kept separate from the `value`
* prop so that the unset state survives the brief window during dragging
* where the value has not yet been emitted back to the consumer.
*/
@State()
private isUnset: boolean;
private labelId: string;
private helperTextId: string;
private clearButtonId: string;
private inputElement?: HTMLInputElement;
public constructor() {
this.labelId = createRandomString();
this.helperTextId = createRandomString();
this.clearButtonId = createRandomString();
}
public componentWillLoad() {
this.displayValue = this.multiplyByFactor(this.getValue());
this.setPercentageClass(this.getValue());
this.syncStateFromValue();
`@State`()
private percentageClass: string | undefined;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/slider/slider.tsx` around lines 146 - 166, `percentageClass`
is being set to `undefined` by the unset-state flow, but it is still declared as
a plain `string` in `slider.tsx`. Update the `@State()` declaration for
`percentageClass` to allow `undefined`, and make sure the assignments in
`enterUnsetState` and `setPercentageClass` remain consistent with that type so
strict null checks pass.

Comment on lines +27 to +28
// (e.g. a value that is in the `Languages` type but unmapped, such as
// `nb`), so a component never throws on an unsupported language.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Comment inaccurately cites nb as an unmapped example.

The comment says the fallback handles "a value that is in the Languages type but unmapped, such as nb", but nb is now aliased on line 17, so the fallback won't trigger for it. Consider updating the example to reference a browser-specific locale (e.g., en-US) or removing the nb reference.

📝 Suggested comment update
-        // Fall back to English when the requested language has no translations
-        // (e.g. a value that is in the `Languages` type but unmapped, such as
-        // `nb`), so a component never throws on an unsupported language.
+        // Fall back to English when the requested language has no translations
+        // (e.g. a browser-specific locale like `en-US` that is not mapped),
+        // so a component never throws on an unsupported language.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// (e.g. a value that is in the `Languages` type but unmapped, such as
// `nb`), so a component never throws on an unsupported language.
// Fall back to English when the requested language has no translations
// (e.g. a browser-specific locale like `en-US` that is not mapped),
// so a component never throws on an unsupported language.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/global/translations.ts` around lines 27 - 28, Update the fallback comment
in translations logic so it no longer uses nb as an unmapped example, since the
Languages mapping now aliases it. Adjust the wording near the fallback handling
to reference a truly unmapped browser locale such as en-US, or remove the
example entirely, and keep the explanation aligned with the existing translation
fallback behavior in the related mappings and lookup code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

limel-slider: no way to unset / clear the value

2 participants