import {Component_A} from './basic/as-component.js';
import {FP_Toggle_A} from './fp-ext/fp-toggle-ext.js';
import {formatOptionsString, initDynamicOptions, generateOptionsString} from './utils/utils.js';
import {ErrorCode, ExceptionIdMap} from '../exception/exceptionDesc.js';
/**
* @typedef TComponents.ToggleProps
* @prop {object} [options] Additional options for the toggle component.
* Items in this object include:
* - **responsive** (boolean, default: false): Whether the toggle group should be responsive.
* @prop {string} [tips] Tooltip text for the component.
* @prop {Function} [onCreated] Function to be called when the toggle component is created.
* @prop {Function} [onMounted] Function to be called when the toggle component is mounted.
* @prop {Function} [onChange] Function to be called when the toggle state changes.
* @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} [rotation] Rotation angle of the component.
* @prop {number} [zIndex] Z-index of the component.
* @prop {string} [color] Text color of the toggle items.
* @prop {object} [font] Font configuration for the toggle labels.
* This object controls 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} [toggledColor] Background color of the toggled items.
* @prop {boolean} [multi] Whether to allow multiple selections.
* @prop {string} [optionItems] Formatted string representing the toggle options.
* @prop {object} [optionConfig] Configuration for dynamic options.
* This object controls how options are provided:
* - **mode** (string, default: 'fixed'): Options mode, e.g. 'fixed', 'sync', 'initialize'.
* - **type** (string): Type of the options data source.
* - **isHidden** (boolean, default: false): Whether this option configuration is hidden.
* - **variablePath** (string): Variable path used when options are dynamic.
* @prop {Array<Object>} [optionIcons] Icon configuration for each option.
* @prop {string[]} [optionActions] Action configuration for each option.
* @prop {string} [defaultState] Default state of the component.
* @memberof TComponents
*/
/**
* @ignore
*/
const logModule = 'as-toggle-a';
/**
* @description Toggle_A element.
* This class focuses on the specific properties of the Toggle_A 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.Toggle_A
* @extends TComponents.Component_A
* @memberof TComponents
* @param {HTMLElement} parent - HTML element that is going to be the parent of the component
* @param {TComponents.ToggleProps} props
* @example
* const toggle = new TComponents.Toggle_A(document.body, {
* position: 'absolute',
* zIndex: 1000,
* });
*
* // Render the component.
* await toggle.render();
*/
export class Toggle_A extends Component_A {
constructor(parent, props = {}) {
super(parent, props);
/**
* @instance
* @private
* @type {TComponents.ToggleProps}
*/
this._props;
this._toggle = new FP_Toggle_A();
this._changed = [];
}
/**
* @description Returns the default values of class properties (excluding parent properties).
* @member TComponents.Toggle_A#defaultProps
* @method
* @protected
* @returns {TComponents.ToggleProps}
*/
defaultProps() {
return {
options: {
responsive: false,
},
tips: '',
// life cycle
onCreated: '',
onMounted: '',
onDispose: '',
onChange: '',
// X/Y/W/H/B/R
position: 'static',
width: 320,
height: 32,
top: 0,
left: 0,
rotation: 0,
zIndex: 0,
// font color
color: '#000000',
// font style
font: {
fontSize: 12,
fontFamily: 'Segoe UI',
style: {
fontStyle: 'normal',
fontWeight: 'normal',
textDecoration: 'none',
},
},
// toggled background color
toggledColor: '#3366ff',
// Data
multi: false, // if allow multi selection
optionItems: `text1|value1;text2|value2;text3|value3`,
optionConfig: {
mode: 'fixed', //fixed, sync,initialize
type: '',
isHidden: false,
variablePath: '',
},
optionIcons: [
{icon: '', toggledIcon: ''},
{icon: '', toggledIcon: ''},
{icon: '', toggledIcon: ''},
],
optionActions: ['', '', ''],
defaultState: 'show_enable',
};
}
/**
* @description Initializes the toggle component.
* @member TComponents.Toggle_A#onInit
* @method
* @async
* @returns {Promise<void>}
*/
async onInit() {}
/**
* @description there are something need to do after render once
* @member TComponents.Toggle_A#afterRenderOnce
* @method
* @protected
* @returns {void}
*/
afterRenderOnce() {
initDynamicOptions(this._props.optionConfig, (data) => {
this.setProps({
optionItems: generateOptionsString(data),
});
});
}
/**
* @description Renders the toggle component.
* @member TComponents.Toggle_A#onRender
* @method
* @async
* @throws {Error} If an error occurs during rendering.
* @returns {Promise<void>}
*/
async onRender() {
try {
this.removeAllEventListeners();
this._toggle._multi = this._props.multi;
this._toggle._model = this.generateModel();
this._toggle._color = this._props.color;
this._toggle._font = this._props.font;
this._toggle._toggledColor = this._props.toggledColor;
this._toggle._enabled = this.enabled;
this._toggle._onselection = this._cbOnChange.bind(this);
// update toggled color class
document.documentElement.style.setProperty('--toggled-bg-color', this._props.toggledColor);
const toggleContainer = this.find('.tc-toggle');
if (toggleContainer) this._toggle.attachToElement(toggleContainer);
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 Toggle_A component ${this.compId}.`,
e,
);
throw new Error(ErrorCode.FailedToRunOnRender, {cause: e});
}
}
/**
* @description Handles the change event for the toggle element.
* @member TComponents.Toggle_A~_cbOnChange
* @method
* @private
* @async
* @param {object} e - The change parameters.
* @returns {void}
*/
async _cbOnChange({changed = [], all = [], item = {}, index = -1}) {
if (this.enabled) {
this._changed = changed;
if (item.action) {
try {
var action = Component_A.genFuncTemplate(item.action, this);
action && (await action());
} catch (e) {
Component_A.popupEventError(e, `Item ${index} Action`, logModule);
return;
}
}
if (this._props.onChange) {
try {
var fn = Component_A.genFuncTemplate(this._props.onChange, this);
fn && (await fn());
} catch (e) {
Component_A.popupEventError(e, 'onChange', logModule);
}
}
}
}
/**
* @description Generate toggle button model with value parameter
* @member TComponents.Toggle_A#generateModel
* @method
* @returns {any[]} The generated model array.
*/
generateModel() {
let model = [];
for (let i = 0; i < this.optionItems.length; i++) {
model.push({
text: this.optionItems[i].text,
value: this.optionItems[i].value,
icon: this._props.optionIcons[i] && this._props.optionIcons[i].icon ? this._props.optionIcons[i].icon : '',
toggledIcon:
this._props.optionIcons[i] && this._props.optionIcons[i].toggledIcon
? this._props.optionIcons[i].toggledIcon
: '',
action: this._props.optionActions[i] ? this._props.optionActions[i] : '',
});
}
return model;
}
/**
* @description Set the toggle status of the specifc index button.
* @member {number} TComponents.Toggle_A#setToggled
* @method
* @param {number} index the index of target button
* @param {boolean} toggled null: invert the status of target button; true/false: set target button to true/false
* @param {boolean} fireCallback True if expecting to trigger onClick event
* @returns {void}
*/
setToggled(index, toggled = null, fireCallback = false) {
if (!this.enabled) return;
if (index < 0 || index >= this._toggle._toggleState.length) {
Logger.w(logModule, ErrorCode.FailedToSetNum, `Invalid index ${index} for toggle component ${this.compId}.`);
throw new Error(ErrorCode.FailedToSetNum, {
cause: `Invalid index ${index} for toggle component ${this.compId}.`,
});
}
this._toggle._setToggled(index, toggled, fireCallback);
}
/**
* @description Returns the markup for the toggle component.
* @member TComponents.Toggle_A#markup
* @method
* @returns {string} HTML markup string.
*/
markup() {
return /*html*/ `
<div class="tc-toggle"></div>
`;
}
/**
* @returns {boolean[]}
*/
get toggleState() {
return this._toggle._toggleState;
}
/**
* @returns {any[]}
*/
get changed() {
return this._changed;
}
/**
* @returns {boolean}
*/
get multi() {
return this._toggle._multi;
}
/**
* @description Set if the toggle button allows multi toggled button.
* @member {boolean} TComponents.Toggle_A#multi
* @instance
* @param {boolean} v
* @example
* const toggle = new TComponents.Toggle_A(document.body, {
* position: 'absolute',
* zIndex: 1000,
* });
*
* // Render the component.
* await toggle.render();
*
* toggle.multi = true;
*/
set multi(v) {
this.setProps({
multi: v,
});
}
/**
* @returns {Function|undefined}
*/
get onChange() {
try {
var fn = Component_A.genFuncTemplateWithPopup(this._props.onChange, this);
} catch (e) {
return undefined;
}
if (typeof fn == 'function') return fn;
else return undefined;
}
/**
* @description Sets the `onChange` event handler for the toggle 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 toggle 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.onChange = "console.log('Action done.');"`
* - Incorrect (Function Declaration): `xx.onChange = "function() { console.log('Action done.'); }"`
* @member {Function} TComponents.Toggle_A#onChange
* @instance
* @param {string|Function} t - A string representing a function to be executed or a function itself to handle the `onChange` event.
* @example
* const toggle = new TComponents.Toggle_A(document.body, {
* position: 'absolute',
* zIndex: 1000,
* });
*
* // Render the component.
* await toggle.render();
* // Example 1: Using a string as the handler:
* toggle.onChange = "console.log(this.multi);"
* @example
* // Example 2: Using a function as the handler:
* toggle.onChange = function () {
* console.log(this.multi);
* };
* @example
* // Example 3: Using an arrow function as the handler:
* // Note that the `this` context will not refer to the toggle object
* toggle.onChange = () => {
* console.log(toggle.multi);
* };
*/
set onChange(t) {
this.setProps({onChange: t});
}
/**
* @returns {any[]}
*/
get optionItems() {
const data = formatOptionsString(this._props.optionItems);
for (let i = 0; i < data.length; i++) {
data[i].text = Component_A.tParse(data[i].text.replace(/^\n+/, ''));
}
return data;
}
/**
* @description Sets the option items from a formatted string.
* @member {string} TComponents.Toggle_A#optionItems
* @instance
* @param {string} itemsString - The formatted string of option items.
* @example
* const toggle = new TComponents.Toggle_A(document.body, {
* position: 'absolute',
* zIndex: 1000,
* });
*
* // Render the component.
* await toggle.render();
*
* toggle.optionItems = `text4|value4;text5|value5;text6|value6`;
*/
set optionItems(itemsString) {
const optionsConfig = this._props.optionConfig;
if (optionsConfig && optionsConfig.mode == 'fixed') {
this.setProps({
optionItems: itemsString,
});
} else {
throw new Error(ErrorCode.FailedToUpdateComponentWithSetter, {
cause: 'Cannot set prop optionItems when optionsConfig is not in fixed mode.',
});
}
}
}
/**
* @description Add css properties to the component
* @alias loadCssClassFromString
* @member TComponents.Toggle_A.loadCssClassFromString
* @method
* @static
* @param {string} css - The css string to be loaded into style tag
* @returns {void}
* @example
* TComponents.Toggle_A.loadCssClassFromString(`
* .tc-toggle {
* height: inherit;
* }`
* );
*/
Toggle_A.loadCssClassFromString(/*css*/ `
.tc-toggle {
height: 100%;
width: 100%;
min-width: 0px;
min-height: 0px;
padding: 0px;
margin: 0px;
}
.tc-toggle .fp-components-toggle {
width: 100%;
height: 100%;
min-width: 0px;
min-height: 0px;
display: flex;
flex-wrap: wrap;
overflow: hidden;
flex-direction: row;
border-radius: 8px 8px 8px 8px;
border: 1px groove rgb(204,204,204);
box-sizing: border-box;
}
.tc-toggle .fp-components-toggle-disabled{
opacity:0.7;
cursor: not-allowed;
}
.tc-toggle .fp-components-toggle-disabled > *{
pointer-events: none;
}
.tc-toggle .fp-components-toggle > * {
border-radius: 0 0 0 0;
border: none;
box-shadow: 5px 5px 0px 8px var(--fp-color-GRAY-10);
justify-content: center;
align-items: center;
white-space: nowrap;
display:flex;
flex-direction:row;
gap: 10px;
text-overflow: ellipsis;
}
.tc-toggle .fp-components-toggle-icon {
width: auto;
height: auto;
background-size: 16px 16px;
background-repeat: no-repeat;
background-position: center;
}
.tc-toggle .fp-components-toggle > *:hover {
background-color: var(--toggled-bg-color);
}
`);