Skip to content

Commit

Permalink
Slider: Svelte and React version (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
fbasso authored Nov 9, 2023
1 parent c9ba24b commit a545430
Show file tree
Hide file tree
Showing 33 changed files with 993 additions and 512 deletions.
8 changes: 5 additions & 3 deletions angular/demo/src/app/samples/slider/default.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
imports: [AgnosUIAngularModule, ReactiveFormsModule, FormsModule],
template: `
<h2>Slider with form control</h2>
<au-component auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [formControl]="sliderControl"></au-component> Form control value:
<div auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [formControl]="sliderControl"></div>
Form control value:
{{ sliderControl.value }}
<hr />
<h2>Slider with value</h2>
<au-component auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [(auValues)]="sliderValues"></au-component> Value:
<div auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [(auValues)]="sliderValues"></div>
Value:
{{ sliderValues }}
<hr />
<h2>Disabled slider</h2>
<au-component auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [formControl]="disabledControl" [auReadonly]="readonlyToggle"></au-component>
<div auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [formControl]="disabledControl" [auReadonly]="readonlyToggle"></div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="disabled" [(ngModel)]="disabledToggle" (change)="handleDisabled()" />
Expand Down
2 changes: 1 addition & 1 deletion angular/demo/src/app/samples/slider/playground.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const undefinedConfig = getUndefinedValues(getSliderDefaultConfig());
standalone: true,
imports: [AgnosUIAngularModule],
providers: provideHashConfig('slider'),
template: `<au-component auSlider #widget></au-component>`,
template: `<div auSlider #widget></div>`,
})
export default class PlaygroundComponent {
@ViewChild('widget') widget: SliderComponent;
Expand Down
4 changes: 2 additions & 2 deletions angular/demo/src/app/samples/slider/range.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
imports: [AgnosUIAngularModule, ReactiveFormsModule, FormsModule],
template: `
<h2>Slider with form control</h2>
<au-component auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [formControl]="sliderControl"></au-component>
<div auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [formControl]="sliderControl"></div>
Form control values: {{ sliderControl.value?.join(', ') }}
<hr />
<h2>Slider with values</h2>
<au-component auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [(auValues)]="sliderValues"></au-component>
<div auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [(auValues)]="sliderValues"></div>
Values: {{ sliderValues.join(', ') }}
`,
})
Expand Down
9 changes: 4 additions & 5 deletions angular/demo/src/app/samples/slider/vertical.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
standalone: true,
imports: [AgnosUIAngularModule, ReactiveFormsModule, FormsModule],
template: `
<h2>Vertical slider</h2>
<div class="d-flex" style="height: 350px">
<div class="col-6" style="height: 300px">
<au-component auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [auVertical]="true" [formControl]="sliderControl"></au-component>
Form control values: {{ sliderControl.value?.join(', ') }}
<div auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [auVertical]="true" [formControl]="sliderControl" auClassName="my-0"></div>
<div class="mt-3">Form control values: {{ sliderControl.value?.join(', ') }}</div>
</div>
<div class="col-6" style="height: 300px">
<au-component auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [auVertical]="true" [formControl]="sliderControlRange"></au-component>
From control value: {{ sliderControlRange.value }}
<div auSlider [auMin]="0" [auMax]="100" [auStepSize]="1" [auVertical]="true" [formControl]="sliderControlRange" auClassName="my-0"></div>
<div class="mt-3">From control value: {{ sliderControlRange.value?.join(', ') }}</div>
</div>
</div>
`,
Expand Down
15 changes: 8 additions & 7 deletions angular/lib/src/lib/slider/slider.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import {take} from 'rxjs';
providers: [{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SliderComponent), multi: true}],
imports: [NgIf, NgFor, UseDirective],
host: {
class: 'au-slider',
'[class]': 'state().vertical ? "au-slider-vertical" : "au-slider-horizontal"',
class: `au-slider`,
'[class]': '(state().vertical ? "au-slider-vertical" : "au-slider-horizontal") + " " + state().className',
'[class.disabled]': 'state().disabled',
'(blur)': 'handleBlur()',
},
Expand All @@ -39,21 +39,21 @@ import {take} from 'rxjs';
></div>
<div
[class]="state().vertical ? 'au-slider-label-vertical au-slider-label-vertical-min' : 'au-slider-label au-slider-label-min'"
[style.visibility]="state().minValueLabelDisplay"
[style.visibility]="state().minValueLabelDisplay ? 'visible' : 'hidden'"
[auUse]="widget.directives.minLabelDirective"
>
{{ state().min }}
</div>
<div
[class]="state().vertical ? 'au-slider-label-vertical au-slider-label-vertical-max' : 'au-slider-label au-slider-label-max'"
[style.visibility]="state().maxValueLabelDisplay"
[style.visibility]="state().maxValueLabelDisplay ? 'visible' : 'hidden'"
[auUse]="widget.directives.maxLabelDirective"
>
{{ state().max }}
</div>
<div
[class]="state().vertical ? 'au-slider-label-vertical au-slider-label-vertical-now' : 'au-slider-label au-slider-label-now'"
[style.visibility]="state().combinedLabelDisplay"
[style.visibility]="state().combinedLabelDisplay ? 'visible' : 'hidden'"
[style.left.%]="state().combinedLabelPositionLeft"
[style.top.%]="state().combinedLabelPositionTop"
>
Expand All @@ -69,7 +69,8 @@ import {take} from 'rxjs';
[attr.aria-disabled]="state().disabled ? true : null"
[attr.aria-valuenow]="item.value"
[attr.aria-valuetext]="item.value"
[attr.disabled]="state().disabled ? true : null"
[attr.aria-orientation]="state().vertical ? 'vertical' : null"
[disabled]="state().disabled"
[class]="state().vertical ? 'au-slider-handle-vertical' : 'au-slider-handle-horizontal'"
[style.left.%]="state().handleDisplayOptions[item.id].left"
[style.top.%]="state().handleDisplayOptions[item.id].top"
Expand All @@ -82,7 +83,7 @@ import {take} from 'rxjs';
[class]="state().vertical ? 'au-slider-label-vertical au-slider-label-vertical-now' : 'au-slider-label au-slider-label-now'"
[style.left.%]="state().handleDisplayOptions[i].left"
[style.top.%]="state().handleDisplayOptions[i].top"
[style.visibility]="state().combinedLabelDisplay === 'visible' ? 'hidden' : 'visible'"
[style.visibility]="state().combinedLabelDisplay ? 'hidden' : 'visible'"
>
{{ state().values[i] }}
</div>
Expand Down
9 changes: 2 additions & 7 deletions core/lib/rating.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {computed, writable} from '@amadeus-it-group/tansu';
import type {ConfigValidator, PropsConfig} from './services';
import {INVALID_VALUE, bindableDerived, stateStores, writablesForProps} from './services';
import {isNumber} from './services/checks';
import {clamp, isNumber} from './services/checks';
import {typeBoolean, typeFunction, typeNumber, typeString} from './services/writables';
import type {SlotContent, Widget} from './types';
import type {WidgetsCommonPropsAndState} from './commonProps';
Expand Down Expand Up @@ -150,11 +150,6 @@ export interface RatingActions {

export type RatingWidget = Widget<RatingProps, RatingState, object, RatingActions>;

// TODO use getValueInRange
function adjustRating(rating: number, maxRating: number): number {
return Math.max(Math.min(rating, maxRating), 0);
}

const noop = () => {};

const defaultConfig: RatingProps = {
Expand Down Expand Up @@ -227,7 +222,7 @@ export function createRating(config?: PropsConfig<RatingProps>): RatingWidget {
// clean inputs adjustment to valid range
const tabindex$ = computed(() => (disabled$() ? -1 : _dirtyTabindex$()));

const rating$ = bindableDerived(onRatingChange$, [_dirtyRating$, maxRating$], ([dirtyRating, maxRating]) => adjustRating(dirtyRating, maxRating));
const rating$ = bindableDerived(onRatingChange$, [_dirtyRating$, maxRating$], ([dirtyRating, maxRating]) => clamp(dirtyRating, maxRating));

// internal inputs
const _hoveredRating$ = writable(0);
Expand Down
14 changes: 12 additions & 2 deletions core/lib/services/writables.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {writable} from '@amadeus-it-group/tansu';
import type {SpyInstance} from 'vitest';
import {beforeEach, describe, expect, test, vi} from 'vitest';
import {writableWithDefault} from './stores';
import {typeArray} from './writables';
import {INVALID_VALUE, writableWithDefault} from './stores';
import {typeArray, typeNumberInRangeFactory} from './writables';

describe(`Writables service`, () => {
const equal = typeArray.equal!;
Expand Down Expand Up @@ -35,4 +35,14 @@ describe(`Writables service`, () => {
expect(equal([15], [15, 15])).toBe(false);
expect(equal([15, 15], [15])).toBe(false);
});

test(`typeNumberInRangeFactory should normalized the value in a range`, () => {
const normalizeValueFn = typeNumberInRangeFactory(1, 3).normalizeValue!;
expect(normalizeValueFn(0)).toBe(1);
expect(normalizeValueFn(1)).toBe(1);
expect(normalizeValueFn(2)).toBe(2);
expect(normalizeValueFn(3)).toBe(3);
expect(normalizeValueFn(4)).toBe(3);
expect(normalizeValueFn(+'a')).toBe(INVALID_VALUE);
});
});
22 changes: 20 additions & 2 deletions core/lib/services/writables.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {isArray, isBoolean, isFunction, isNumber, isString} from './checks';
import {clamp, isArray, isBoolean, isFunction, isNumber, isString} from './checks';
import type {WritableWithDefaultOptions} from './stores';
import {INVALID_VALUE} from './stores';

Expand All @@ -7,10 +7,28 @@ export const testToNormalizeValue =
(value: T) =>
filter(value) ? value : INVALID_VALUE;

const numberNormalizeFn = testToNormalizeValue(isNumber);

export const typeNumber: WritableWithDefaultOptions<number> = {
normalizeValue: testToNormalizeValue(isNumber),
normalizeValue: numberNormalizeFn,
};

/**
* A factory function that creates a type guard function to check and rectify a value is within a specified range.
*
* @param min - The minimum value allowed.
* @param max - The maximum value allowed.
* @returns A type guard function that returns the clamp value if the value is a value number, and INVALID_VALUE otherwise.
*/
export function typeNumberInRangeFactory(min: number, max: number) {
return <WritableWithDefaultOptions<number>>{
normalizeValue: (value) => {
const normalizedNumber = numberNormalizeFn(value);
return normalizedNumber === INVALID_VALUE ? INVALID_VALUE : clamp(normalizedNumber, max, min);
},
};
}

export const typeBoolean: WritableWithDefaultOptions<boolean> = {
normalizeValue: testToNormalizeValue(isBoolean),
};
Expand Down
Loading

0 comments on commit a545430

Please sign in to comment.