Skip to content

Commit

Permalink
fix(slider): support non-supported units in "Intl.numberFormat"
Browse files Browse the repository at this point in the history
  • Loading branch information
Westbrook committed Sep 13, 2021
1 parent 1fe3339 commit ac32355
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 7 deletions.
19 changes: 19 additions & 0 deletions packages/slider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,25 @@ for a multi-handle slider, you can format the combined value label for all
handles by passing a formatting function to the `getAriaValueText` property
on the parent `sp-slider`.

### Units not included in `Intl.NumberFormatOptions`

While `Intl.NumberFormatOptions` does support a [wide range of units](https://tc39.es/proposal-unified-intl-numberformat/section6/locales-currencies-tz_proposed_out.html#sec-issanctionedsimpleunitidentifier), it is possible to encounter units (e.g. the graphics units of `pixel`, `pixels`, `points`, etc.) that are not supported therein. When this occurs, an `<sp-slider>` element will attempt to polyfill support for this unit. See the following example delivering `{ style: "unit", unit: "px" }` below:

```html
<sp-slider
style="width: 200px"
value="500"
format-options='{
"style": "unit",
"unit": "px"
}'
>
Document width in pixels
</sp-slider>
```

Note: the polyfilling done here is very simplistic and is triggered by supplying options that would otherwise cause the `Intl.NumberFormat()` call to throw an error. Once the unsupporting unit of `px` causes the construction of the object to throw, a back up formatter/parser pair will be created without the supplied unit data. When the `style` is set to `unit`, the `unit` value of will be adopted as the _static_ unit display. This means that neither pluralization or translation will be handled within the `<sp-number-field>` element itself. If pluralization or translation is important to the delivered interface, please be sure to handle passing those strings into to element via the `formatOptions` property reactively to the value of the element or locale of that page in question.

### Label Visibility

Be default an `<sp-slider>` element has both a "text" label and a "value" label. Either or both of these can be surpressed visually as needed by your application UI. This delivery is controlled by the `label-visibility` attribute (or `labelVisibility` property) which accepts `text`, `value`, or `none` as values.
Expand Down
6 changes: 3 additions & 3 deletions packages/slider/src/Slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ export class Slider extends ObserveSlotText(SliderHandle, '') {
) => {
const valueArray = [...values.values()];
if (valueArray.length === 2)
return `${valueArray[0]} - ${valueArray[1]}`;
return valueArray.join(', ');
return `${valueArray[0]}${this._forcedUnit} - ${valueArray[1]}${this._forcedUnit}`;
return valueArray.join(`${this._forcedUnit}, `) + this._forcedUnit;
};

public get ariaValueText(): string {
if (!this.getAriaValueText) {
return `${this.value}`;
return `${this.value}${this._forcedUnit}`;
}
return this.getAriaValueText(this.handleController.formattedValues);
}
Expand Down
31 changes: 27 additions & 4 deletions packages/slider/src/SliderHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export class SliderHandle extends Focusable {
return this.handleController?.inputForHandle(this) ?? this;
}

_forcedUnit = '';

@property({ type: Number })
value = 10;

Expand Down Expand Up @@ -161,12 +163,33 @@ export class SliderHandle extends Focusable {
!this._numberFormatCache ||
this.resolvedLanguage !== this._numberFormatCache.language
) {
this._numberFormatCache = {
language: this.resolvedLanguage,
numberFormat: new NumberFormatter(
let numberFormatter: NumberFormatter;
try {
numberFormatter = new NumberFormatter(
this.resolvedLanguage,
this.formatOptions
),
);
this._forcedUnit = '';
// numberFormatter.format(1);
} catch (error) {
const {
style,
unit,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
unitDisplay,
...formatOptionsNoUnit
} = this.formatOptions || {};
if (style === 'unit') {
this._forcedUnit = unit as string;
}
numberFormatter = new NumberFormatter(
this.resolvedLanguage,
formatOptionsNoUnit
);
}
this._numberFormatCache = {
language: this.resolvedLanguage,
numberFormat: numberFormatter,
};
}
/* c8 ignore next */
Expand Down
109 changes: 109 additions & 0 deletions packages/slider/stories/slider.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,34 @@ noVisibleLabels.args = {
labelVisibility: 'none',
};

export const px = (args: StoryArgs): TemplateResult => {
const handleEvent = (event: Event): void => {
const target = event.target as Slider;
if (target.value != null) {
action(event.type)(target.value.toString());
}
};
return html`
<div style="width: 500px; margin: 12px 20px;">
<sp-slider
max="360"
min="0"
value="90"
step="1"
@input=${handleEvent}
@change=${handleEvent}
.formatOptions=${{
style: 'unit',
unit: 'px',
}}
...=${spreadProps(args)}
>
Angle
</sp-slider>
</div>
`;
};

class NumberFieldDefined extends HTMLElement {
constructor() {
super();
Expand Down Expand Up @@ -471,6 +499,87 @@ TwoHandles.args = {
tickStep: 10,
};

export const TwoHandlesPt = (args: StoryArgs): TemplateResult => {
const handleEvent = (event: Event): void => {
const target = event.target as SliderHandle;
if (target.value != null) {
if (typeof target.value === 'object') {
action(event.type)(target.value);
} else {
action(event.type)(`${target.name}: ${target.value}`);
}
}
};
return html`
<div style="width: 500px; margin: 12px 20px;">
<sp-slider
value="5"
step="1"
min="0"
max="255"
@input=${handleEvent}
@change=${handleEvent}
.formatOptions=${{
style: 'unit',
unit: 'pt',
}}
...=${spreadProps(args)}
>
Output Levels
<sp-slider-handle
slot="handle"
name="min"
value="5"
></sp-slider-handle>
<sp-slider-handle
slot="handle"
name="max"
value="250"
></sp-slider-handle>
</sp-slider>
</div>
`;
};
TwoHandlesPt.args = {
variant: 'range',
tickStep: 10,
};

export const ThreeHandlesPc = (args: StoryArgs): TemplateResult => {
const handleEvent = (event: Event): void => {
const target = event.target as SliderHandle;
if (target.value != null) {
if (typeof target.value === 'object') {
action(event.type)(target.value);
} else {
action(event.type)(`${target.name}: ${target.value}`);
}
}
};
return html`
<div style="width: 500px; margin: 12px 20px;">
<sp-slider
value="5"
step="1"
min="0"
max="255"
@input=${handleEvent}
@change=${handleEvent}
.formatOptions=${{
style: 'unit',
unit: 'pc',
}}
...=${spreadProps(args)}
>
Output Levels
<sp-slider-handle slot="handle" value="5"></sp-slider-handle>
<sp-slider-handle slot="handle" value="133"></sp-slider-handle>
<sp-slider-handle slot="handle" value="250"></sp-slider-handle>
</sp-slider>
</div>
`;
};

export const ThreeHandlesOrdered = (args: StoryArgs): TemplateResult => {
const handleEvent = (event: Event): void => {
const target = event.target as SliderHandle;
Expand Down

0 comments on commit ac32355

Please sign in to comment.