@ckeditor/ckeditor5-font
Version:
Font feature for CKEditor 5.
1,673 lines (1,649 loc) • 49.7 kB
JavaScript
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
import { Command, Plugin } from "@ckeditor/ckeditor5-core";
import { ModelDocumentSelection, addBackgroundStylesRules, isLengthStyleValue, isPercentageStyleValue } from "@ckeditor/ckeditor5-engine";
import { ColorSelectorView, MenuBarMenuListItemButtonView, MenuBarMenuListItemView, MenuBarMenuListView, MenuBarMenuView, UIModel, addListToDropdown, createDropdown, focusChildOnDropdownOpen, getLocalizedColorOptions, normalizeColorOptions } from "@ckeditor/ckeditor5-ui";
import { CKEditorError, Collection } from "@ckeditor/ckeditor5-utils";
import { IconFontBackground, IconFontColor, IconFontFamily, IconFontSize } from "@ckeditor/ckeditor5-icons";
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontcommand
*/
/**
* The base font command.
*/
var FontCommand = class extends Command {
/**
* A model attribute on which this command operates.
*/
attributeKey;
/**
* Creates an instance of the command.
*
* @param editor Editor instance.
* @param attributeKey The name of a model attribute on which this command operates.
*/
constructor(editor, attributeKey) {
super(editor);
this.attributeKey = attributeKey;
}
/**
* @inheritDoc
*/
refresh() {
const model = this.editor.model;
const doc = model.document;
this.value = doc.selection.getAttribute(this.attributeKey);
this.isEnabled = model.schema.checkAttributeInSelection(doc.selection, this.attributeKey);
}
/**
* Executes the command. Applies the `value` of the {@link #attributeKey} to the selection.
* If no `value` is passed, it removes the attribute from the selection.
*
* @param options Options for the executed command.
* @param options.value The value to apply.
* @fires execute
*/
execute(options = {}) {
const model = this.editor.model;
const selection = model.document.selection;
const value = options.value;
const batch = options.batch;
const updateAttribute = (writer) => {
if (selection.isCollapsed) if (value) writer.setSelectionAttribute(this.attributeKey, value);
else writer.removeSelectionAttribute(this.attributeKey);
else {
const ranges = model.schema.getValidRanges(selection.getRanges(), this.attributeKey, { includeEmptyRanges: true });
for (const range of ranges) {
let itemOrRange = range;
let attributeKey = this.attributeKey;
if (range.isCollapsed) {
itemOrRange = range.start.parent;
attributeKey = ModelDocumentSelection._getStoreAttributeKey(this.attributeKey);
}
if (value) writer.setAttribute(attributeKey, value, itemOrRange);
else writer.removeAttribute(attributeKey, itemOrRange);
}
}
};
if (batch) model.enqueueChange(batch, (writer) => {
updateAttribute(writer);
});
else model.change((writer) => {
updateAttribute(writer);
});
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* The name of the font size plugin.
*/
const FONT_SIZE = "fontSize";
/**
* The name of the font family plugin.
*/
const FONT_FAMILY = "fontFamily";
/**
* The name of the font color plugin.
*/
const FONT_COLOR = "fontColor";
/**
* The name of the font background color plugin.
*/
const FONT_BACKGROUND_COLOR = "fontBackgroundColor";
/**
* Builds a proper converter definition out of input data.
*
* @internal
*/
function buildDefinition(modelAttributeKey, options) {
const definition = {
model: {
key: modelAttributeKey,
values: []
},
view: {},
upcastAlso: {}
};
for (const option of options) {
definition.model.values.push(option.model);
definition.view[option.model] = option.view;
if (option.upcastAlso) definition.upcastAlso[option.model] = option.upcastAlso;
}
return definition;
}
/**
* A {@link module:font/fontcolor~FontColor font color} and
* {@link module:font/fontbackgroundcolor~FontBackgroundColor font background color} helper
* responsible for upcasting data to the model.
*
* **Note**: The `styleAttr` parameter should be either `'color'` or `'background-color'`.
*
* @internal
*/
function renderUpcastAttribute(styleAttr) {
return (viewElement) => normalizeColorCode(viewElement.getStyle(styleAttr));
}
/**
* A {@link module:font/fontcolor~FontColor font color} and
* {@link module:font/fontbackgroundcolor~FontBackgroundColor font background color} helper
* responsible for downcasting a color attribute to a `<span>` element.
*
* **Note**: The `styleAttr` parameter should be either `'color'` or `'background-color'`.
*
* @internal
*/
function renderDowncastElement(styleAttr) {
return (modelAttributeValue, { writer }) => writer.createAttributeElement("span", { style: `${styleAttr}:${modelAttributeValue}` }, { priority: 7 });
}
/**
* A helper that adds {@link module:ui/colorselector/colorselectorview~ColorSelectorView} to the color dropdown with proper initial values.
*
* @param options Configuration options
* @param options.dropdownView The dropdown view to which a {@link module:ui/colorselector/colorselectorview~ColorSelectorView}
* will be added.
* @param options.colors An array with definitions representing colors to be displayed in the color selector.
* @param options.columns The number of columns in the color grid.
* @param options.removeButtonLabel The label for the button responsible for removing the color.
* @param options.colorPickerLabel The label for the color picker button.
* @param options.documentColorsLabel The label for the section with document colors.
* @param options.documentColorsCount The number of document colors inside the dropdown.
* @param options.colorPickerViewConfig Configuration of the color picker view.
* @returns The new color selector view.
* @internal
*/
function addColorSelectorToDropdown({ dropdownView, colors, columns, removeButtonLabel, colorPickerLabel, documentColorsLabel, documentColorsCount, colorPickerViewConfig }) {
const locale = dropdownView.locale;
const colorSelectorView = new ColorSelectorView(locale, {
colors,
columns,
removeButtonLabel,
colorPickerLabel,
documentColorsLabel,
documentColorsCount,
colorPickerViewConfig
});
dropdownView.colorSelectorView = colorSelectorView;
dropdownView.panelView.children.add(colorSelectorView);
return colorSelectorView;
}
/**
* Fixes the color value string.
*/
function normalizeColorCode(value) {
return value.replace(/\s/g, "");
}
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* The font family command. It is used by {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing}
* to apply the font family.
*
* ```ts
* editor.execute( 'fontFamily', { value: 'Arial' } );
* ```
*
* **Note**: Executing the command without the value removes the attribute from the model.
*/
var FontFamilyCommand = class extends FontCommand {
/**
* @inheritDoc
*/
constructor(editor) {
super(editor, FONT_FAMILY);
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* Normalizes the {@link module:font/fontconfig~FontFamilyConfig#options configuration options}
* to the {@link module:font/fontconfig~FontFamilyOption} format.
*
* @param configuredOptions An array of options taken from the configuration.
* @internal
*/
function normalizeOptions(configuredOptions) {
return configuredOptions.map(getOptionDefinition$1).filter((option) => option !== void 0);
}
/**
* Normalizes the CSS `font-family` property value to an array of unquoted and trimmed font faces.
*
* @internal
*/
function normalizeFontFamilies(fontDefinition) {
return fontDefinition.replace(/["']/g, "").split(",").map((name) => name.trim());
}
/**
* Returns an option definition either created from string shortcut.
* If object is passed then this method will return it without alternating it. Returns undefined for item than cannot be parsed.
*
*/
function getOptionDefinition$1(option) {
if (typeof option === "object") return option;
if (option === "default") return {
title: "Default",
model: void 0
};
if (typeof option !== "string") return;
return generateFontPreset(option);
}
/**
* Creates a predefined preset for pixel size. It deconstructs font-family like string into full configuration option.
* A font definition is passed as coma delimited set of font family names. Font names might be quoted.
*
* @param fontDefinition A font definition form configuration.
*/
function generateFontPreset(fontDefinition) {
const fontNames = normalizeFontFamilies(fontDefinition);
const firstFontName = fontNames[0];
const cssFontNames = fontNames.map(normalizeFontNameForCSS).join(", ");
return {
title: firstFontName,
model: cssFontNames,
view: {
name: "span",
styles: { "font-family": cssFontNames },
priority: 7
}
};
}
/**
* Normalizes font name for the style attribute. It adds braces (') if font name contains spaces.
*/
function normalizeFontNameForCSS(fontName) {
fontName = fontName.trim();
if (fontName.indexOf(" ") > 0) fontName = `'${fontName}'`;
return fontName;
}
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontfamily/fontfamilyediting
*/
/**
* The font family editing feature.
*
* It introduces the {@link module:font/fontfamily/fontfamilycommand~FontFamilyCommand command} and
* the `fontFamily` attribute in the {@link module:engine/model/model~Model model} which renders
* in the {@link module:engine/view/view view} as an inline `<span>` element (`<span style="font-family: Arial">`),
* depending on the {@link module:font/fontconfig~FontFamilyConfig configuration}.
*/
var FontFamilyEditing = class extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return "FontFamilyEditing";
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/
constructor(editor) {
super(editor);
editor.config.define(FONT_FAMILY, {
options: [
"default",
"Arial, Helvetica, sans-serif",
"Courier New, Courier, monospace",
"Georgia, serif",
"Lucida Sans Unicode, Lucida Grande, sans-serif",
"Tahoma, Geneva, sans-serif",
"Times New Roman, Times, serif",
"Trebuchet MS, Helvetica, sans-serif",
"Verdana, Geneva, sans-serif"
],
supportAllValues: false
});
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
editor.model.schema.extend("$text", { allowAttributes: FONT_FAMILY });
editor.model.schema.setAttributeProperties(FONT_FAMILY, {
isFormatting: true,
copyOnEnter: true
});
const definition = buildDefinition(FONT_FAMILY, normalizeOptions(editor.config.get("fontFamily.options")).filter((item) => item.model));
if (editor.config.get("fontFamily.supportAllValues")) {
this._prepareAnyValueConverters();
this._prepareCompatibilityConverter();
} else editor.conversion.attributeToElement(definition);
editor.commands.add(FONT_FAMILY, new FontFamilyCommand(editor));
}
/**
* These converters enable keeping any value found as `style="font-family: *"` as a value of an attribute on a text even
* if it is not defined in the plugin configuration.
*/
_prepareAnyValueConverters() {
const editor = this.editor;
editor.conversion.for("downcast").attributeToElement({
model: FONT_FAMILY,
view: (attributeValue, { writer }) => {
return writer.createAttributeElement("span", { style: "font-family:" + attributeValue }, { priority: 7 });
}
});
editor.conversion.for("upcast").elementToAttribute({
model: {
key: FONT_FAMILY,
value: (viewElement) => viewElement.getStyle("font-family")
},
view: {
name: "span",
styles: { "font-family": /.*/ }
}
});
}
/**
* Adds support for legacy `<font face="..">` formatting.
*/
_prepareCompatibilityConverter() {
this.editor.conversion.for("upcast").elementToAttribute({
view: {
name: "font",
attributes: { "face": /.*/ }
},
model: {
key: FONT_FAMILY,
value: (viewElement) => viewElement.getAttribute("face")
}
});
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontfamily/fontfamilyui
*/
/**
* The font family UI plugin. It introduces the `'fontFamily'` dropdown.
*/
var FontFamilyUI = class extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return "FontFamilyUI";
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
const options = this._getLocalizedOptions();
const command = editor.commands.get(FONT_FAMILY);
const accessibleLabel = t("Font Family");
const listOptions = _prepareListOptions$1(options, command);
editor.ui.componentFactory.add(FONT_FAMILY, (locale) => {
const dropdownView = createDropdown(locale);
addListToDropdown(dropdownView, listOptions, {
role: "menu",
ariaLabel: accessibleLabel
});
dropdownView.buttonView.set({
label: accessibleLabel,
icon: IconFontFamily,
tooltip: true
});
dropdownView.extendTemplate({ attributes: { class: "ck-font-family-dropdown" } });
dropdownView.bind("isEnabled").to(command);
this.listenTo(dropdownView, "execute", (evt) => {
editor.execute(evt.source.commandName, { value: evt.source.commandParam });
editor.editing.view.focus();
});
return dropdownView;
});
editor.ui.componentFactory.add(`menuBar:${FONT_FAMILY}`, (locale) => {
const menuView = new MenuBarMenuView(locale);
menuView.buttonView.set({
label: accessibleLabel,
icon: IconFontFamily
});
menuView.bind("isEnabled").to(command);
const listView = new MenuBarMenuListView(locale);
for (const definition of listOptions) {
const listItemView = new MenuBarMenuListItemView(locale, menuView);
const buttonView = new MenuBarMenuListItemButtonView(locale);
buttonView.set({
role: "menuitemradio",
isToggleable: true
});
buttonView.bind(...Object.keys(definition.model)).to(definition.model);
buttonView.delegate("execute").to(menuView);
buttonView.on("execute", () => {
editor.execute(definition.model.commandName, { value: definition.model.commandParam });
editor.editing.view.focus();
});
listItemView.children.add(buttonView);
listView.items.add(listItemView);
}
menuView.panelView.children.add(listView);
return menuView;
});
}
/**
* Returns options as defined in `config.fontFamily.options` but processed to account for
* editor localization, i.e. to display {@link module:font/fontconfig~FontFamilyOption}
* in the correct language.
*
* Note: The reason behind this method is that there is no way to use {@link module:utils/locale~Locale#t}
* when the user configuration is defined because the editor does not exist yet.
*/
_getLocalizedOptions() {
const editor = this.editor;
const t = editor.t;
return normalizeOptions(editor.config.get(FONT_FAMILY).options).map((option) => {
if (option.title === "Default") option.title = t("Default");
return option;
});
}
};
/**
* Prepares FontFamily dropdown items.
*/
function _prepareListOptions$1(options, command) {
const itemDefinitions = new Collection();
for (const option of options) {
const def = {
type: "button",
model: new UIModel({
commandName: FONT_FAMILY,
commandParam: option.model,
label: option.title,
role: "menuitemradio",
withText: true
})
};
def.model.bind("isOn").to(command, "value", (value) => {
if (value === option.model) return true;
if (!value || !option.model) return false;
return normalizeFontFamilies(value)[0].toLowerCase() === normalizeFontFamilies(option.model)[0].toLowerCase();
});
if (option.view && typeof option.view !== "string" && option.view.styles) def.model.set("labelStyle", `font-family: ${option.view.styles["font-family"]}`);
itemDefinitions.add(def);
}
return itemDefinitions;
}
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontfamily
*/
/**
* The font family plugin.
*
* For a detailed overview, check the {@glink features/font font feature} documentatiom
* and the {@glink api/font package page}.
*
* This is a "glue" plugin which loads the {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing} and
* {@link module:font/fontfamily/fontfamilyui~FontFamilyUI} features in the editor.
*/
var FontFamily = class extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [FontFamilyEditing, FontFamilyUI];
}
/**
* @inheritDoc
*/
static get pluginName() {
return "FontFamily";
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* The font size command. It is used by {@link module:font/fontsize/fontsizeediting~FontSizeEditing}
* to apply the font size.
*
* ```ts
* editor.execute( 'fontSize', { value: 'small' } );
* ```
*
* **Note**: Executing the command without the value removes the attribute from the model.
*/
var FontSizeCommand = class extends FontCommand {
/**
* @inheritDoc
*/
constructor(editor) {
super(editor, FONT_SIZE);
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontsize/utils
*/
/**
* Normalizes and translates the {@link module:font/fontconfig~FontSizeConfig#options configuration options}
* to the {@link module:font/fontconfig~FontSizeOption} format.
*
* @param configuredOptions An array of options taken from the configuration.
* @internal
*/
function normalizeOptions$1(configuredOptions) {
return configuredOptions.map((item) => getOptionDefinition(item)).filter((option) => option !== void 0);
}
const namedPresets = {
get tiny() {
return {
title: "Tiny",
model: "tiny",
view: {
name: "span",
classes: "text-tiny",
priority: 7
}
};
},
get small() {
return {
title: "Small",
model: "small",
view: {
name: "span",
classes: "text-small",
priority: 7
}
};
},
get big() {
return {
title: "Big",
model: "big",
view: {
name: "span",
classes: "text-big",
priority: 7
}
};
},
get huge() {
return {
title: "Huge",
model: "huge",
view: {
name: "span",
classes: "text-huge",
priority: 7
}
};
}
};
/**
* Returns an option definition either from preset or creates one from number shortcut.
* If object is passed then this method will return it without alternating it. Returns undefined for item than cannot be parsed.
*/
function getOptionDefinition(option) {
if (typeof option === "number") option = String(option);
if (typeof option === "object" && isFullItemDefinition(option)) return attachPriority(option);
const preset = findPreset(option);
if (preset) return attachPriority(preset);
if (option === "default") return {
model: void 0,
title: "Default"
};
if (isNumericalDefinition(option)) return;
return generatePixelPreset(option);
}
/**
* Creates a predefined preset for pixel size.
* @param definition Font size in pixels.
* @returns
*/
function generatePixelPreset(definition) {
if (typeof definition === "string") definition = {
title: definition,
model: `${parseFloat(definition)}px`
};
definition.view = {
name: "span",
styles: { "font-size": definition.model }
};
return attachPriority(definition);
}
/**
* Adds the priority to the view element definition if missing. It's required due to https://github.com/ckeditor/ckeditor5/issues/2291
*/
function attachPriority(definition) {
if (definition.view && typeof definition.view !== "string" && !definition.view.priority) definition.view.priority = 7;
return definition;
}
/**
* Returns a prepared preset definition. If passed an object, a name of preset should be defined as `model` value.
*
* @param definition.model A preset name.
*/
function findPreset(definition) {
return typeof definition === "string" ? namedPresets[definition] : namedPresets[definition.model];
}
/**
* We treat `definition` as completed if it is an object that contains `title`, `model` and `view` values.
*/
function isFullItemDefinition(definition) {
return definition.title && definition.model && definition.view;
}
function isNumericalDefinition(definition) {
let numberValue;
if (typeof definition === "object") if (!definition.model)
/**
* Provided value as an option for {@link module:font/fontsize~FontSize} seems to invalid.
*
* See valid examples described in the {@link module:font/fontconfig~FontSizeConfig#options plugin configuration}.
*
* @error font-size-invalid-definition
*/
throw new CKEditorError("font-size-invalid-definition", null, definition);
else numberValue = parseFloat(definition.model);
else numberValue = parseFloat(definition);
return isNaN(numberValue);
}
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontsize/fontsizeediting
*/
const styleFontSize = [
"x-small",
"x-small",
"small",
"medium",
"large",
"x-large",
"xx-large",
"xxx-large"
];
/**
* The font size editing feature.
*
* It introduces the {@link module:font/fontsize/fontsizecommand~FontSizeCommand command} and the `fontSize`
* attribute in the {@link module:engine/model/model~Model model} which renders in the {@link module:engine/view/view view}
* as a `<span>` element with either:
* * a style attribute (`<span style="font-size:12px">...</span>`),
* * or a class attribute (`<span class="text-small">...</span>`)
*
* depending on the {@link module:font/fontconfig~FontSizeConfig configuration}.
*/
var FontSizeEditing = class extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return "FontSizeEditing";
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/
constructor(editor) {
super(editor);
editor.config.define(FONT_SIZE, {
options: [
"tiny",
"small",
"default",
"big",
"huge"
],
supportAllValues: false
});
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
editor.model.schema.extend("$text", { allowAttributes: FONT_SIZE });
editor.model.schema.setAttributeProperties(FONT_SIZE, {
isFormatting: true,
copyOnEnter: true
});
const supportAllValues = editor.config.get("fontSize.supportAllValues");
const definition = buildDefinition(FONT_SIZE, normalizeOptions$1(this.editor.config.get("fontSize.options")).filter((item) => item.model));
if (supportAllValues) {
this._prepareAnyValueConverters(definition);
this._prepareCompatibilityConverter();
} else editor.conversion.attributeToElement(definition);
editor.commands.add(FONT_SIZE, new FontSizeCommand(editor));
}
/**
* These converters enable keeping any value found as `style="font-size: *"` as a value of an attribute on a text even
* if it is not defined in the plugin configuration.
*
* @param definition Converter definition out of input data.
*/
_prepareAnyValueConverters(definition) {
const editor = this.editor;
const presets = definition.model.values.filter((value) => {
return !isLengthStyleValue(String(value)) && !isPercentageStyleValue(String(value));
});
if (presets.length)
/**
* If {@link module:font/fontconfig~FontSizeConfig#supportAllValues `config.fontSize.supportAllValues`} is `true`,
* you need to use numerical values as font size options.
*
* See valid examples described in the {@link module:font/fontconfig~FontSizeConfig#options plugin configuration}.
*
* @error font-size-invalid-use-of-named-presets
* @param {Array.<string>} presets Invalid values.
*/
throw new CKEditorError("font-size-invalid-use-of-named-presets", null, { presets });
editor.conversion.for("downcast").attributeToElement({
model: FONT_SIZE,
view: (attributeValue, { writer }) => {
if (!attributeValue) return;
return writer.createAttributeElement("span", { style: "font-size:" + attributeValue }, { priority: 7 });
}
});
editor.conversion.for("upcast").elementToAttribute({
model: {
key: FONT_SIZE,
value: (viewElement) => viewElement.getStyle("font-size")
},
view: {
name: "span",
styles: { "font-size": /.*/ }
}
});
}
/**
* Adds support for legacy `<font size="..">` formatting.
*/
_prepareCompatibilityConverter() {
this.editor.conversion.for("upcast").elementToAttribute({
view: {
name: "font",
attributes: { "size": /^[+-]?\d{1,3}$/ }
},
model: {
key: FONT_SIZE,
value: (viewElement) => {
const value = viewElement.getAttribute("size");
const isRelative = value[0] === "-" || value[0] === "+";
let size = parseInt(value, 10);
if (isRelative) size = 3 + size;
const maxSize = styleFontSize.length - 1;
return styleFontSize[Math.min(Math.max(size, 0), maxSize)];
}
}
});
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontsize/fontsizeui
*/
/**
* The font size UI plugin. It introduces the `'fontSize'` dropdown.
*/
var FontSizeUI = class extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return "FontSizeUI";
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
const options = this._getLocalizedOptions();
const command = editor.commands.get(FONT_SIZE);
const accessibleLabel = t("Font Size");
const listOptions = _prepareListOptions(options, command);
editor.ui.componentFactory.add(FONT_SIZE, (locale) => {
const dropdownView = createDropdown(locale);
addListToDropdown(dropdownView, listOptions, {
role: "menu",
ariaLabel: accessibleLabel
});
dropdownView.buttonView.set({
label: accessibleLabel,
icon: IconFontSize,
tooltip: true
});
dropdownView.extendTemplate({ attributes: { class: ["ck-font-size-dropdown"] } });
dropdownView.bind("isEnabled").to(command);
this.listenTo(dropdownView, "execute", (evt) => {
editor.execute(evt.source.commandName, { value: evt.source.commandParam });
editor.editing.view.focus();
});
return dropdownView;
});
editor.ui.componentFactory.add(`menuBar:${FONT_SIZE}`, (locale) => {
const menuView = new MenuBarMenuView(locale);
menuView.buttonView.set({
label: accessibleLabel,
icon: IconFontSize
});
menuView.bind("isEnabled").to(command);
const listView = new MenuBarMenuListView(locale);
for (const definition of listOptions) {
const listItemView = new MenuBarMenuListItemView(locale, menuView);
const buttonView = new MenuBarMenuListItemButtonView(locale);
buttonView.set({
role: "menuitemradio",
isToggleable: true
});
buttonView.bind(...Object.keys(definition.model)).to(definition.model);
buttonView.delegate("execute").to(menuView);
buttonView.on("execute", () => {
editor.execute(definition.model.commandName, { value: definition.model.commandParam });
editor.editing.view.focus();
});
listItemView.children.add(buttonView);
listView.items.add(listItemView);
}
menuView.panelView.children.add(listView);
return menuView;
});
}
/**
* Returns options as defined in `config.fontSize.options` but processed to account for
* editor localization, i.e. to display {@link module:font/fontconfig~FontSizeOption}
* in the correct language.
*
* Note: The reason behind this method is that there is no way to use {@link module:utils/locale~Locale#t}
* when the user configuration is defined because the editor does not exist yet.
*/
_getLocalizedOptions() {
const editor = this.editor;
const t = editor.t;
const localizedTitles = {
Default: t("Default"),
Tiny: t("Tiny"),
Small: t("Small"),
Big: t("Big"),
Huge: t("Huge")
};
return normalizeOptions$1(editor.config.get(FONT_SIZE).options).map((option) => {
const title = localizedTitles[option.title];
if (title && title != option.title) option = Object.assign({}, option, { title });
return option;
});
}
};
/**
* Prepares FontSize dropdown items.
*/
function _prepareListOptions(options, command) {
const itemDefinitions = new Collection();
for (const option of options) {
const def = {
type: "button",
model: new UIModel({
commandName: FONT_SIZE,
commandParam: option.model,
label: option.title,
class: "ck-fontsize-option",
role: "menuitemradio",
withText: true
})
};
if (option.view && typeof option.view !== "string") {
if (option.view.styles) def.model.set("labelStyle", `font-size:${option.view.styles["font-size"]}`);
if (option.view.classes) def.model.set("class", `${def.model.class} ${option.view.classes}`);
}
def.model.bind("isOn").to(command, "value", (value) => value === option.model);
itemDefinitions.add(def);
}
return itemDefinitions;
}
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontsize
*/
/**
* The font size plugin.
*
* For a detailed overview, check the {@glink features/font font feature} documentation
* and the {@glink api/font package page}.
*
* This is a "glue" plugin which loads the {@link module:font/fontsize/fontsizeediting~FontSizeEditing} and
* {@link module:font/fontsize/fontsizeui~FontSizeUI} features in the editor.
*/
var FontSize = class extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [FontSizeEditing, FontSizeUI];
}
/**
* @inheritDoc
*/
static get pluginName() {
return "FontSize";
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
/**
* Normalizes and translates the {@link module:font/fontconfig~FontSizeConfig#options configuration options}
* to the {@link module:font/fontconfig~FontSizeOption} format.
*
* @param options An array of options taken from the configuration.
*/
normalizeSizeOptions(options) {
return normalizeOptions$1(options);
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* The font color command. It is used by {@link module:font/fontcolor/fontcolorediting~FontColorEditing}
* to apply the font color.
*
* ```ts
* editor.execute( 'fontColor', { value: 'rgb(250, 20, 20)' } );
* ```
*
* **Note**: Executing the command with the `null` value removes the attribute from the model.
*/
var FontColorCommand = class extends FontCommand {
/**
* @inheritDoc
*/
constructor(editor) {
super(editor, FONT_COLOR);
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontcolor/fontcolorediting
*/
/**
* The font color editing feature.
*
* It introduces the {@link module:font/fontcolor/fontcolorcommand~FontColorCommand command} and
* the `fontColor` attribute in the {@link module:engine/model/model~Model model} which renders
* in the {@link module:engine/view/view view} as a `<span>` element (`<span style="color: ...">`),
* depending on the {@link module:font/fontconfig~FontColorConfig configuration}.
*/
var FontColorEditing = class extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return "FontColorEditing";
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/
constructor(editor) {
super(editor);
editor.config.define(FONT_COLOR, {
colors: [
{
color: "hsl(0, 0%, 0%)",
label: "Black"
},
{
color: "hsl(0, 0%, 30%)",
label: "Dim grey"
},
{
color: "hsl(0, 0%, 60%)",
label: "Grey"
},
{
color: "hsl(0, 0%, 90%)",
label: "Light grey"
},
{
color: "hsl(0, 0%, 100%)",
label: "White",
hasBorder: true
},
{
color: "hsl(0, 75%, 60%)",
label: "Red"
},
{
color: "hsl(30, 75%, 60%)",
label: "Orange"
},
{
color: "hsl(60, 75%, 60%)",
label: "Yellow"
},
{
color: "hsl(90, 75%, 60%)",
label: "Light green"
},
{
color: "hsl(120, 75%, 60%)",
label: "Green"
},
{
color: "hsl(150, 75%, 60%)",
label: "Aquamarine"
},
{
color: "hsl(180, 75%, 60%)",
label: "Turquoise"
},
{
color: "hsl(210, 75%, 60%)",
label: "Light blue"
},
{
color: "hsl(240, 75%, 60%)",
label: "Blue"
},
{
color: "hsl(270, 75%, 60%)",
label: "Purple"
}
],
columns: 5
});
editor.conversion.for("upcast").elementToAttribute({
view: {
name: "span",
styles: { "color": /[\s\S]+/ }
},
model: {
key: FONT_COLOR,
value: renderUpcastAttribute("color")
}
});
editor.conversion.for("upcast").elementToAttribute({
view: {
name: "font",
attributes: { "color": /^#?\w+$/ }
},
model: {
key: FONT_COLOR,
value: (viewElement) => viewElement.getAttribute("color")
}
});
editor.conversion.for("downcast").attributeToElement({
model: FONT_COLOR,
view: renderDowncastElement("color")
});
editor.commands.add(FONT_COLOR, new FontColorCommand(editor));
editor.model.schema.extend("$text", { allowAttributes: FONT_COLOR });
editor.model.schema.setAttributeProperties(FONT_COLOR, {
isFormatting: true,
copyOnEnter: true
});
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/ui/colorui
*/
/**
* The color UI plugin which isolates the common logic responsible for displaying dropdowns with color grids.
*
* It is used to create the `'fontBackgroundColor'` and `'fontColor'` dropdowns, each hosting
* a {@link module:ui/colorselector/colorselectorview~ColorSelectorView}.
*/
var FontColorUIBase = class extends Plugin {
/**
* The name of the command which will be executed when a color tile is clicked.
*/
commandName;
/**
* The name of this component in the {@link module:ui/componentfactory~ComponentFactory}.
* Also the configuration scope name in `editor.config`.
*/
componentName;
/**
* The SVG icon used by the dropdown.
*/
icon;
/**
* The label used by the dropdown.
*/
dropdownLabel;
/**
* The number of columns in the color grid.
*/
columns;
/**
* Creates a plugin which introduces a dropdown with a pre–configured
* {@link module:ui/colorselector/colorselectorview~ColorSelectorView}.
*
* @param editor An editor instance.
* @param config The configuration object.
* @param config.commandName The name of the command which will be executed when a color tile is clicked.
* @param config.componentName The name of the dropdown in the {@link module:ui/componentfactory~ComponentFactory}
* and the configuration scope name in `editor.config`.
* @param config.icon The SVG icon used by the dropdown.
* @param config.dropdownLabel The label used by the dropdown.
*/
constructor(editor, { commandName, componentName, icon, dropdownLabel }) {
super(editor);
this.commandName = commandName;
this.componentName = componentName;
this.icon = icon;
this.dropdownLabel = dropdownLabel;
this.columns = editor.config.get(`${this.componentName}.columns`);
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const locale = editor.locale;
const t = locale.t;
const command = editor.commands.get(this.commandName);
const componentConfig = editor.config.get(this.componentName);
const localizedColors = getLocalizedColorOptions(locale, normalizeColorOptions(componentConfig.colors));
const documentColorsCount = componentConfig.documentColors;
const hasColorPicker = componentConfig.colorPicker !== false;
editor.ui.componentFactory.add(this.componentName, (locale) => {
const dropdownView = createDropdown(locale);
let dropdownContentRendered = false;
const colorSelectorView = addColorSelectorToDropdown({
dropdownView,
colors: localizedColors.map((option) => ({
label: option.label,
color: option.model,
options: { hasBorder: option.hasBorder }
})),
columns: this.columns,
removeButtonLabel: t("Remove color"),
colorPickerLabel: t("Color picker"),
documentColorsLabel: documentColorsCount !== 0 ? t("Document colors") : "",
documentColorsCount: documentColorsCount === void 0 ? this.columns : documentColorsCount,
colorPickerViewConfig: hasColorPicker ? componentConfig.colorPicker || {} : false
});
colorSelectorView.bind("selectedColor").to(command, "value");
dropdownView.buttonView.set({
label: this.dropdownLabel,
icon: this.icon,
tooltip: true
});
dropdownView.extendTemplate({ attributes: { class: "ck-color-ui-dropdown" } });
dropdownView.bind("isEnabled").to(command);
colorSelectorView.on("execute", (evt, data) => {
if (dropdownView.isOpen) editor.execute(this.commandName, {
value: data.value,
batch: this._undoStepBatch
});
if (data.source !== "colorPicker") editor.editing.view.focus();
if (data.source === "colorPickerSaveButton") dropdownView.isOpen = false;
});
colorSelectorView.on("colorPicker:show", () => {
this._undoStepBatch = editor.model.createBatch();
});
colorSelectorView.on("colorPicker:cancel", () => {
if (this._undoStepBatch.operations.length) {
dropdownView.isOpen = false;
editor.execute("undo", this._undoStepBatch);
}
editor.editing.view.focus();
});
dropdownView.on("change:isOpen", (evt, name, isVisible) => {
if (!dropdownContentRendered) {
dropdownContentRendered = true;
dropdownView.colorSelectorView.appendUI();
}
if (isVisible) {
if (documentColorsCount !== 0) colorSelectorView.updateDocumentColors(editor.model, this.componentName);
colorSelectorView.updateSelectedColors();
colorSelectorView.showColorGridsFragment();
}
});
focusChildOnDropdownOpen(dropdownView, () => dropdownView.colorSelectorView.colorGridsFragmentView.staticColorsGrid.items.find((item) => item.isOn));
return dropdownView;
});
editor.ui.componentFactory.add(`menuBar:${this.componentName}`, (locale) => {
const menuView = new MenuBarMenuView(locale);
menuView.buttonView.set({
label: this.dropdownLabel,
icon: this.icon
});
menuView.bind("isEnabled").to(command);
let contentRendered = false;
const colorSelectorView = new ColorSelectorView(locale, {
colors: localizedColors.map((option) => ({
label: option.label,
color: option.model,
options: { hasBorder: option.hasBorder }
})),
columns: this.columns,
removeButtonLabel: t("Remove color"),
colorPickerLabel: t("Color picker"),
documentColorsLabel: documentColorsCount !== 0 ? t("Document colors") : "",
documentColorsCount: documentColorsCount === void 0 ? this.columns : documentColorsCount,
colorPickerViewConfig: false
});
colorSelectorView.bind("selectedColor").to(command, "value");
colorSelectorView.delegate("execute").to(menuView);
colorSelectorView.on("execute", (evt, data) => {
editor.execute(this.commandName, {
value: data.value,
batch: this._undoStepBatch
});
editor.editing.view.focus();
});
menuView.on("change:isOpen", (evt, name, isVisible) => {
if (!contentRendered) {
contentRendered = true;
colorSelectorView.appendUI();
}
if (isVisible) {
if (documentColorsCount !== 0) colorSelectorView.updateDocumentColors(editor.model, this.componentName);
colorSelectorView.updateSelectedColors();
colorSelectorView.showColorGridsFragment();
}
});
menuView.panelView.children.add(colorSelectorView);
return menuView;
});
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontcolor/fontcolorui
*/
/**
* The font color UI plugin. It introduces the `'fontColor'` dropdown.
*/
var FontColorUI = class extends FontColorUIBase {
/**
* @inheritDoc
*/
constructor(editor) {
const t = editor.locale.t;
super(editor, {
commandName: FONT_COLOR,
componentName: FONT_COLOR,
icon: IconFontColor,
dropdownLabel: t("Font Color")
});
}
/**
* @inheritDoc
*/
static get pluginName() {
return "FontColorUI";
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontcolor
*/
/**
* The font color plugin.
*
* For a detailed overview, check the {@glink features/font font feature} documentation
* and the {@glink api/font package page}.
*
* This is a "glue" plugin which loads the {@link module:font/fontcolor/fontcolorediting~FontColorEditing} and
* {@link module:font/fontcolor/fontcolorui~FontColorUI} features in the editor.
*/
var FontColor = class extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [FontColorEditing, FontColorUI];
}
/**
* @inheritDoc
*/
static get pluginName() {
return "FontColor";
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* The font background color command. It is used by
* {@link module:font/fontbackgroundcolor/fontbackgroundcolorediting~FontBackgroundColorEditing}
* to apply the font background color.
*
* ```ts
* editor.execute( 'fontBackgroundColor', { value: 'rgb(250, 20, 20)' } );
* ```
*
* **Note**: Executing the command with the `null` value removes the attribute from the model.
*/
var FontBackgroundColorCommand = class extends FontCommand {
/**
* @inheritDoc
*/
constructor(editor) {
super(editor, FONT_BACKGROUND_COLOR);
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontbackgroundcolor/fontbackgroundcolorediting
*/
/**
* The font background color editing feature.
*
* It introduces the {@link module:font/fontbackgroundcolor/fontbackgroundcolorcommand~FontBackgroundColorCommand command} and
* the `fontBackgroundColor` attribute in the {@link module:engine/model/model~Model model} which renders
* in the {@link module:engine/view/view view} as a `<span>` element (`<span style="background-color: ...">`),
* depending on the {@link module:font/fontconfig~FontColorConfig configuration}.
*/
var FontBackgroundColorEditing = class extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return "FontBackgroundColorEditing";
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/
constructor(editor) {
super(editor);
editor.config.define(FONT_BACKGROUND_COLOR, {
colors: [
{
color: "hsl(0, 0%, 0%)",
label: "Black"
},
{
color: "hsl(0, 0%, 30%)",
label: "Dim grey"
},
{
color: "hsl(0, 0%, 60%)",
label: "Grey"
},
{
color: "hsl(0, 0%, 90%)",
label: "Light grey"
},
{
color: "hsl(0, 0%, 100%)",
label: "White",
hasBorder: true
},
{
color: "hsl(0, 75%, 60%)",
label: "Red"
},
{
color: "hsl(30, 75%, 60%)",
label: "Orange"
},
{
color: "hsl(60, 75%, 60%)",
label: "Yellow"
},
{
color: "hsl(90, 75%, 60%)",
label: "Light green"
},
{
color: "hsl(120, 75%, 60%)",
label: "Green"
},
{
color: "hsl(150, 75%, 60%)",
label: "Aquamarine"
},
{
color: "hsl(180, 75%, 60%)",
label: "Turquoise"
},
{
color: "hsl(210, 75%, 60%)",
label: "Light blue"
},
{
color: "hsl(240, 75%, 60%)",
label: "Blue"
},
{
color: "hsl(270, 75%, 60%)",
label: "Purple"
}
],
columns: 5
});
editor.data.addStyleProcessorRules(addBackgroundStylesRules);
editor.conversion.for("upcast").elementToAttribute({
view: {
name: "span",
styles: { "background-color": /[\s\S]+/ }
},
model: {
key: FONT_BACKGROUND_COLOR,
value: renderUpcastAttribute("background-color")
}
});
editor.conversion.for("downcast").attributeToElement({
model: FONT_BACKGROUND_COLOR,
view: renderDowncastElement("background-color")
});
editor.commands.add(FONT_BACKGROUND_COLOR, new FontBackgroundColorCommand(editor));
editor.model.schema.extend("$text", { allowAttributes: FONT_BACKGROUND_COLOR });
editor.model.schema.setAttributeProperties(FONT_BACKGROUND_COLOR, {
isFormatting: true,
copyOnEnter: true
});
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontbackgroundcolor/fontbackgroundcolorui
*/
/**
* The font background color UI plugin. It introduces the `'fontBackgroundColor'` dropdown.
*/
var FontBackgroundColorUI = class extends FontColorUIBase {
/**
* @inheritDoc
*/
constructor(editor) {
const t = editor.locale.t;
super(editor, {
commandName: FONT_BACKGROUND_COLOR,
componentName: FONT_BACKGROUND_COLOR,
icon: IconFontBackground,
dropdownLabel: t("Font Background Color")
});
}
/**
* @inheritDoc
*/
static get pluginName() {
return "FontBackgroundColorUI";
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/fontbackgroundcolor
*/
/**
* The font background color plugin.
*
* For a detailed overview, check the {@glink features/font font feature} documentation
* and the {@glink api/font package page}.
*
* This is a "glue" plugin which loads
* the {@link module:font/fontbackgroundcolor/fontbackgroundcolorediting~FontBackgroundColorEditing} and
* {@link module:font/fontbackgroundcolor/fontbackgroundcolorui~FontBackgroundColorUI} features in the editor.
*/
var FontBackgroundColor = class extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [FontBackgroundColorEditing, FontBackgroundColorUI];
}
/**
* @inheritDoc
*/
static get pluginName() {
return "FontBackgroundColor";
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module font/font
*/
/**
* A plugin that enables a set of text styling features:
*
* * {@link module:font/fontsize~FontSize},
* * {@link module:font/fontfamily~FontFamily}.
* * {@link module:font/fontcolor~FontColor},
* * {@link module:font/fontbackgroundcolor~FontBackgroundColor}.
*
* For a detailed overview, check the {@glink features/font Font feature} documentation
* and the {@glink api/font package page}.
*/
var Font = class extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [
FontFamily,
FontSize,
FontColor,
FontBackgroundColor
];
}
/**
* @inheritDoc
*/
static get pluginName() {
return "Font";
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
};
/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
export { Font, FontBackgroundColor, FontBackgroundColorCommand, FontBackgroundColorEditing, FontBackgroundColorUI, FontColor, FontColorCommand, FontColorEditing, FontColorUI, FontColorUIBase, FontCommand, FontFamily, FontFamilyCommand, FontFamilyEditing, FontFamilyUI, FontSize, FontSizeCommand, FontSizeEditing, FontSizeUI, addColorSelectorToDropdown as _addFontColorSelectorToDropdown, buildDefinition as _buildFontDefinition, normalizeOptions as _normalizeFontFamilyOptions, normalizeOptions$1 as _normalizeFontSizeOptions, renderDowncastElement as _renderDowncastFontElement, renderUpcastAttribute as _renderUpcastFontColorAttribute };
//# sourceMappingURL=index.js.map