416 lines
15 KiB
JavaScript
416 lines
15 KiB
JavaScript
/*!
|
|
* Jodit Editor (https://xdsoft.net/jodit/)
|
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
* Copyright (c) 2013-2025 Valeriy Chupurnov. All rights reserved. https://xdsoft.net
|
|
*/
|
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
r = Reflect.decorate(decorators, target, key, desc);
|
|
else
|
|
for (var i = decorators.length - 1; i >= 0; i--)
|
|
if (d = decorators[i])
|
|
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
};
|
|
import { STATUSES } from "../../../core/component/statuses.js";
|
|
import { autobind, cacheHTML, component, watch } from "../../../core/decorators/index.js";
|
|
import { Dom } from "../../../core/dom/index.js";
|
|
import { assert, attr, call, camelCase, isArray, isFunction, isJoditObject, isPlainObject, isString, keys, position } from "../../../core/helpers/index.js";
|
|
import { UIButton, UIButtonState } from "../../../core/ui/button/index.js";
|
|
import { findControlType } from "../../../core/ui/helpers/get-control-type.js";
|
|
import { Icon } from "../../../core/ui/icon.js";
|
|
import { Popup } from "../../../core/ui/popup/popup.js";
|
|
import { ToolbarCollection } from "../collection/collection.js";
|
|
import { makeCollection } from "../factory.js";
|
|
let ToolbarButton = class ToolbarButton extends UIButton {
|
|
/** @override */
|
|
className() {
|
|
return 'ToolbarButton';
|
|
}
|
|
/**
|
|
* Get parent toolbar
|
|
*/
|
|
get toolbar() {
|
|
return this.closest(ToolbarCollection);
|
|
}
|
|
/** @override **/
|
|
update() {
|
|
var _a, _b;
|
|
const { control, state } = this, tc = this.closest(ToolbarCollection);
|
|
if (!tc) {
|
|
return;
|
|
}
|
|
const value = (_a = control.value) === null || _a === void 0 ? void 0 : _a.call(control, tc.jodit, this);
|
|
if (value !== undefined) {
|
|
state.value = value;
|
|
}
|
|
state.disabled = this.__calculateDisabledStatus(tc);
|
|
state.activated = this.__calculateActivatedStatus(tc);
|
|
(_b = control.update) === null || _b === void 0 ? void 0 : _b.call(control, tc.jodit, this);
|
|
}
|
|
/**
|
|
* Calculates whether the button is active
|
|
*/
|
|
__calculateActivatedStatus(tc) {
|
|
var _a, _b;
|
|
if (isJoditObject(this.j) && !this.j.editorIsActive) {
|
|
return false;
|
|
}
|
|
if ((_b = (_a = this.control).isActive) === null || _b === void 0 ? void 0 : _b.call(_a, this.j, this)) {
|
|
return true;
|
|
}
|
|
return Boolean(tc && tc.shouldBeActive(this));
|
|
}
|
|
/**
|
|
* Calculates whether an element is blocked for the user
|
|
*/
|
|
__calculateDisabledStatus(tc) {
|
|
var _a, _b;
|
|
if (this.j.o.disabled) {
|
|
return true;
|
|
}
|
|
if (this.j.o.readonly &&
|
|
(!this.j.o.activeButtonsInReadOnly ||
|
|
!this.j.o.activeButtonsInReadOnly.includes(this.control.name))) {
|
|
return true;
|
|
}
|
|
if ((_b = (_a = this.control).isDisabled) === null || _b === void 0 ? void 0 : _b.call(_a, this.j, this)) {
|
|
return true;
|
|
}
|
|
return Boolean(tc && tc.shouldBeDisabled(this));
|
|
}
|
|
/** @override */
|
|
onChangeActivated() {
|
|
attr(this.button, 'aria-pressed', this.state.activated);
|
|
super.onChangeActivated();
|
|
}
|
|
/** @override */
|
|
onChangeText() {
|
|
if (isFunction(this.control.template)) {
|
|
this.text.innerHTML = this.control.template(this.j, this.control.name, this.j.i18n(this.state.text));
|
|
}
|
|
else {
|
|
super.onChangeText();
|
|
}
|
|
this.setMod('text-icons', Boolean(this.text.innerText.trim().length));
|
|
}
|
|
/** @override */
|
|
onChangeTabIndex() {
|
|
attr(this.button, 'tabindex', this.state.tabIndex);
|
|
}
|
|
createContainer() {
|
|
const cn = this.componentName;
|
|
const container = this.j.c.span(cn);
|
|
const button = super.createContainer();
|
|
attr(container, 'role', 'listitem');
|
|
button.classList.remove(cn);
|
|
button.classList.add(cn + '__button');
|
|
Object.defineProperty(button, 'component', {
|
|
value: this
|
|
});
|
|
container.appendChild(button);
|
|
const trigger = this.j.c.fromHTML(`<span role="trigger" class="${cn}__trigger">${Icon.get('chevron')}</span>`);
|
|
// For caching
|
|
button.appendChild(trigger);
|
|
return container;
|
|
}
|
|
/** @override */
|
|
focus() {
|
|
var _a;
|
|
(_a = this.container.querySelector('button')) === null || _a === void 0 ? void 0 : _a.focus();
|
|
}
|
|
onChangeHasTrigger() {
|
|
if (this.state.hasTrigger) {
|
|
this.container.appendChild(this.trigger);
|
|
}
|
|
else {
|
|
Dom.safeRemove(this.trigger);
|
|
}
|
|
this.setMod('with-trigger', this.state.hasTrigger || null);
|
|
}
|
|
/** @override */
|
|
onChangeDisabled() {
|
|
const disabled = this.state.disabled ? 'disabled' : null;
|
|
attr(this.trigger, 'disabled', disabled);
|
|
attr(this.button, 'disabled', disabled);
|
|
attr(this.container, 'disabled', disabled);
|
|
}
|
|
constructor(jodit, control, target = null) {
|
|
super(jodit);
|
|
this.control = control;
|
|
this.target = target;
|
|
this.state = {
|
|
...UIButtonState(),
|
|
theme: 'toolbar',
|
|
currentValue: '',
|
|
hasTrigger: false
|
|
};
|
|
this.openedPopup = null;
|
|
const button = this.getElm('button');
|
|
assert(button, 'Element button should exists');
|
|
this.button = button;
|
|
Object.defineProperty(button, 'component', {
|
|
value: this,
|
|
configurable: true
|
|
});
|
|
const trigger = this.getElm('trigger');
|
|
assert(trigger, 'Element trigger should exists');
|
|
this.trigger = trigger;
|
|
trigger.remove();
|
|
// Prevent lost focus
|
|
jodit.e.on([this.button, this.trigger], 'mousedown', (e) => e.preventDefault());
|
|
this.onAction(this.onClick);
|
|
this.hookStatus(STATUSES.ready, () => {
|
|
this.__initFromControl();
|
|
this.update();
|
|
});
|
|
if (control.mods) {
|
|
Object.keys(control.mods).forEach(mod => {
|
|
control.mods && this.setMod(mod, control.mods[mod]);
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* Init constant data from control
|
|
*/
|
|
__initFromControl() {
|
|
var _a;
|
|
const { control: ctr, state } = this;
|
|
this.updateSize();
|
|
state.name = ctr.name;
|
|
const { textIcons } = this.j.o;
|
|
if (textIcons === true ||
|
|
(isFunction(textIcons) && textIcons(ctr.name)) ||
|
|
ctr.template) {
|
|
state.icon = UIButtonState().icon;
|
|
state.text = ctr.text || ctr.name;
|
|
}
|
|
else {
|
|
if (ctr.iconURL) {
|
|
state.icon.iconURL = ctr.iconURL;
|
|
}
|
|
else {
|
|
const name = ctr.icon || ctr.name;
|
|
state.icon.name =
|
|
Icon.exists(name) || ((_a = this.j.o.extraIcons) === null || _a === void 0 ? void 0 : _a[name])
|
|
? name
|
|
: '';
|
|
}
|
|
if (!ctr.iconURL && !state.icon.name) {
|
|
state.text = ctr.text || ctr.name;
|
|
}
|
|
}
|
|
if (ctr.tooltip) {
|
|
state.tooltip = this.j.i18n(isFunction(ctr.tooltip)
|
|
? ctr.tooltip(this.j, ctr, this)
|
|
: ctr.tooltip);
|
|
}
|
|
state.hasTrigger = Boolean(ctr.list || (ctr.popup && ctr.exec));
|
|
}
|
|
/**
|
|
* Click on trigger button
|
|
*/
|
|
onTriggerClick(e) {
|
|
var _a, _b, _c;
|
|
if (this.openedPopup) {
|
|
this.__closePopup();
|
|
return;
|
|
}
|
|
const { control: ctr } = this;
|
|
e.buffer = {
|
|
actionTrigger: this
|
|
};
|
|
if (ctr.list) {
|
|
return this.__openControlList(ctr);
|
|
}
|
|
if (isFunction(ctr.popup)) {
|
|
const popup = this.openPopup();
|
|
popup.parentElement = this;
|
|
try {
|
|
if (this.j.e.fire(camelCase(`before-${ctr.name}-open-popup`), this.target, ctr, popup) !== false) {
|
|
const target = (_c = (_b = (_a = this.toolbar) === null || _a === void 0 ? void 0 : _a.getTarget(this)) !== null && _b !== void 0 ? _b : this.target) !== null && _c !== void 0 ? _c : null;
|
|
const elm = ctr.popup(this.j, target, this.__closePopup, this);
|
|
if (elm) {
|
|
popup
|
|
.setContent(isString(elm) ? this.j.c.fromHTML(elm) : elm)
|
|
.open(() => position(this.container), false, this.j.o.allowTabNavigation
|
|
? this.container
|
|
: undefined);
|
|
}
|
|
else {
|
|
this.__closePopup();
|
|
}
|
|
}
|
|
}
|
|
catch (e) {
|
|
this.__closePopup();
|
|
throw e;
|
|
}
|
|
/**
|
|
* Fired after the popup was opened for some control button
|
|
*/
|
|
/**
|
|
* Close all opened popups
|
|
*/
|
|
this.j.e.fire(camelCase(`after-${ctr.name}-open-popup`), popup.container);
|
|
}
|
|
}
|
|
/**
|
|
* Create an open popup list
|
|
*/
|
|
__openControlList(control) {
|
|
var _a;
|
|
const controls = (_a = this.jodit.options.controls) !== null && _a !== void 0 ? _a : {}, getControl = (key) => findControlType(key, controls);
|
|
const list = control.list, menu = this.openPopup(), toolbar = makeCollection(this.j);
|
|
menu.parentElement = this;
|
|
toolbar.parentElement = menu;
|
|
toolbar.mode = 'vertical';
|
|
const isListItem = (key) => isPlainObject(key) && 'title' in key && 'value' in key;
|
|
const getButton = (key, value) => {
|
|
if (isString(value) && getControl(value)) {
|
|
return {
|
|
name: value.toString(),
|
|
...getControl(value)
|
|
};
|
|
}
|
|
if (isString(key) && getControl(key)) {
|
|
return {
|
|
name: key.toString(),
|
|
...getControl(key),
|
|
...(typeof value === 'object' ? value : {})
|
|
};
|
|
}
|
|
if (isListItem(key)) {
|
|
value = key.value;
|
|
key = key.title;
|
|
}
|
|
const { childTemplate } = control;
|
|
const childControl = {
|
|
name: key.toString(),
|
|
template: childTemplate &&
|
|
((j, k, v) => childTemplate(j, k, v, this)),
|
|
exec: control.childExec
|
|
? (view, current, options) => {
|
|
var _a;
|
|
return (_a = control.childExec) === null || _a === void 0 ? void 0 : _a.call(control, view, current, {
|
|
...options,
|
|
parentControl: control
|
|
});
|
|
}
|
|
: control.exec,
|
|
data: control.data,
|
|
command: control.command,
|
|
isActive: control.isChildActive,
|
|
value: control.value,
|
|
isDisabled: control.isChildDisabled,
|
|
mode: control.mode,
|
|
args: [...(control.args ? control.args : []), key, value]
|
|
};
|
|
if (isString(value)) {
|
|
childControl.text = value;
|
|
}
|
|
return childControl;
|
|
};
|
|
toolbar.build(isArray(list)
|
|
? list.map(getButton)
|
|
: keys(list, false).map(key => getButton(key, list[key])), this.target);
|
|
menu.setContent(toolbar).open(() => position(this.container), false, this.j.o.allowTabNavigation ? this.container : undefined);
|
|
this.state.activated = true;
|
|
}
|
|
onOutsideClick(e) {
|
|
if (!this.openedPopup) {
|
|
return;
|
|
}
|
|
if (!e ||
|
|
!Dom.isNode(e.target) ||
|
|
(!Dom.isOrContains(this.container, e.target) &&
|
|
!this.openedPopup.isOwnClick(e))) {
|
|
this.__closePopup();
|
|
}
|
|
}
|
|
openPopup() {
|
|
this.__closePopup();
|
|
this.openedPopup = new Popup(this.j, false);
|
|
this.j.e
|
|
.on(this.ow, 'mousedown touchstart', this.onOutsideClick)
|
|
.on('escape closeAllPopups', this.onOutsideClick);
|
|
return this.openedPopup;
|
|
}
|
|
__closePopup() {
|
|
if (this.openedPopup) {
|
|
this.j.e
|
|
.off(this.ow, 'mousedown touchstart', this.onOutsideClick)
|
|
.off('escape closeAllPopups', this.onOutsideClick);
|
|
this.state.activated = false;
|
|
this.openedPopup.close();
|
|
this.openedPopup.destruct();
|
|
this.openedPopup = null;
|
|
}
|
|
}
|
|
/**
|
|
* Click handler
|
|
*/
|
|
onClick(originalEvent) {
|
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
const { control: ctr } = this;
|
|
if (isFunction(ctr.exec)) {
|
|
const target = (_c = (_b = (_a = this.toolbar) === null || _a === void 0 ? void 0 : _a.getTarget(this)) !== null && _b !== void 0 ? _b : this.target) !== null && _c !== void 0 ? _c : null;
|
|
const result = ctr.exec(this.j, target, {
|
|
control: ctr,
|
|
originalEvent,
|
|
button: this
|
|
});
|
|
// For memorise exec
|
|
if (result !== false && result !== true) {
|
|
(_e = (_d = this.j) === null || _d === void 0 ? void 0 : _d.e) === null || _e === void 0 ? void 0 : _e.fire('synchro');
|
|
if (this.parentElement) {
|
|
this.parentElement.update();
|
|
}
|
|
/**
|
|
* Fired after calling `button.exec` function
|
|
*/
|
|
(_g = (_f = this.j) === null || _f === void 0 ? void 0 : _f.e) === null || _g === void 0 ? void 0 : _g.fire('closeAllPopups afterExec');
|
|
}
|
|
if (result !== false) {
|
|
return;
|
|
}
|
|
}
|
|
if (ctr.list) {
|
|
return this.__openControlList(ctr);
|
|
}
|
|
if (isFunction(ctr.popup)) {
|
|
return this.onTriggerClick(originalEvent);
|
|
}
|
|
if (ctr.command || ctr.name) {
|
|
call(isJoditObject(this.j)
|
|
? this.j.execCommand.bind(this.j)
|
|
: this.j.od.execCommand.bind(this.j.od), ctr.command || ctr.name, false, ctr.args && ctr.args[0]);
|
|
this.j.e.fire('closeAllPopups');
|
|
}
|
|
}
|
|
destruct() {
|
|
this.__closePopup();
|
|
return super.destruct();
|
|
}
|
|
};
|
|
__decorate([
|
|
cacheHTML
|
|
], ToolbarButton.prototype, "createContainer", null);
|
|
__decorate([
|
|
watch('state.hasTrigger', { immediately: false })
|
|
], ToolbarButton.prototype, "onChangeHasTrigger", null);
|
|
__decorate([
|
|
watch('trigger:click')
|
|
], ToolbarButton.prototype, "onTriggerClick", null);
|
|
__decorate([
|
|
autobind
|
|
], ToolbarButton.prototype, "onOutsideClick", null);
|
|
__decorate([
|
|
autobind
|
|
], ToolbarButton.prototype, "__closePopup", null);
|
|
ToolbarButton = __decorate([
|
|
component
|
|
], ToolbarButton);
|
|
export { ToolbarButton };
|