diff --git a/packages/survey-angular-ui/src/components/slider/slider-label-item.component.html b/packages/survey-angular-ui/src/components/slider/slider-label-item.component.html index d1dbfec480..b516fe9c85 100644 --- a/packages/survey-angular-ui/src/components/slider/slider-label-item.component.html +++ b/packages/survey-angular-ui/src/components/slider/slider-label-item.component.html @@ -3,6 +3,10 @@ [style]="{ left: model.getPercent(item.value) + '%' }" (pointerup)="model.handleLabelPointerUp($any($event), item.value)">
-
+
+
+
{{ item.value }}
+
+
\ No newline at end of file diff --git a/packages/survey-angular-ui/src/components/slider/slider-label-item.component.ts b/packages/survey-angular-ui/src/components/slider/slider-label-item.component.ts index 81eaaf2a30..5b4c45f628 100644 --- a/packages/survey-angular-ui/src/components/slider/slider-label-item.component.ts +++ b/packages/survey-angular-ui/src/components/slider/slider-label-item.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from "@angular/core"; -import { ItemValue, QuestionSliderModel } from "survey-core"; +import { QuestionSliderModel, SliderLabelItemValue } from "survey-core"; import { BaseAngular } from "../../base-angular"; import { AngularComponentFactory } from "../../component-factory"; @@ -10,7 +10,7 @@ import { AngularComponentFactory } from "../../component-factory"; export class SliderLabelItemComponent extends BaseAngular { @Input() model!: QuestionSliderModel; - @Input() item!: ItemValue; + @Input() item!: SliderLabelItemValue; // onClick(event: any): void { // this.model.setValueFromClick(event.target.value); diff --git a/packages/survey-core/entries/chunks/model.ts b/packages/survey-core/entries/chunks/model.ts index d6626b74cd..204f270395 100644 --- a/packages/survey-core/entries/chunks/model.ts +++ b/packages/survey-core/entries/chunks/model.ts @@ -218,7 +218,7 @@ export { QuestionFileModelBase, QuestionFileModel, QuestionFilePage } from "../. export { QuestionHtmlModel } from "../../src/question_html"; export { QuestionRadiogroupModel } from "../../src/question_radiogroup"; export { QuestionRatingModel, RenderedRatingItem } from "../../src/question_rating"; -export { QuestionSliderModel } from "../../src/question_slider"; +export { QuestionSliderModel, SliderLabelItemValue } from "../../src/question_slider"; export { QuestionExpressionModel } from "../../src/question_expression"; export { QuestionTextBase, CharacterCounter } from "../../src/question_textbase"; export { QuestionTextModel } from "../../src/question_text"; diff --git a/packages/survey-core/src/default-theme/blocks/sd-slider.scss b/packages/survey-core/src/default-theme/blocks/sd-slider.scss index d25db0325e..d0e4499fe5 100644 --- a/packages/survey-core/src/default-theme/blocks/sd-slider.scss +++ b/packages/survey-core/src/default-theme/blocks/sd-slider.scss @@ -48,10 +48,6 @@ // } } -.sd-slider--tooltips-always-mode { - margin-top: var(--lbr-slider-margin-top-tooltip, calcSize(5)); -} - .sd-slider-container { position: relative; min-height: var(--sjs-postcss-fix-slider-thumb-height); @@ -265,6 +261,7 @@ cursor: pointer; color: $foreground; position: absolute; + top: 0; width: var(--sjs-postcss-fix-slider-thumb-width); display: flex; flex-direction: column; @@ -273,12 +270,12 @@ margin-left: calc(var(--sjs-postcss-fix-slider-thumb-width) / -2); } -.sd-slider__label--long:first-child { - align-items: flex-start; +.sd-slider__label--long:first-child .sd-slider__label-text-container { + align-self: flex-start; } -.sd-slider__label--long:last-child { - align-items: flex-end; +.sd-slider__label--long:last-child .sd-slider__label-text-container { + align-self: flex-end; } .sd-slider__label-tick { @@ -300,6 +297,10 @@ line-height: var(--lbr-font-default-line-height, calcSize(3)); } +.sd-slider__label-text--secondary { + color: var(--lbr-slider-label-text-color-secondary, $font-questiondescription-color); +} + input[type="range"].sd-slider__input { position: absolute; pointer-events: none; @@ -491,6 +492,10 @@ input[type="range"][name="range-input"].sd-slider__input::-moz-range-thumb { .sd-slider__label-tick { background: var(--lbr-slider-label-tick-color, $border-light); } + + .sd-slider__label-text--secondary { + color: var(--lbr-slider-label-text-color, $font-editorfont-color); + } } .sd-question--preview { @@ -501,7 +506,7 @@ input[type="range"][name="range-input"].sd-slider__input::-moz-range-thumb { } } - &.sd-slider--negative-scale { + &.sd-slider--negative-scale-mode { } } @@ -563,7 +568,7 @@ input[type="range"][name="range-input"].sd-slider__input::-moz-range-thumb { } } - .sd-slider--negative-scale { + .sd-slider--negative-scale-mode { .sd-slider__inverse-track--left { &::before { background: var(--lbr-slider-path-color-preview, $border-light); @@ -574,6 +579,10 @@ input[type="range"][name="range-input"].sd-slider__input::-moz-range-thumb { .sd-slider__label-tick { background: var(--lbr-slider-label-tick-color-preview, $foreground); } + + .sd-slider__label-text--secondary { + color: var(--lbr-slider-label-text-color, $font-editorfont-color); + } } .sd-question--error { @@ -644,7 +653,7 @@ input[type="range"][name="range-input"].sd-slider__input::-moz-range-thumb { } } -.sd-slider--negative-scale { +.sd-slider--negative-scale-mode { .sd-slider__range-track { &::before { display: none; @@ -669,6 +678,23 @@ input[type="range"][name="range-input"].sd-slider__input::-moz-range-thumb { } } +.sd-slider--tooltips-always-mode { + margin-top: var(--lbr-slider-margin-top-tooltip, calcSize(5)); +} + +.sd-slider--labels-show-value-text-mode { + margin-bottom: calcSize(3); + + .sd-slider__label--long:first-child .sd-slider__label-text-container .sd-slider__label-text:first-child { + text-align: start; + margin-left: 11px; + } + + .sd-slider__label--long:last-child .sd-slider__label-text-container .sd-slider__label-text:first-child { + text-align: end; + } +} + [dir="rtl"], [style*="direction:rtl"], [style*="direction: rtl"] { diff --git a/packages/survey-core/src/defaultCss/defaultCss.ts b/packages/survey-core/src/defaultCss/defaultCss.ts index c717b15c73..b4b9bbfe66 100644 --- a/packages/survey-core/src/defaultCss/defaultCss.ts +++ b/packages/survey-core/src/defaultCss/defaultCss.ts @@ -683,10 +683,11 @@ export var defaultCss = { slider: { root: "sd-slider", rootSingleMode: "sd-slider--single", - rootNegativeScaleMode: "sd-slider--negative-scale", + rootNegativeScaleMode: "sd-slider--negative-scale-mode", rootDesignMode: "sd-slider--design-mode", rootAnimatedThumbMode: "sd-slider--animated-thumb-mode", rootTooltipsAlwaysMode: "sd-slider--tooltips-always-mode", + rootLabelsShowValueTextMode: "sd-slider--labels-show-value-text-mode", visualContainer: "sd-slider-container", visualContainerSlider: "sd-slider-container__slider", rangeTrack: "sd-slider__track sd-slider__range-track", @@ -706,7 +707,9 @@ export var defaultCss = { label: "sd-slider__label", labelLongMod: "sd-slider__label--long", labelTick: "sd-slider__label-tick", + labelTextContainer: "sd-slider__label-text-container", labelText: "sd-slider__label-text", + labelTextSecondaryMode: "sd-slider__label-text sd-slider__label-text--secondary", clearButton: "", }, comment: { diff --git a/packages/survey-core/src/question_slider.ts b/packages/survey-core/src/question_slider.ts index 6f02d498b3..c52492e333 100644 --- a/packages/survey-core/src/question_slider.ts +++ b/packages/survey-core/src/question_slider.ts @@ -29,6 +29,12 @@ export class SliderLabelItemValue extends ItemValue { } return this.value || 0; } + public get showValue(): boolean { + return this.getPropertyValue("showValue", false); + } + public set showValue(val: boolean) { + this.setPropertyValue("showValue", val); + } } /** @@ -193,7 +199,10 @@ export class QuestionSliderModel extends Question implements ISliderLabelItemOwn * - `text`: `string`\ * The label text to display. * - * Numbers and objects can be combined in the same array. For instance, the following slider configuration specifies textual labels for extreme scale points and adds numeric labels between them. + * - `showValue`: `boolean`\ + * Specifies whether to display the numeric value alongside the label text. Default value: `false`. + * + * Numbers and objects can be combined in the same array. For instance, the following slider configuration adds textual labels for the minimum and maximum scale values and numeric labels for intermediate points. The extreme labels also display their corresponding values. * * ```js * const surveyJson = { @@ -202,12 +211,12 @@ export class QuestionSliderModel extends Question implements ISliderLabelItemOwn * "type": "slider", * // ... * "customLabels": [ - * { value: 0, text: "Lowest" }, + * { "value": 0, "text": "Lowest", "showValue": true }, * 20, * 40 * 60 * 80, - * { value: 100, text: "Highest" }, + * { "value": 100, "text": "Highest", "showValue": true } * ] * } * ] @@ -219,10 +228,10 @@ export class QuestionSliderModel extends Question implements ISliderLabelItemOwn * @see labelCount * @see labelFormat */ - public get customLabels(): ItemValue[] { + public get customLabels(): SliderLabelItemValue[] { return this.getPropertyValue("customLabels"); } - public set customLabels(val: ItemValue[]) { + public set customLabels(val: SliderLabelItemValue[]) { this.setPropertyValue("customLabels", val); } @property({ defaultValue: true }) allowDragRange: boolean; @@ -277,6 +286,7 @@ export class QuestionSliderModel extends Question implements ISliderLabelItemOwn .append(this.cssClasses.rootDesignMode, !!this.isDesignMode) .append(this.cssClasses.rootAnimatedThumbMode, !!this.animatedThumb) .append(this.cssClasses.rootTooltipsAlwaysMode, this.tooltipVisibility === "always") + .append(this.cssClasses.rootLabelsShowValueTextMode, this.isLabelsShowValueText) .toString(); } @@ -889,6 +899,10 @@ export class QuestionSliderModel extends Question implements ISliderLabelItemOwn private isAllowToChange():boolean { return !this.isReadOnly && !this.isDisabledAttr && !this.isPreviewStyle && !this.isDisabledStyle; } + + private get isLabelsShowValueText(): boolean { + return !!this.customLabels.find(l => l.showValue); + } } function getCorrectMinMax(min: any, max: any, isMax: boolean, step: number): any { @@ -902,7 +916,8 @@ Serializer.addClass( [ { name: "!value:number" }, { name: "visibleIf", visible: false }, - { name: "enableIf", visible: false } + { name: "enableIf", visible: false }, + { name: "showValue:boolean", locationInTable: "detail", default: false } ], (value: any) => new SliderLabelItemValue(value), "itemvalue" diff --git a/packages/survey-core/tests/question_slider_tests.ts b/packages/survey-core/tests/question_slider_tests.ts index 45b24a61a3..c62e241956 100644 --- a/packages/survey-core/tests/question_slider_tests.ts +++ b/packages/survey-core/tests/question_slider_tests.ts @@ -804,7 +804,7 @@ QUnit.test("check if customLabels produces correct renderedLabels", (assert) => let q1 = new QuestionSliderModel("q1"); q1.max = 1000; q1.autoGenerate = false; - q1.customLabels = [new ItemValue(500, "middle")]; + q1.customLabels = [new SliderLabelItemValue(500, "middle")]; assert.deepEqual(q1.renderedLabels[0].text, "middle", "text is correct"); assert.deepEqual(q1.renderedLabels[0].value, 500, "value is correct"); }); diff --git a/packages/survey-react-ui/src/components/slider/slider-label-item.tsx b/packages/survey-react-ui/src/components/slider/slider-label-item.tsx index 81ad1d83fc..bdfcf8492c 100644 --- a/packages/survey-react-ui/src/components/slider/slider-label-item.tsx +++ b/packages/survey-react-ui/src/components/slider/slider-label-item.tsx @@ -20,11 +20,24 @@ export class SliderLabelItem extends SurveyElementBase { protected renderElement(): React.JSX.Element { const { cssClasses, handleLabelPointerUp, getLabelCss, getPercent } = this.question; const { value, locText } = this.item; + let labelText = null; + let labelTextSecondary = null; + if (this.item.showValue) { + labelText =
{this.item.value}
; + labelTextSecondary =
+ {this.renderLocString(locText)} +
; + } else { + labelText =
+ {this.renderLocString(locText)} +
; + } return
{ handleLabelPointerUp(e.nativeEvent, value); } }>
-
- {this.renderLocString(locText)} +
+ {labelText} + {labelTextSecondary}
; } diff --git a/packages/survey-vue3-ui/src/components/slider/SliderLabelItem.vue b/packages/survey-vue3-ui/src/components/slider/SliderLabelItem.vue index 2d24489f68..0c8e625891 100644 --- a/packages/survey-vue3-ui/src/components/slider/SliderLabelItem.vue +++ b/packages/survey-vue3-ui/src/components/slider/SliderLabelItem.vue @@ -2,11 +2,22 @@
-
- +
+
+ +
+
+ {{item.value}} +
+
+ +
diff --git a/packages/survey-vue3-ui/src/components/slider/slider.ts b/packages/survey-vue3-ui/src/components/slider/slider.ts index 7a1c07768a..893aa3aa41 100644 --- a/packages/survey-vue3-ui/src/components/slider/slider.ts +++ b/packages/survey-vue3-ui/src/components/slider/slider.ts @@ -1,6 +1,6 @@ -import type { QuestionSliderModel, ItemValue } from "survey-core"; +import type { QuestionSliderModel, SliderLabelItemValue } from "survey-core"; export interface ISliderItemProps { question: QuestionSliderModel; - item: ItemValue; + item: SliderLabelItemValue; } diff --git a/screenshots/slider.spec.ts b/screenshots/slider.spec.ts index 72847006e4..7d6e62d8f6 100644 --- a/screenshots/slider.spec.ts +++ b/screenshots/slider.spec.ts @@ -220,5 +220,45 @@ frameworks.forEach(framework => { await compareScreenshot(page, ".sd-question", "slider-single-tooltips-always.png"); }); + test("Slider: Custom Labels", async ({ page }) => { + const json = { + "elements": [ + { + "type": "slider", + "name": "q1", + "customLabels": [ + 0, + 20, + 40, + 60, + 80, + 100, + ] + } + ] + }; + const question = new Question(page, "q1"); + await page.setViewportSize({ width: 1920, height: 1080 }); + await initSurvey(page, framework, json); + + await compareScreenshot(page, ".sd-question", "slider-custom-labels.png"); + + await question.setPropertyValue("customLabels", [ + { value: 0, text: "Lowest", showValue: true }, + 20, + 40, + { value: 60, text: "Middle" }, + 80, + { value: 100, text: " Highest", showValue: true }, + ]); + await compareScreenshot(page, ".sd-question", "slider-custom-labels-secondary.png"); + + await question.setPropertyValue("readOnly", true); + await compareScreenshot(page, ".sd-question", "slider-custom-labels-secondary--read-only.png"); + + await new Survey(page).showPreview(); + await compareScreenshot(page, ".sd-question", "slider-custom-labels-secondary--preview.png"); + }); + }); }); \ No newline at end of file diff --git a/screenshots/slider.spec.ts-snapshots/slider-custom-labels-secondary--preview.png b/screenshots/slider.spec.ts-snapshots/slider-custom-labels-secondary--preview.png new file mode 100644 index 0000000000..e15e0a0146 Binary files /dev/null and b/screenshots/slider.spec.ts-snapshots/slider-custom-labels-secondary--preview.png differ diff --git a/screenshots/slider.spec.ts-snapshots/slider-custom-labels-secondary--read-only.png b/screenshots/slider.spec.ts-snapshots/slider-custom-labels-secondary--read-only.png new file mode 100644 index 0000000000..80ba89db1d Binary files /dev/null and b/screenshots/slider.spec.ts-snapshots/slider-custom-labels-secondary--read-only.png differ diff --git a/screenshots/slider.spec.ts-snapshots/slider-custom-labels-secondary.png b/screenshots/slider.spec.ts-snapshots/slider-custom-labels-secondary.png new file mode 100644 index 0000000000..bfcc27e50a Binary files /dev/null and b/screenshots/slider.spec.ts-snapshots/slider-custom-labels-secondary.png differ diff --git a/screenshots/slider.spec.ts-snapshots/slider-custom-labels.png b/screenshots/slider.spec.ts-snapshots/slider-custom-labels.png new file mode 100644 index 0000000000..1163b366d3 Binary files /dev/null and b/screenshots/slider.spec.ts-snapshots/slider-custom-labels.png differ diff --git a/tests/markup/etalon_slider.ts b/tests/markup/etalon_slider.ts index e284433949..2bd4f3d597 100644 --- a/tests/markup/etalon_slider.ts +++ b/tests/markup/etalon_slider.ts @@ -92,6 +92,28 @@ registerMarkupTests( }, snapshot: "slider-read-only-range-mode", }, + { + name: "Slider: Custom Labels", + json: { + questions: [ + { + name: "name", + "type": "slider", + sliderType: "single", + titleLocation: "hidden", + "customLabels": [ + { value: 0, text: "Lowest", showValue: true }, + 20, + 40, + { value: 60, text: "Middle" }, + 80, + { value: 100, text: " Highest", showValue: true }, + ] + } + ] + }, + snapshot: "slider-custom-labels", + }, // { // name: "Slider: Error State: Single Mode", // json: { diff --git a/tests/markup/snapshots/slider-custom-labels.snap.html b/tests/markup/snapshots/slider-custom-labels.snap.html new file mode 100644 index 0000000000..58e153abdc --- /dev/null +++ b/tests/markup/snapshots/slider-custom-labels.snap.html @@ -0,0 +1,84 @@ +
+
+
+
+
+
+
+
+
+ +
+
+
+
0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
0
+
+ Lowest +
+
+
+
+
+
+
+
+ 20 +
+
+
+
+
+
+
+
+ 40 +
+
+
+
+
+
+
+
+ Middle +
+
+
+
+
+
+
+
+ 80 +
+
+
+
+
+
+
+
100
+
+ Highest +
+
+
+
+
+
\ No newline at end of file diff --git a/tests/markup/snapshots/slider-default-value-range-mode.snap.html b/tests/markup/snapshots/slider-default-value-range-mode.snap.html index ae37ab2014..fb888c480e 100644 --- a/tests/markup/snapshots/slider-default-value-range-mode.snap.html +++ b/tests/markup/snapshots/slider-default-value-range-mode.snap.html @@ -39,43 +39,55 @@
-
- 0 +
+
+ 0 +
-
- 20 +
+
+ 20 +
-
- 40 +
+
+ 40 +
-
- 60 +
+
+ 60 +
-
- 80 +
+
+ 80 +
-
- 100 +
+
+ 100 +
diff --git a/tests/markup/snapshots/slider-default-value-single-mode.snap.html b/tests/markup/snapshots/slider-default-value-single-mode.snap.html index b03261b567..6f3f8f5f52 100644 --- a/tests/markup/snapshots/slider-default-value-single-mode.snap.html +++ b/tests/markup/snapshots/slider-default-value-single-mode.snap.html @@ -26,43 +26,55 @@
-
- 0 +
+
+ 0 +
-
- 20 +
+
+ 20 +
-
- 40 +
+
+ 40 +
-
- 60 +
+
+ 60 +
-
- 80 +
+
+ 80 +
-
- 100 +
+
+ 100 +
diff --git a/tests/markup/snapshots/slider-range-mode.snap.html b/tests/markup/snapshots/slider-range-mode.snap.html index e50599354f..ad2d95cdb9 100644 --- a/tests/markup/snapshots/slider-range-mode.snap.html +++ b/tests/markup/snapshots/slider-range-mode.snap.html @@ -39,43 +39,55 @@
-
- 0 +
+
+ 0 +
-
- 20 +
+
+ 20 +
-
- 40 +
+
+ 40 +
-
- 60 +
+
+ 60 +
-
- 80 +
+
+ 80 +
-
- 100 +
+
+ 100 +
diff --git a/tests/markup/snapshots/slider-read-only-range-mode.snap.html b/tests/markup/snapshots/slider-read-only-range-mode.snap.html index ae37ab2014..fb888c480e 100644 --- a/tests/markup/snapshots/slider-read-only-range-mode.snap.html +++ b/tests/markup/snapshots/slider-read-only-range-mode.snap.html @@ -39,43 +39,55 @@
-
- 0 +
+
+ 0 +
-
- 20 +
+
+ 20 +
-
- 40 +
+
+ 40 +
-
- 60 +
+
+ 60 +
-
- 80 +
+
+ 80 +
-
- 100 +
+
+ 100 +
diff --git a/tests/markup/snapshots/slider-read-only-single-mode.snap.html b/tests/markup/snapshots/slider-read-only-single-mode.snap.html index b03261b567..6f3f8f5f52 100644 --- a/tests/markup/snapshots/slider-read-only-single-mode.snap.html +++ b/tests/markup/snapshots/slider-read-only-single-mode.snap.html @@ -26,43 +26,55 @@
-
- 0 +
+
+ 0 +
-
- 20 +
+
+ 20 +
-
- 40 +
+
+ 40 +
-
- 60 +
+
+ 60 +
-
- 80 +
+
+ 80 +
-
- 100 +
+
+ 100 +
diff --git a/tests/markup/snapshots/slider-single-mode.snap.html b/tests/markup/snapshots/slider-single-mode.snap.html index f5535e50ba..77882e97d6 100644 --- a/tests/markup/snapshots/slider-single-mode.snap.html +++ b/tests/markup/snapshots/slider-single-mode.snap.html @@ -26,43 +26,55 @@
-
- 0 +
+
+ 0 +
-
- 20 +
+
+ 20 +
-
- 40 +
+
+ 40 +
-
- 60 +
+
+ 60 +
-
- 80 +
+
+ 80 +
-
- 100 +
+
+ 100 +