import {Component_A} from './basic/as-component.js';
import {FP_Slider_A} from './fp-ext/fp-slider-ext.js';
import {getDecimalPlaces} from './utils/utils.js';
import {ErrorCode} from '../exception/exceptionDesc.js';
/**
* @typedef TComponents.SliderProps
* @prop {object} [options] General options for the slider behavior.
* Items in this object include:
* - **responsive** (boolean, default: false): Whether the slider width should adapt to the container width.
* @prop {string} [tips] Tooltip text for the component.
* @prop {Function} [onCreated] Function to be called when the component is created.
* @prop {Function} [onMounted] Function to be called when the component is mounted.
* @prop {Function} [onPointerRelease] Callback function that is called when the pointer is released after dragging the slider.
* @prop {Function} [onPointerDown] Callback function that is called while the pointer is dragging the slider.
* @prop {string} [position] CSS position property.
* @prop {number} [width] Width of the component.
* @prop {number} [height] Height of the component.
* @prop {number} [top] Top position of the component.
* @prop {number} [left] Left position of the component.
* @prop {number} [borderRadius] Border radius of the component.
* @prop {number} [rotation] Rotation angle of the component.
* @prop {number} [zIndex] Z-index of the component.
* @prop {string} [color] Font color of the description label.
* @prop {object} [font] Font configuration for the description label.
* This object controls text appearance:
* - **fontSize** (number, default: 14): Font size in pixels.
* - **fontFamily** (string, default: 'Segoe UI'): Font family name.
* - **style** (object): Font style configuration, containing `fontStyle`, `fontWeight`, `textDecoration`.
* @prop {string} [rangeValueColor] Font color of the min/max range values.
* @prop {object} [rangeValueFont] Font configuration for the min/max range values.
* This object controls range text appearance:
* - **fontSize** (number, default: 12): Font size in pixels.
* - **fontFamily** (string, default: 'Segoe UI'): Font family name.
* - **style** (object): Font style configuration, containing `fontStyle`, `fontWeight`, `textDecoration`.
* @prop {string} [backgroundColor] Background color of the slider container.
* @prop {string} [border] CSS border style of the slider container.
* @prop {boolean} [displayLabel] Whether to display the description label.
* @prop {string} [descrLabel] Text content of the description label.
* @prop {boolean} [displayValue] Whether to display the current value label.
* @prop {number} [value] Current value of the slider.
* @prop {number} [min] Minimum value of the slider range.
* @prop {number} [max] Maximum value of the slider range.
* @prop {boolean} [enabled] Whether the slider is enabled and interactive.
* @prop {string} [activeColor] Color of the active (filled) part of the slider track.
* @prop {string} [inactiveColor] Color of the inactive part of the slider track.
* @prop {boolean} [displayTicks] Whether to display tick marks along the track.
* @prop {number} [tickStep] Step between tick marks and value increments.
* @prop {string} [defaultState] Initial state of the component.
* @memberof TComponents
*/
/**
* @ignore
*/
const logModule = 'as-slider';
/**
* @description Slider element. Additional callbacks can be added with the {@link TComponents.Slider#onChange|onChange} method.
* This class focuses on the specific properties of the Slider component.
* Since it inherits from Accessor_A, all basic properties (e.g., height, width) are available but documented in the Accessor_A part.
* @class TComponents.Slider
* @extends TComponents.Component_A
* @memberof TComponents
* @param {HTMLElement} parent - HTML element that is going to be the parent of the component
* @param {TComponents.SliderProps} props
* @example
* const slider = new TComponents.Slider(document.body, {
* position: 'absolute',
* zIndex: 1000,
* max:200,
* min:50,
* value:100,
* });
*
* // Render the component.
* slider.render();
*/
export class Slider extends Component_A {
constructor(parent, props = {}) {
super(parent, props);
/**
* @instance
* @private
* @type {TComponents.SliderProps}
*/
this._props;
this._slider = new FP_Slider_A();
}
/**
* @description Returns the default values of class properties (excluding parent properties).
* @member TComponents.Slider#defaultProps
* @method
* @protected
* @returns {TComponents.SliderProps}
*/
defaultProps() {
return {
options: {
responsive: false,
},
tips: '',
// life cycle
onCreated: '',
onMounted: '',
onDispose: '',
onPointerRelease: '',
onPointerDown: '',
// X/Y/W/H/B/R
position: 'static',
width: 200,
height: 80,
top: 0,
left: 0,
borderRadius: 4,
rotation: 0,
zIndex: 0,
//label font color
color: '#000000',
//label font style
font: {
fontSize: 14,
fontFamily: 'Segoe UI',
style: {
fontStyle: 'normal',
fontWeight: 'bold',
textDecoration: 'none',
},
},
// range value font style
rangeValueColor: '#0000008e',
rangeValueFont: {
fontSize: 12,
fontFamily: 'Segoe UI',
style: {
fontStyle: 'normal',
fontWeight: 'normal',
textDecoration: 'none',
},
},
// Background
backgroundColor: '#ffffff',
// Border
border: '0px solid #dbdbdb',
// label
displayLabel: true,
descrLabel: 'Label',
// value
displayValue: true,
value: 30,
min: 0,
max: 100,
enabled: true,
// range track
activeColor: '#3366ff',
inactiveColor: '#d3d3d3',
displayTicks: true,
tickStep: 10,
defaultState: 'show_enable',
};
}
/**
* @description Initializes the Slider component.
* @member TComponents.Slider#onInit
* @method
* @returns {void}
*/
onInit() {}
/**
* @description Renders the Slider component.
* @member TComponents.Slider#onRender
* @method
* @throws {Error} Throws an error if rendering fails.
* @returns {void}
*/
onRender() {
try {
this.removeAllEventListeners();
//background color and radius
this._slider._backgroundColor = this._props.backgroundColor;
this._slider._border = this._props.border;
this._slider._borderRadius = this._props.borderRadius;
//label
this._slider._displayLabel = this._props.displayLabel;
this._slider._label = Component_A.tParse(this._props.descrLabel);
this._slider._labelFont = this._props.font;
this._slider._labelColor = this._props.color;
//value
this._slider._displayValue = this._props.displayValue;
this._slider._value = this._props.value;
// range
this._slider._activeColor = this._props.activeColor;
this._slider._inactiveColor = this._props.inactiveColor;
this._slider._displayTicks = this._props.displayTicks;
this._slider._tickStep = this._props.tickStep;
this._slider._numberOfDecimals = getDecimalPlaces(this._props.tickStep);
this._slider._max = this._props.max;
this._slider._min = this._props.min;
// range value color
this._slider._rangeValueFont = this._props.rangeValueFont;
this._slider._rangeValueColor = this._props.rangeValueColor;
// enable status
this._slider.enabled = this.enabled;
//onPointerRelease
this._slider.onrelease = this._onPointerRelease.bind(this);
this._slider.ondrag = this._onPointerDown.bind(this);
this._dynamicRender();
this._addTips();
} catch (e) {
// Runtime errors: write specific content to the log, throw error code
Logger.e(
logModule,
ErrorCode.FailedToRunOnRender,
`Error happens on onRender of slider component ${this.compId}.`,
e,
);
throw new Error(ErrorCode.FailedToRunOnRender, {cause: e});
}
}
/**
* @description Dynamically renders the Slider component.
* @member TComponents.Slider~_dynamicRender
* @method
* @private
* @returns {void}
*/
_dynamicRender() {
if (this._props.options.responsive) {
setTimeout(() => {
const {clientWidth} = this.container;
this._slider._width = clientWidth - 28;
const sliderContainer = this.find('.tc-slider');
if (sliderContainer) this._slider.attachToElement(sliderContainer);
});
} else {
this._slider._width = this._props.width - 28;
const sliderContainer = this.find('.tc-slider');
if (sliderContainer) this._slider.attachToElement(sliderContainer);
}
}
/**
* @description Generates the markup for the Slider component.
* @member TComponents.Slider#markup
* @method
* @returns {string} HTML markup string
*/
markup() {
return /*html*/ `
<div class="tc-slider"></div>
`;
}
/**
* @description Pointer up/leave event handler.
* @member TComponents.Slider~_onPointerRelease
* @method
* @private
* @async
* @throws {Error} If the synchronization with popup fails or if the user-defined function throws an error.
* @returns {Promise<void>}
*/
async _onPointerRelease(value) {
if (this.enabled && this._props.onPointerRelease) {
this._slider.value = value;
this.commitProps({value: value});
try {
var fn = Component_A.genFuncTemplate(this._props.onPointerRelease, this);
if (typeof fn == 'function') {
await fn(value);
}
} catch (e) {
Component_A.popupEventError(e, 'onPointerRelease', logModule);
}
}
}
/**
* @description Pointer down event handler.
* @member TComponents.Slider~_onPointerDown
* @method
* @private
* @async
* @throws {Error} If the synchronization with popup fails or if the user-defined function throws an error.
* @returns {Promise<void>}
*/
async _onPointerDown(value) {
if (this.enabled && this._props.onPointerDown) {
try {
var fn = Component_A.genFuncTemplate(this._props.onPointerDown, this);
fn && (await fn(value));
} catch (e) {
Component_A.popupEventError(e, 'onPointerDown', logModule);
return;
}
}
}
/**
* @returns {Function|undefined}
*/
get onPointerRelease() {
try {
var fn = Component_A.genFuncTemplateWithPopup(this._props.onPointerRelease, this);
} catch (e) {
return undefined;
}
if (typeof fn == 'function') return fn;
else return undefined;
}
/**
* @description Sets the pointer release event handler for the Slider component.
* The handler can either be a string representing a function to be executed or a function itself.
* 1. If you are using an arrow function, like `()=>{}`,
* the `this` property of the scope may not refer to the slider object.
* 2. If you are using string assignment to define code execution,
* the string should contain `only the body of the code (executable statements)`,
* not a complete function declaration. Therefore, including function keywords like function or async function is incorrect.
* - Correct (Statements Only): `xx.onPointerRelease = "console.log('Action done.');"`
* - Incorrect (Function Declaration): `xx.onPointerRelease = "function() { console.log('Action done.'); }"`
* @member {Function} TComponents.Slider#onPointerRelease
* @instance
* @param {Function} t - The new pointer release handler function.
* @example
* const slider = new TComponents.Slider(document.body, {
* position: 'absolute',
* zIndex: 1000,
* max:200,
* min:50,
* value:100,
* });
*
* // Render the component.
* slider.render();
*
* // Example 1: Using a string as the handler:
* slider.onPointerRelease = "console.log('state changed', this.text);";
* @example
* // Example 2: Using a arrow function as the handler:
* // Note that the `this` context will not refer to the slider object
* slider.onPointerRelease = () => { console.log('state changed', slider.text); };
* @example
* // Example 3: Using a common function as the handler:
* slider.onPointerRelease = async function() {
* console.log('state changed', this.text);
* };
*/
set onPointerRelease(t) {
this.setProps({onPointerRelease: t});
}
/**
* @description Sets the pointer down event handler for the Slider component.
* * The handler can either be a string representing a function to be executed or a function itself.
* 1. If you are using an arrow function, like `()=>{}`,
* the `this` property of the scope may not refer to the slider object.
* 2. If you are using string assignment to define code execution,
* the string should contain `only the body of the code (executable statements)`,
* not a complete function declaration. Therefore, including function keywords like function or async function is incorrect.
* - Correct (Statements Only): `xx.onPointerDown = "console.log('Action done.');"`
* - Incorrect (Function Declaration): `xx.onPointerDown = "function() { console.log('Action done.'); }"`
* @member {Function} TComponents.Slider#onPointerDown
* @instance
* @param {Function} t - The new pointer down handler function.
* @example
* const slider = new TComponents.Slider(document.body, {
* position: 'absolute',
* zIndex: 1000,
* max:200,
* min:50,
* value:100,
* });
*
* // Render the component.
* slider.render();
*
* // Example 1: Using a string as the handler:
* slider.onPointerDown = "console.log('state changed', this.text);";
* @example
* // Example 2: Using a arrow function as the handler:
* // Note that the `this` context will not refer to the slider object
* slider.onPointerDown = () => { console.log('state changed', slider.text); };
* @example
* // Example 3: Using a common function as the handler:
* slider.onPointerDown = async function() {
* console.log('state changed', this.text);
* };
*/
set onPointerDown(t) {
this.setProps({onPointerDown: t});
}
/**
* @description Sets the value of the Slider component.
* @member {number} TComponents.Slider#value
* @instance
* @param {number} v - The value
* @example
* const slider = new TComponents.Slider(document.body, {
* position: 'absolute',
* zIndex: 1000,
* max:200,
* min:50,
* value:100,
* });
*
* // Render the component.
* slider.render();
*
* // Set value.
* slider.value = 20;
*/
set value(v) {
// check validity
if (v > this.max || v < this.min) {
Logger.e(logModule, ErrorCode.FailedToSetNum, 'The value must be between the min value and the max value.', v);
throw new Error(ErrorCode.FailedToSetNum, {
cause: 'The value must be between the min value and the max value.',
});
}
this._slider.value = v;
this.commitProps({text: v});
}
/**
* @returns {string}
*/
get value() {
return this._slider.value;
}
/**
* @returns {number}
*/
get max() {
return this._props.max;
}
/**
* @description Sets the maximum value of the slidr
* @member {number} TComponents.Slider#max
* @instance
* @param {number} v the max
* @example
* const slider = new TComponents.Slider(document.body, {
* position: 'absolute',
* zIndex: 1000,
* max:200,
* min:50,
* value:100,
* });
*
* // Render the component.
* slider.render();
*
* // Set max value.
* slider.max = 100;
*/
set max(v) {
if (typeof v !== 'number') {
throw new Error(ErrorCode.FailedToSetNum, {
cause: `The min value must be a number.`,
});
}
if (v <= this.min || v < this.value) {
throw new Error(ErrorCode.FailedToSetMax, {
cause: `The max value must be larger than min value`,
});
}
this.setProps({
max: v,
});
}
/**
* @returns {number}
*/
get min() {
return this._props.min;
}
/**
* @description Sets the minimum value of the Slider component range.
* @member {number} TComponents.Slider#min
* @instance
* @param {number} e - The min state to set.
* @example
* const slider = new TComponents.Slider(document.body, {
* position: 'absolute',
* zIndex: 1000,
* max:200,
* min:50,
* value:100,
* });
*
* // Render the component.
* slider.render();
*
* // Set max value.
* slider.min = 10;
*/
set min(v) {
// check validity
if (typeof v !== 'number') {
throw new Error(ErrorCode.FailedToSetNum, {
cause: `The min value must be a number.`,
});
}
if (v >= this.max || v > this.value) {
Logger.e(logModule, ErrorCode.FailedToSetMin, 'The min value must be less than the max value', v);
throw new Error(ErrorCode.FailedToSetMin, {cause: 'The min value must be less than the max value'});
}
this.setProps({
min: v,
});
}
/**
* @returns {boolean}
*/
get displayLabel() {
return this._props.displayLabel;
}
/**
* @description Sets the property to true to display the description lanel in the Slider component.
* @member {boolean} TComponents.Slider#displayLabel
* @instance
* @param {boolean} b - True if description label shall display.
* @example
* const slider = new TComponents.Slider(document.body, {
* position: 'absolute',
* zIndex: 1000,
* max:200,
* min:50,
* value:100,
* });
*
* // Render the component.
* slider.render();
*
* // Set max value.
* slider.displayLabel = 'hello world';
*/
set displayLabel(b) {
this.setProps({
displayLabel: b,
});
}
/**
* @returns {boolean}
*/
get displayValue() {
return this._props.displayValue;
}
/**
* @description Sets the property to true to display the value lanel in the Slider component.
* @member {boolean} TComponents.Slider#displayValue
* @instance
* @param {boolean} b - True if value label shall display.
* @example
* const slider = new TComponents.Slider(document.body, {
* position: 'absolute',
* zIndex: 1000,
* max:200,
* min:50,
* value:100,
* });
*
* // Render the component.
* slider.render();
*
* // Set max value.
* slider.displayValue = false;
*/
set displayValue(b) {
this.setProps({
displayValue: b,
});
}
/**
* @returns {string}
*/
get descrLabel() {
return this._props.descrLabel;
}
/**
* @description Sets the content of description label.
* @member {string} TComponents.Slider#descrLabel
* @instance
* @param {string} c - The text content of description label
* @example
* const slider = new TComponents.Slider(document.body, {
* position: 'absolute',
* zIndex: 1000,
* max:200,
* min:50,
* value:100,
* });
*
* // Render the component.
* slider.render();
*
* // Set max value.
* slider.descrLabel = 'hello world';
*/
set descrLabel(c) {
this.setProps({
descrLabel: c,
});
}
/**
* @returns {string}
*/
get activeColor() {
return this._props.activeColor;
}
/**
* @description Sets the color of active track of the Slider component.
* @member {string} TComponents.Slider#activeColor
* @instance
* @param {string} c - The color of active track of the Slider component.
* @example
* const slider = new TComponents.Slider(document.body, {
* position: 'absolute',
* zIndex: 1000,
* max:200,
* min:50,
* value:100,
* });
*
* // Render the component.
* slider.render();
*
* // Set max value.
* slider.activeColor = 'red';
*/
set activeColor(c) {
this.setProps({
activeColor: c,
});
}
/**
* @returns {string}
*/
get inactiveColor() {
return this._props.inactiveColor;
}
/**
* @description Sets the color of inactive track of the Slider component.
* @member {string} TComponents.Slider#inactiveColor
* @instance
* @param {string} c - The color of inactive track of the Slider component.
* @example
* const slider = new TComponents.Slider(document.body, {
* position: 'absolute',
* zIndex: 1000,
* max:200,
* min:50,
* value:100,
* });
*
* // Render the component.
* slider.render();
*
* // Set max value.
* slider.inactiveColor = 'red';
*/
set inactiveColor(c) {
this.setProps({
inactiveColor: c,
});
}
/**
* @returns {boolean}
*/
get displayTicks() {
return this._props.displayTicks;
}
/**
* @description Sets to true to display step ticks in the Slider component.
* @member {boolean} TComponents.Slider#displayTicks
* @instance
* @param {boolean} d - True if expecting step ticks to be displayed.
* @example
* const slider = new TComponents.Slider(document.body, {
* position: 'absolute',
* zIndex: 1000,
* max:200,
* min:50,
* value:100,
* });
*
* // Render the component.
* slider.render();
*
* // Set max value.
* slider.displayTicks = false;
*/
set displayTicks(d) {
this.setProps({
displayTicks: d,
});
}
}
/**
* @description Add css properties to the component
* @alias loadCssClassFromString
* @member TComponents.Slider.loadCssClassFromString
* @method
* @static
* @param {string} css - The css string to be loaded into style tag
* @returns {void}
* @example
* TComponents.Slider.loadCssClassFromString(`
* .tc-slider {
* height: inherit;
* }`
* );
*/
Slider.loadCssClassFromString(/*css*/ `
.tc-slider {
height: inherit;
width: 100%;
min-width: 0px;
min-height: 0px;
padding: 0px;
margin: 0px;
}
.tc-slider .fp-components-slider {
max-width: 100%;
box-sizing: border-box;
max-height:100%;
gap: calc(10px + 1%);
justify-content: space-between;
height: inherit;
width: inherit;
overflow: hidden;
}
.tc-slider .fp-components-slider > *:last-child {
margin-bottom: 0 !important;
}
.tc-slider .fp-components-slider-disabled{
cursor:not-allowed !important;
}
.tc-slider .fp-components-slider:hover{
opacity:0.7;
}
.tc-slider .fp-components-slider__labels-wrapper{
height: auto;
margin-bottom: auto;
}
.tc-slider .fp-components-slider__range-wrapper {
position: relative;
height: 4px;
width: 100%;
background-position-x: -1px;
border-radius:4px;
background-image: linear-gradient(to right, white 1px, lightgrey 1px);
min-height: 4px;
}
.tc-slider .fp-components-slider__track {
height: 4px;
top: 50%;
position: absolute;
transform: translate(0%, -50%);
pointer-events: none;
background-color:transparent;
}
.tc-slider .fp-components-slider__tick{
width: 2px;
height: 4px;
position: absolute;
background-color: transparent;
transform: translate(-50%, -50%);
top:50%;
pointer-events: none;
}
.tc-slider .fp-components-slider__track--active {
background-color: var(--fp-color-BLUE-60);
pointer-events: inherit;
border-radius:4px;
}
.tc-slider .fp-components-slider__track-touchbox {
position: absolute;
top:50%;
transform: translate(-50%, -50%);
}
`);