UNPKG

11.3 kBJavaScriptView Raw
1/**
2 * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4 */
5/**
6 * @module table/utils/ui/table-properties
7 */
8import { ButtonView, Model } from 'ckeditor5/src/ui';
9import { Collection } from 'ckeditor5/src/utils';
10import { isColor, isLength, isPercentage } from 'ckeditor5/src/engine';
11import ColorInputView from '../../ui/colorinputview';
12const isEmpty = (val) => val === '';
13/**
14 * Returns an object containing pairs of CSS border style values and their localized UI
15 * labels. Used by {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView}
16 * and {@link module:table/tableproperties/ui/tablepropertiesview~TablePropertiesView}.
17 *
18 * @param t The "t" function provided by the editor that is used to localize strings.
19 */
20export function getBorderStyleLabels(t) {
21 return {
22 none: t('None'),
23 solid: t('Solid'),
24 dotted: t('Dotted'),
25 dashed: t('Dashed'),
26 double: t('Double'),
27 groove: t('Groove'),
28 ridge: t('Ridge'),
29 inset: t('Inset'),
30 outset: t('Outset')
31 };
32}
33/**
34 * Returns a localized error string that can be displayed next to color (background, border)
35 * fields that have an invalid value.
36 *
37 * @param t The "t" function provided by the editor that is used to localize strings.
38 */
39export function getLocalizedColorErrorText(t) {
40 return t('The color is invalid. Try "#FF0000" or "rgb(255,0,0)" or "red".');
41}
42/**
43 * Returns a localized error string that can be displayed next to length (padding, border width)
44 * fields that have an invalid value.
45 *
46 * @param t The "t" function provided by the editor that is used to localize strings.
47 */
48export function getLocalizedLengthErrorText(t) {
49 return t('The value is invalid. Try "10px" or "2em" or simply "2".');
50}
51/**
52 * Returns `true` when the passed value is an empty string or a valid CSS color expression.
53 * Otherwise, `false` is returned.
54 *
55 * See {@link module:engine/view/styles/utils~isColor}.
56 */
57export function colorFieldValidator(value) {
58 value = value.trim();
59 return isEmpty(value) || isColor(value);
60}
61/**
62 * Returns `true` when the passed value is an empty string, a number without a unit or a valid CSS length expression.
63 * Otherwise, `false` is returned.
64 *
65 * See {@link module:engine/view/styles/utils~isLength}.
66 * See {@link module:engine/view/styles/utils~isPercentage}.
67 */
68export function lengthFieldValidator(value) {
69 value = value.trim();
70 return isEmpty(value) || isNumberString(value) || isLength(value) || isPercentage(value);
71}
72/**
73 * Returns `true` when the passed value is an empty string, a number without a unit or a valid CSS length expression.
74 * Otherwise, `false` is returned.
75 *
76 * See {@link module:engine/view/styles/utils~isLength}.
77 */
78export function lineWidthFieldValidator(value) {
79 value = value.trim();
80 return isEmpty(value) || isNumberString(value) || isLength(value);
81}
82/**
83 * Generates item definitions for a UI dropdown that allows changing the border style of a table or a table cell.
84 *
85 * @param defaultStyle The default border.
86 */
87export function getBorderStyleDefinitions(view, defaultStyle) {
88 const itemDefinitions = new Collection();
89 const styleLabels = getBorderStyleLabels(view.t);
90 for (const style in styleLabels) {
91 const definition = {
92 type: 'button',
93 model: new Model({
94 _borderStyleValue: style,
95 label: styleLabels[style],
96 role: 'menuitemradio',
97 withText: true
98 })
99 };
100 if (style === 'none') {
101 definition.model.bind('isOn').to(view, 'borderStyle', value => {
102 if (defaultStyle === 'none') {
103 return !value;
104 }
105 return value === style;
106 });
107 }
108 else {
109 definition.model.bind('isOn').to(view, 'borderStyle', value => {
110 return value === style;
111 });
112 }
113 itemDefinitions.add(definition);
114 }
115 return itemDefinitions;
116}
117/**
118 * A helper that fills a toolbar with buttons that:
119 *
120 * * have some labels,
121 * * have some icons,
122 * * set a certain UI view property value upon execution.
123 *
124 * @param nameToValue A function that maps a button name to a value. By default names are the same as values.
125 */
126export function fillToolbar(options) {
127 const { view, icons, toolbar, labels, propertyName, nameToValue, defaultValue } = options;
128 for (const name in labels) {
129 const button = new ButtonView(view.locale);
130 button.set({
131 label: labels[name],
132 icon: icons[name],
133 tooltip: labels[name]
134 });
135 // If specified the `nameToValue()` callback, map the value based on the option's name.
136 const buttonValue = nameToValue ? nameToValue(name) : name;
137 button.bind('isOn').to(view, propertyName, value => {
138 // `value` comes from `view[ propertyName ]`.
139 let valueToCompare = value;
140 // If it's empty, and the `defaultValue` is specified, use it instead.
141 if (value === '' && defaultValue) {
142 valueToCompare = defaultValue;
143 }
144 return buttonValue === valueToCompare;
145 });
146 button.on('execute', () => {
147 view[propertyName] = buttonValue;
148 });
149 toolbar.items.add(button);
150 }
151}
152/**
153 * A default color palette used by various user interfaces related to tables, for instance,
154 * by {@link module:table/tablecellproperties/tablecellpropertiesui~TableCellPropertiesUI} or
155 * {@link module:table/tableproperties/tablepropertiesui~TablePropertiesUI}.
156 *
157 * The color palette follows the {@link module:table/tableconfig~TableColorConfig table color configuration format}
158 * and contains the following color definitions:
159 *
160 * ```ts
161 * const defaultColors = [
162 * {
163 * color: 'hsl(0, 0%, 0%)',
164 * label: 'Black'
165 * },
166 * {
167 * color: 'hsl(0, 0%, 30%)',
168 * label: 'Dim grey'
169 * },
170 * {
171 * color: 'hsl(0, 0%, 60%)',
172 * label: 'Grey'
173 * },
174 * {
175 * color: 'hsl(0, 0%, 90%)',
176 * label: 'Light grey'
177 * },
178 * {
179 * color: 'hsl(0, 0%, 100%)',
180 * label: 'White',
181 * hasBorder: true
182 * },
183 * {
184 * color: 'hsl(0, 75%, 60%)',
185 * label: 'Red'
186 * },
187 * {
188 * color: 'hsl(30, 75%, 60%)',
189 * label: 'Orange'
190 * },
191 * {
192 * color: 'hsl(60, 75%, 60%)',
193 * label: 'Yellow'
194 * },
195 * {
196 * color: 'hsl(90, 75%, 60%)',
197 * label: 'Light green'
198 * },
199 * {
200 * color: 'hsl(120, 75%, 60%)',
201 * label: 'Green'
202 * },
203 * {
204 * color: 'hsl(150, 75%, 60%)',
205 * label: 'Aquamarine'
206 * },
207 * {
208 * color: 'hsl(180, 75%, 60%)',
209 * label: 'Turquoise'
210 * },
211 * {
212 * color: 'hsl(210, 75%, 60%)',
213 * label: 'Light blue'
214 * },
215 * {
216 * color: 'hsl(240, 75%, 60%)',
217 * label: 'Blue'
218 * },
219 * {
220 * color: 'hsl(270, 75%, 60%)',
221 * label: 'Purple'
222 * }
223 * ];
224 * ```
225 */
226export const defaultColors = [
227 {
228 color: 'hsl(0, 0%, 0%)',
229 label: 'Black'
230 },
231 {
232 color: 'hsl(0, 0%, 30%)',
233 label: 'Dim grey'
234 },
235 {
236 color: 'hsl(0, 0%, 60%)',
237 label: 'Grey'
238 },
239 {
240 color: 'hsl(0, 0%, 90%)',
241 label: 'Light grey'
242 },
243 {
244 color: 'hsl(0, 0%, 100%)',
245 label: 'White',
246 hasBorder: true
247 },
248 {
249 color: 'hsl(0, 75%, 60%)',
250 label: 'Red'
251 },
252 {
253 color: 'hsl(30, 75%, 60%)',
254 label: 'Orange'
255 },
256 {
257 color: 'hsl(60, 75%, 60%)',
258 label: 'Yellow'
259 },
260 {
261 color: 'hsl(90, 75%, 60%)',
262 label: 'Light green'
263 },
264 {
265 color: 'hsl(120, 75%, 60%)',
266 label: 'Green'
267 },
268 {
269 color: 'hsl(150, 75%, 60%)',
270 label: 'Aquamarine'
271 },
272 {
273 color: 'hsl(180, 75%, 60%)',
274 label: 'Turquoise'
275 },
276 {
277 color: 'hsl(210, 75%, 60%)',
278 label: 'Light blue'
279 },
280 {
281 color: 'hsl(240, 75%, 60%)',
282 label: 'Blue'
283 },
284 {
285 color: 'hsl(270, 75%, 60%)',
286 label: 'Purple'
287 }
288];
289/**
290 * Returns a creator for a color input with a label.
291 *
292 * For given options, it returns a function that creates an instance of a
293 * {@link module:table/ui/colorinputview~ColorInputView color input} logically related to
294 * a {@link module:ui/labeledfield/labeledfieldview~LabeledFieldView labeled view} in the DOM.
295 *
296 * The helper does the following:
297 *
298 * * It sets the color input `id` and `ariaDescribedById` attributes.
299 * * It binds the color input `isReadOnly` to the labeled view.
300 * * It binds the color input `hasError` to the labeled view.
301 * * It enables a logic that cleans up the error when the user starts typing in the color input.
302 *
303 * Usage:
304 *
305 * ```ts
306 * const colorInputCreator = getLabeledColorInputCreator( {
307 * colorConfig: [ ... ],
308 * columns: 3,
309 * } );
310 *
311 * const labeledInputView = new LabeledFieldView( locale, colorInputCreator );
312 * console.log( labeledInputView.view ); // A color input instance.
313 * ```
314 *
315 * @internal
316 * @param options Color input options.
317 * @param options.colorConfig The configuration of the color palette displayed in the input's dropdown.
318 * @param options.columns The configuration of the number of columns the color palette consists of in the input's dropdown.
319 * @param options.defaultColorValue If specified, the color input view will replace the "Remove color" button with
320 * the "Restore default" button. Instead of clearing the input field, the default color value will be set.
321 */
322export function getLabeledColorInputCreator(options) {
323 return (labeledFieldView, viewUid, statusUid) => {
324 const colorInputView = new ColorInputView(labeledFieldView.locale, {
325 colorDefinitions: colorConfigToColorGridDefinitions(options.colorConfig),
326 columns: options.columns,
327 defaultColorValue: options.defaultColorValue
328 });
329 colorInputView.inputView.set({
330 id: viewUid,
331 ariaDescribedById: statusUid
332 });
333 colorInputView.bind('isReadOnly').to(labeledFieldView, 'isEnabled', value => !value);
334 colorInputView.bind('hasError').to(labeledFieldView, 'errorText', value => !!value);
335 colorInputView.on('input', () => {
336 // UX: Make the error text disappear and disable the error indicator as the user
337 // starts fixing the errors.
338 labeledFieldView.errorText = null;
339 });
340 labeledFieldView.bind('isEmpty', 'isFocused').to(colorInputView);
341 return colorInputView;
342 };
343}
344/**
345 * A simple helper method to detect number strings.
346 * I allows full number notation, so omitting 0 is not allowed:
347 */
348function isNumberString(value) {
349 const parsedValue = parseFloat(value);
350 return !Number.isNaN(parsedValue) && value === String(parsedValue);
351}
352function colorConfigToColorGridDefinitions(colorConfig) {
353 return colorConfig.map(item => ({
354 color: item.model,
355 label: item.label,
356 options: {
357 hasBorder: item.hasBorder
358 }
359 }));
360}