jodit 4.17.5 → 4.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/es2015/jodit.css +87 -3
  3. package/es2015/jodit.fat.min.css +1 -1
  4. package/es2015/jodit.fat.min.js +9 -9
  5. package/es2015/jodit.js +873 -163
  6. package/es2015/jodit.min.css +1 -1
  7. package/es2015/jodit.min.js +16 -16
  8. package/es2015/plugins/debug/debug.css +1 -1
  9. package/es2015/plugins/debug/debug.js +1 -1
  10. package/es2015/plugins/debug/debug.min.js +1 -1
  11. package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
  12. package/es2015/plugins/speech-recognize/speech-recognize.js +1 -1
  13. package/es2015/plugins/speech-recognize/speech-recognize.min.js +1 -1
  14. package/es2018/jodit.fat.min.css +1 -1
  15. package/es2018/jodit.fat.min.js +9 -9
  16. package/es2018/jodit.min.css +1 -1
  17. package/es2018/jodit.min.js +9 -9
  18. package/es2018/plugins/debug/debug.min.js +1 -1
  19. package/es2018/plugins/speech-recognize/speech-recognize.min.js +1 -1
  20. package/es2021/jodit.css +87 -3
  21. package/es2021/jodit.fat.min.css +1 -1
  22. package/es2021/jodit.fat.min.js +16 -16
  23. package/es2021/jodit.js +851 -163
  24. package/es2021/jodit.min.css +1 -1
  25. package/es2021/jodit.min.js +16 -16
  26. package/es2021/plugins/debug/debug.css +1 -1
  27. package/es2021/plugins/debug/debug.js +1 -1
  28. package/es2021/plugins/debug/debug.min.js +1 -1
  29. package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
  30. package/es2021/plugins/speech-recognize/speech-recognize.js +1 -1
  31. package/es2021/plugins/speech-recognize/speech-recognize.min.js +1 -1
  32. package/es2021.en/jodit.css +87 -3
  33. package/es2021.en/jodit.fat.min.css +1 -1
  34. package/es2021.en/jodit.fat.min.js +15 -15
  35. package/es2021.en/jodit.js +851 -163
  36. package/es2021.en/jodit.min.css +1 -1
  37. package/es2021.en/jodit.min.js +15 -15
  38. package/es2021.en/plugins/debug/debug.css +1 -1
  39. package/es2021.en/plugins/debug/debug.js +1 -1
  40. package/es2021.en/plugins/debug/debug.min.js +1 -1
  41. package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
  42. package/es2021.en/plugins/speech-recognize/speech-recognize.js +1 -1
  43. package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +1 -1
  44. package/es5/jodit.css +92 -6
  45. package/es5/jodit.fat.min.css +1 -1
  46. package/es5/jodit.fat.min.js +2 -2
  47. package/es5/jodit.js +1141 -223
  48. package/es5/jodit.min.css +3 -3
  49. package/es5/jodit.min.js +2 -2
  50. package/es5/plugins/debug/debug.css +1 -1
  51. package/es5/plugins/debug/debug.js +1 -1
  52. package/es5/plugins/debug/debug.min.js +1 -1
  53. package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
  54. package/es5/plugins/speech-recognize/speech-recognize.js +1 -1
  55. package/es5/plugins/speech-recognize/speech-recognize.min.js +1 -1
  56. package/es5/polyfills.fat.min.js +1 -1
  57. package/es5/polyfills.js +1 -1
  58. package/es5/polyfills.min.js +1 -1
  59. package/esm/config.d.ts +65 -2
  60. package/esm/config.js +29 -2
  61. package/esm/core/constants.js +1 -1
  62. package/esm/core/helpers/utils/align.d.ts +11 -5
  63. package/esm/core/helpers/utils/align.js +28 -8
  64. package/esm/core/helpers/utils/index.d.ts +1 -0
  65. package/esm/core/helpers/utils/index.js +1 -0
  66. package/esm/core/helpers/utils/media-wrapper.d.ts +29 -0
  67. package/esm/core/helpers/utils/media-wrapper.js +63 -0
  68. package/esm/core/selection/selection.js +34 -8
  69. package/esm/modules/history/history.js +20 -12
  70. package/esm/modules/toolbar/button/button.d.ts +18 -0
  71. package/esm/modules/toolbar/button/button.js +75 -5
  72. package/esm/plugins/font/config.d.ts +12 -0
  73. package/esm/plugins/font/config.js +25 -10
  74. package/esm/plugins/font/font-picker.d.ts +51 -0
  75. package/esm/plugins/font/font-picker.js +238 -0
  76. package/esm/plugins/font/font.js +12 -0
  77. package/esm/plugins/font/google-fonts.d.ts +65 -0
  78. package/esm/plugins/font/google-fonts.js +143 -0
  79. package/esm/plugins/image-properties/readers/index.js +2 -1
  80. package/esm/plugins/image-properties/writers/index.js +2 -2
  81. package/esm/plugins/inline-popup/config/items/img.js +10 -8
  82. package/esm/plugins/inline-popup/inline-popup.js +13 -6
  83. package/esm/plugins/line-height/line-height.js +1 -1
  84. package/esm/plugins/media/config.d.ts +26 -1
  85. package/esm/plugins/media/config.js +1 -3
  86. package/esm/plugins/media/media.js +12 -4
  87. package/esm/plugins/placeholder/placeholder.js +1 -1
  88. package/esm/plugins/source/editor/engines/ace.d.ts +2 -0
  89. package/esm/plugins/source/editor/engines/ace.js +35 -3
  90. package/esm/plugins/video/config.js +24 -4
  91. package/esm/types/toolbar.d.ts +2 -0
  92. package/esm/types/types.d.ts +13 -1
  93. package/package.json +1 -1
  94. package/types/config.d.ts +65 -2
  95. package/types/core/helpers/utils/align.d.ts +11 -5
  96. package/types/core/helpers/utils/index.d.ts +1 -0
  97. package/types/core/helpers/utils/media-wrapper.d.ts +29 -0
  98. package/types/modules/toolbar/button/button.d.ts +18 -0
  99. package/types/plugins/font/config.d.ts +12 -0
  100. package/types/plugins/font/font-picker.d.ts +51 -0
  101. package/types/plugins/font/google-fonts.d.ts +65 -0
  102. package/types/plugins/media/config.d.ts +26 -1
  103. package/types/plugins/source/editor/engines/ace.d.ts +2 -0
  104. package/types/types/toolbar.d.ts +2 -0
  105. package/types/types/types.d.ts +13 -1
@@ -36,6 +36,7 @@ import { Icon } from "../../../core/ui/icon.js";
36
36
  import { Popup } from "../../../core/ui/popup/popup.js";
37
37
  import { makeCollection } from "../factory.js";
38
38
  const INPUT_TAGS = new Set(['input', 'textarea', 'select']);
39
+ const TYPE_AHEAD_TIMEOUT = 1000;
39
40
  const FOCUSABLE_SELECTOR = [
40
41
  'button:not([disabled])',
41
42
  'a[href]',
@@ -177,6 +178,9 @@ let ToolbarButton = class ToolbarButton extends UIButton {
177
178
  currentValue: '',
178
179
  hasTrigger: false
179
180
  };
181
+ this.__typed = '';
182
+ this.__typedAt = 0;
183
+ this.__typedTo = null;
180
184
  this.openedPopup = null;
181
185
  const button = this.getElm('button');
182
186
  assert(button, 'Element button should exists');
@@ -366,11 +370,65 @@ let ToolbarButton = class ToolbarButton extends UIButton {
366
370
  items[(index + 1) % items.length].focus();
367
371
  break;
368
372
  default:
369
- return;
373
+ if (!this.control.list ||
374
+ !this.__typeAhead(e.key, items, index)) {
375
+ return;
376
+ }
370
377
  }
371
378
  e.preventDefault();
372
379
  e.stopPropagation();
373
380
  }
381
+ /**
382
+ * The mouse opens a dropdown without taking the focus out of the editor.
383
+ * While a dropdown list is open, typing there searches the list, as in an
384
+ * open `<select>`, instead of writing into the content
385
+ */
386
+ __onEditorKeyDown(e) {
387
+ if (!this.openedPopup ||
388
+ !this.control.list ||
389
+ e.altKey ||
390
+ e.ctrlKey ||
391
+ e.metaKey ||
392
+ !this.__typeAhead(e.key, this.__popupFocusables(), -1)) {
393
+ return;
394
+ }
395
+ e.preventDefault();
396
+ return false;
397
+ }
398
+ /**
399
+ * Typing in a dropdown list focuses the next item whose label starts with
400
+ * the typed text, whatever their case and accents, and repeating one letter
401
+ * cycles through the items that start with it, like the WAI-ARIA menu
402
+ * pattern's type-ahead. The typed text starts again after a pause, or once
403
+ * the focus has moved on
404
+ */
405
+ __typeAhead(key, items, index) {
406
+ var _a, _b;
407
+ const now = Date.now();
408
+ if (now - this.__typedAt > TYPE_AHEAD_TIMEOUT ||
409
+ items[index] !== this.__typedTo) {
410
+ this.__typed = '';
411
+ }
412
+ if (key.length !== 1 || (key === ' ' && !this.__typed)) {
413
+ return false;
414
+ }
415
+ this.__typedAt = now;
416
+ this.__typed += key;
417
+ this.__typedTo = items[index];
418
+ const { compare } = new Intl.Collator(undefined, {
419
+ usage: 'search',
420
+ sensitivity: 'base'
421
+ }), typed = this.__typed, cycle = Array.from(typed).every(char => compare(char, typed[0]) === 0), prefix = cycle ? typed[0] : typed, start = cycle ? index + 1 : Math.max(index, 0);
422
+ for (let i = 0; i < items.length; i += 1) {
423
+ const item = items[(start + i) % items.length], label = (_b = (_a = item.textContent) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
424
+ if (compare(label.slice(0, prefix.length), prefix) === 0) {
425
+ item.focus();
426
+ this.__typedTo = item;
427
+ break;
428
+ }
429
+ }
430
+ return true;
431
+ }
374
432
  /**
375
433
  * Visible controls of the opened dropdown that can take the focus
376
434
  */
@@ -385,12 +443,16 @@ let ToolbarButton = class ToolbarButton extends UIButton {
385
443
  * Create an open popup list
386
444
  */
387
445
  __openControlList(control) {
388
- var _a;
446
+ var _a, _b, _c, _d, _e;
389
447
  const controls = (_a = this.jodit.options.controls) !== null && _a !== void 0 ? _a : {}, getControl = (key) => findControlType(key, controls);
390
448
  const list = control.list;
391
449
  const menu = this.openPopup();
392
- const toolbar = makeCollection(this.j);
393
450
  menu.parentElement = this;
451
+ const content = (_b = control.popup) === null || _b === void 0 ? void 0 : _b.call(control, this.j, (_e = (_d = (_c = this.toolbar) === null || _c === void 0 ? void 0 : _c.getTarget(this)) !== null && _d !== void 0 ? _d : this.target) !== null && _e !== void 0 ? _e : null, this.__closePopup, this);
452
+ if (content) {
453
+ return this.__openMenu(menu, content);
454
+ }
455
+ const toolbar = makeCollection(this.j);
394
456
  toolbar.parentElement = menu;
395
457
  toolbar.mode = 'vertical';
396
458
  const isListItem = (key) => isPlainObject(key) && 'title' in key && 'value' in key;
@@ -442,7 +504,10 @@ let ToolbarButton = class ToolbarButton extends UIButton {
442
504
  toolbar.build(isArray(list)
443
505
  ? list.map(getButton)
444
506
  : keys(list, false).map(key => getButton(key, list[key])), this.target);
445
- menu.setContent(toolbar).open(() => position(this.container), false, this.j.o.allowTabNavigation ? this.container : undefined);
507
+ this.__openMenu(menu, toolbar);
508
+ }
509
+ __openMenu(menu, content) {
510
+ menu.setContent(content).open(() => position(this.container), false, this.j.o.allowTabNavigation ? this.container : undefined);
446
511
  this.state.activated = true;
447
512
  }
448
513
  onOutsideClick(e) {
@@ -462,6 +527,7 @@ let ToolbarButton = class ToolbarButton extends UIButton {
462
527
  this.j.e
463
528
  .on(this.ow, 'mousedown touchstart', this.onOutsideClick)
464
529
  .on('escape closeAllPopups', this.onOutsideClick)
530
+ .on('keydown', this.__onEditorKeyDown)
465
531
  .on(this.openedPopup.container, 'keydown', this.__onPopupKeyDown);
466
532
  return this.openedPopup;
467
533
  }
@@ -479,7 +545,8 @@ let ToolbarButton = class ToolbarButton extends UIButton {
479
545
  this.openedPopup = null;
480
546
  this.j.e
481
547
  .off(this.ow, 'mousedown touchstart', this.onOutsideClick)
482
- .off('escape closeAllPopups', this.onOutsideClick);
548
+ .off('escape closeAllPopups', this.onOutsideClick)
549
+ .off('keydown', this.__onEditorKeyDown);
483
550
  // The keyboard brought the focus into the dropdown: give it back to
484
551
  // the button, otherwise it would fall out to the document
485
552
  const { activeElement } = this.od, hadFocus = activeElement &&
@@ -558,6 +625,9 @@ __decorate([
558
625
  __decorate([
559
626
  autobind
560
627
  ], ToolbarButton.prototype, "__onPopupKeyDown", null);
628
+ __decorate([
629
+ autobind
630
+ ], ToolbarButton.prototype, "__onEditorKeyDown", null);
561
631
  __decorate([
562
632
  autobind
563
633
  ], ToolbarButton.prototype, "onOutsideClick", null);
@@ -6,6 +6,18 @@
6
6
  declare module 'jodit/config' {
7
7
  interface Config {
8
8
  defaultFontSizePoints: 'px' | 'pt';
9
+ font: {
10
+ googleFonts: {
11
+ /**
12
+ * Adds the Google Fonts library to the font family dropdown, which then
13
+ * also lists the theme's fonts and the fonts the content uses. The
14
+ * address answers with the JSON of the Google Fonts Developer API:
15
+ * the API itself (`https://www.googleapis.com/webfonts/v1/webfonts?key=…`)
16
+ * or a proxy of it. Empty leaves the dropdown as it is
17
+ */
18
+ url: string;
19
+ };
20
+ };
9
21
  }
10
22
  }
11
23
  export {};
@@ -4,16 +4,23 @@
4
4
  * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
5
  */
6
6
  import { Dom } from "../../core/dom/index.js";
7
+ import { isArray } from "../../core/helpers/checker/is-array.js";
7
8
  import { trimChars } from "../../core/helpers/string/trim.js";
8
9
  import { css } from "../../core/helpers/utils/css.js";
9
10
  import { Icon } from "../../core/ui/icon.js";
10
11
  import { Config } from "../../config.js";
11
12
  import fontIcon from "./icons/font.svg.js";
12
13
  import fontsizeIcon from "./icons/fontsize.svg.js";
14
+ import { UIFontPicker } from "./font-picker.js";
13
15
  /**
14
16
  * Default font-size points
15
17
  */
16
18
  Config.prototype.defaultFontSizePoints = 'px';
19
+ Config.prototype.font = {
20
+ googleFonts: {
21
+ url: ''
22
+ }
23
+ };
17
24
  Icon.set('font', fontIcon).set('fontsize', fontsizeIcon);
18
25
  Config.prototype.controls.fontsize = {
19
26
  command: 'fontsize',
@@ -78,6 +85,7 @@ Config.prototype.controls.fontsize = {
78
85
  return keySet.has(normalize(value.toString()));
79
86
  }
80
87
  };
88
+ const hadSelection = new WeakSet();
81
89
  Config.prototype.controls.font = {
82
90
  ...Config.prototype.controls.fontsize,
83
91
  command: 'fontname',
@@ -108,6 +116,9 @@ Config.prototype.controls.font = {
108
116
  catch (_a) { }
109
117
  return `<span data-style="${key}" style="${isAvailable ? `font-family: ${key}!important;` : ''}">${value}</span>`;
110
118
  },
119
+ popup: (editor, current, close, button) => editor.o.font.googleFonts.url
120
+ ? new UIFontPicker(editor, button, close)
121
+ : false,
111
122
  data: {
112
123
  cssRule: 'font-family',
113
124
  normalize: (v) => {
@@ -119,19 +130,23 @@ Config.prototype.controls.font = {
119
130
  },
120
131
  // When no font is explicitly set, the computed font-family equals the
121
132
  // editor's own default (e.g. `-apple-system, …`). Return '' so the
122
- // button shows the `Default` list entry instead of that raw stack.
123
- // See #1370
124
- value: (editor) => {
125
- const current = editor.s.current();
126
- if (!current) {
127
- return;
133
+ // button shows the list's `Default` entry, when it has one, instead of
134
+ // that raw stack. See #1370
135
+ value: (editor, button) => {
136
+ const current = editor.s.current(), box = current && Dom.closest(current, Dom.isElement, editor.editor);
137
+ // Until there is a selection the button shows the editor's own font,
138
+ // and afterwards the font of the last one
139
+ if (box) {
140
+ hadSelection.add(button);
128
141
  }
129
- const box = Dom.closest(current, Dom.isElement, editor.editor);
130
- if (!box) {
142
+ else if (hadSelection.has(button)) {
131
143
  return;
132
144
  }
133
- const value = css(box, 'font-family').toString();
134
- if (value === css(editor.editor, 'font-family').toString()) {
145
+ const value = css(box || editor.editor, 'font-family').toString(), { list } = button.control;
146
+ if (value === css(editor.editor, 'font-family').toString() &&
147
+ (isArray(list)
148
+ ? list.includes('')
149
+ : '' in (list !== null && list !== void 0 ? list : {}))) {
135
150
  return '';
136
151
  }
137
152
  return value;
@@ -0,0 +1,51 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ /**
7
+ * @module plugins/font
8
+ */
9
+ import type { IJodit, IToolbarButton } from "../../types/index";
10
+ import { UIElement } from "../../core/ui/element";
11
+ /**
12
+ * The font family dropdown with Google Fonts. Under their own headings it
13
+ * lists the fonts the editor's styles give text and headings, the fonts the
14
+ * content uses, the control's list and the Google Fonts library
15
+ */
16
+ export declare class UIFontPicker extends UIElement<IJodit> {
17
+ private readonly __button;
18
+ private readonly __close;
19
+ className(): string;
20
+ private readonly __googleFonts;
21
+ private readonly __fonts;
22
+ private __observer;
23
+ constructor(jodit: IJodit, __button: IToolbarButton, __close: () => void);
24
+ private __render;
25
+ /**
26
+ * The fonts the editor's styles give text and each heading
27
+ */
28
+ private __themeFonts;
29
+ /**
30
+ * The fonts the content sets
31
+ */
32
+ private __usedFonts;
33
+ private __append;
34
+ /**
35
+ * Shows the names of the Google Fonts in view, and those just below it,
36
+ * in their own fonts. Setting every item's font up front would have the
37
+ * browser look for each of the library's families among the installed
38
+ * fonts the first time the list opens
39
+ */
40
+ private __observeGoogleFonts;
41
+ /**
42
+ * Keeps the editor's selection, as the toolbar buttons do
43
+ */
44
+ protected onMouseDown(e: MouseEvent): void;
45
+ protected onClick(e: MouseEvent): void;
46
+ /**
47
+ * The font the selected text has without a font of its own: its block's
48
+ */
49
+ private __blockFont;
50
+ destruct(): void;
51
+ }
@@ -0,0 +1,238 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
7
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
8
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
9
+ r = Reflect.decorate(decorators, target, key, desc);
10
+ else
11
+ for (var i = decorators.length - 1; i >= 0; i--)
12
+ if (d = decorators[i])
13
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
15
+ };
16
+ import { component } from "../../core/decorators/component/component.js";
17
+ import { watch } from "../../core/decorators/watch/watch.js";
18
+ import { Dom } from "../../core/dom/dom.js";
19
+ import { uniqueUid } from "../../core/global.js";
20
+ import { isArray } from "../../core/helpers/checker/is-array.js";
21
+ import { attr } from "../../core/helpers/utils/attr.js";
22
+ import { css } from "../../core/helpers/utils/css.js";
23
+ import { keys } from "../../core/helpers/utils/utils.js";
24
+ import { UIElement } from "../../core/ui/element.js";
25
+ import { firstFamily, GoogleFonts } from "./google-fonts.js";
26
+ const HEADINGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
27
+ const GENERIC_FAMILIES = new Set([
28
+ 'serif',
29
+ 'sans-serif',
30
+ 'monospace',
31
+ 'cursive',
32
+ 'fantasy',
33
+ 'system-ui',
34
+ 'ui-serif',
35
+ 'ui-sans-serif',
36
+ 'ui-monospace',
37
+ 'ui-rounded'
38
+ ]);
39
+ /**
40
+ * A family that names a font, not a generic family or a vendor keyword such
41
+ * as `-apple-system`
42
+ */
43
+ function isFontName(family) {
44
+ return (Boolean(family) &&
45
+ !family.startsWith('-') &&
46
+ !GENERIC_FAMILIES.has(family));
47
+ }
48
+ /**
49
+ * The font family dropdown with Google Fonts. Under their own headings it
50
+ * lists the fonts the editor's styles give text and headings, the fonts the
51
+ * content uses, the control's list and the Google Fonts library
52
+ */
53
+ let UIFontPicker = class UIFontPicker extends UIElement {
54
+ className() {
55
+ return 'UIFontPicker';
56
+ }
57
+ constructor(jodit, __button, __close) {
58
+ super(jodit);
59
+ this.__button = __button;
60
+ this.__close = __close;
61
+ this.__fonts = new Map();
62
+ this.__observer = null;
63
+ this.__googleFonts = jodit.getInstance(GoogleFonts);
64
+ this.__render();
65
+ if (!this.__googleFonts.fonts.size) {
66
+ this.__googleFonts.ready.then(() => !this.isDestructed && this.__render());
67
+ }
68
+ }
69
+ __render() {
70
+ var _a, _b;
71
+ const { control } = this.__button, { list = {} } = control, listed = isArray(list)
72
+ ? list.map((key) => ({
73
+ value: key.toString(),
74
+ title: key.toString()
75
+ }))
76
+ : keys(list, false).map((key) => ({
77
+ value: key,
78
+ title: list[key].toString()
79
+ })), google = Array.from(this.__googleFonts.fonts.values(), (font) => ({
80
+ value: this.__googleFonts.value(font),
81
+ title: font.family,
82
+ font
83
+ })), byFamily = new Map();
84
+ [...google, ...listed].forEach(entry => byFamily.set(firstFamily(entry.value), entry));
85
+ const theme = new Map(), used = new Map();
86
+ // The theme's own fonts are listed too, so its body font can be chosen
87
+ // to take a font off
88
+ this.__themeFonts().forEach(fontFamily => {
89
+ var _a;
90
+ const family = firstFamily(fontFamily), title = fontFamily.split(',')[0].replace(/["']/g, '').trim(), entry = (_a = byFamily.get(family)) !== null && _a !== void 0 ? _a : (isFontName(family)
91
+ ? { value: fontFamily, title }
92
+ : undefined);
93
+ entry && !theme.has(family) && theme.set(family, entry);
94
+ });
95
+ this.__usedFonts().forEach(fontFamily => {
96
+ const family = firstFamily(fontFamily), entry = byFamily.get(family);
97
+ entry && !theme.has(family) && used.set(family, entry);
98
+ });
99
+ const defaultEntry = listed.find(entry => entry.value === ''), current = firstFamily((_b = (_a = control.value) === null || _a === void 0 ? void 0 : _a.call(control, this.j, this.__button)) !== null && _b !== void 0 ? _b : '');
100
+ Dom.detach(this.container);
101
+ this.__fonts.clear();
102
+ defaultEntry && this.__append(current, [defaultEntry]);
103
+ this.__append(current, [...theme.values()], 'Theme fonts');
104
+ this.__append(current, [...used.values()], 'Used fonts');
105
+ this.__append(current, listed.filter(entry => entry !== defaultEntry), 'System fonts');
106
+ this.__append(current, google, 'Google Fonts');
107
+ this.__observeGoogleFonts();
108
+ }
109
+ /**
110
+ * The fonts the editor's styles give text and each heading
111
+ */
112
+ __themeFonts() {
113
+ const { editor } = this.j, fonts = [css(editor, 'font-family').toString()];
114
+ HEADINGS.forEach(tag => {
115
+ const heading = this.j.createInside.element(tag, {
116
+ style: 'display: none'
117
+ });
118
+ Dom.append(editor, heading);
119
+ fonts.push(css(heading, 'font-family').toString());
120
+ Dom.safeRemove(heading);
121
+ });
122
+ return fonts;
123
+ }
124
+ /**
125
+ * The fonts the content sets
126
+ */
127
+ __usedFonts() {
128
+ return Array.from(this.j.editor.querySelectorAll('[style*="font-family"]'), element => element.style.fontFamily);
129
+ }
130
+ __append(current, entries, heading) {
131
+ if (!entries.length) {
132
+ return;
133
+ }
134
+ const { control } = this.__button, group = this.j.c.div(this.getFullElName('group'));
135
+ if (heading) {
136
+ const title = this.j.c.div(this.getFullElName('heading'), {
137
+ id: `${this.getFullElName('heading')}-${uniqueUid()}`
138
+ });
139
+ title.textContent = this.i18n(heading);
140
+ attr(group, { role: 'group', 'aria-labelledby': title.id });
141
+ Dom.append(group, title);
142
+ }
143
+ entries.forEach(entry => {
144
+ var _a, _b;
145
+ const item = this.j.c.element('button', {
146
+ type: 'button',
147
+ class: this.getFullElName('item'),
148
+ 'data-value': entry.value,
149
+ 'aria-pressed': firstFamily(entry.value) === current
150
+ });
151
+ if (entry.font) {
152
+ item.textContent = entry.title;
153
+ this.__fonts.set(item, entry.font);
154
+ }
155
+ else {
156
+ item.innerHTML =
157
+ (_b = (_a = control.childTemplate) === null || _a === void 0 ? void 0 : _a.call(control, this.j, entry.value, entry.title, this.__button)) !== null && _b !== void 0 ? _b : entry.title;
158
+ }
159
+ Dom.append(group, item);
160
+ });
161
+ Dom.append(this.container, group);
162
+ }
163
+ /**
164
+ * Shows the names of the Google Fonts in view, and those just below it,
165
+ * in their own fonts. Setting every item's font up front would have the
166
+ * browser look for each of the library's families among the installed
167
+ * fonts the first time the list opens
168
+ */
169
+ __observeGoogleFonts() {
170
+ this.j.async.requestAnimationFrame(() => {
171
+ var _a;
172
+ (_a = this.__observer) === null || _a === void 0 ? void 0 : _a.disconnect();
173
+ if (this.isDestructed || !this.__fonts.size) {
174
+ return;
175
+ }
176
+ const observer = new IntersectionObserver(entries => {
177
+ const fonts = [];
178
+ entries.forEach(({ isIntersecting, target }) => {
179
+ const font = this.__fonts.get(target);
180
+ if (isIntersecting && font) {
181
+ fonts.push(font);
182
+ css(target, {
183
+ fontFamily: this.__googleFonts.value(font)
184
+ });
185
+ observer.unobserve(target);
186
+ }
187
+ });
188
+ this.__googleFonts.preview(fonts);
189
+ }, {
190
+ root: this.container.parentElement,
191
+ rootMargin: '0px 0px 400px 0px'
192
+ });
193
+ this.__fonts.forEach((_, item) => observer.observe(item));
194
+ this.__observer = observer;
195
+ });
196
+ }
197
+ /**
198
+ * Keeps the editor's selection, as the toolbar buttons do
199
+ */
200
+ onMouseDown(e) {
201
+ e.preventDefault();
202
+ }
203
+ onClick(e) {
204
+ var _a;
205
+ const item = Dom.up(e.target, node => Dom.isHTMLElement(node) && node.hasAttribute('data-value'), this.container);
206
+ if (!item) {
207
+ return;
208
+ }
209
+ const value = (_a = attr(item, 'data-value')) !== null && _a !== void 0 ? _a : '', font = this.__googleFonts.find(value),
210
+ // The font the text has anyway is taken off rather than written
211
+ inherited = firstFamily(value) === firstFamily(this.__blockFont());
212
+ font && !inherited && this.__googleFonts.load(font);
213
+ this.j.execCommand('fontname', false, inherited ? '' : value);
214
+ this.__close();
215
+ }
216
+ /**
217
+ * The font the selected text has without a font of its own: its block's
218
+ */
219
+ __blockFont() {
220
+ const block = Dom.up(this.j.s.current(), Dom.isBlock, this.j.editor);
221
+ return css(block !== null && block !== void 0 ? block : this.j.editor, 'font-family').toString();
222
+ }
223
+ destruct() {
224
+ var _a;
225
+ (_a = this.__observer) === null || _a === void 0 ? void 0 : _a.disconnect();
226
+ super.destruct();
227
+ }
228
+ };
229
+ __decorate([
230
+ watch('container:mousedown')
231
+ ], UIFontPicker.prototype, "onMouseDown", null);
232
+ __decorate([
233
+ watch('container:click')
234
+ ], UIFontPicker.prototype, "onClick", null);
235
+ UIFontPicker = __decorate([
236
+ component
237
+ ], UIFontPicker);
238
+ export { UIFontPicker };
@@ -6,6 +6,7 @@
6
6
  import { pluginSystem } from "../../core/global.js";
7
7
  import { normalizeSize } from "../../core/helpers/index.js";
8
8
  import "./config.js";
9
+ import { GoogleFonts } from "./google-fonts.js";
9
10
  /**
10
11
  * Process commands `fontsize` and `fontname`
11
12
  */
@@ -19,6 +20,17 @@ export function font(editor) {
19
20
  name: 'fontsize',
20
21
  group: 'font'
21
22
  });
23
+ if (editor.o.font.googleFonts.url) {
24
+ const googleFonts = editor.getInstance(GoogleFonts), loadUsed = editor.async.debounce(() => googleFonts.loadUsed(), editor.defaultTimeout);
25
+ googleFonts.ready.then(loadUsed);
26
+ editor.e.on('afterInit afterSetMode change', loadUsed);
27
+ }
28
+ // An iframe's stylesheets can arrive after the toolbar first shows the
29
+ // editor's font
30
+ editor.e.on('afterInit', () => {
31
+ editor.iframe &&
32
+ editor.e.on(editor.ed, 'load', () => editor.e.fire('updateToolbar'), { capture: true });
33
+ });
22
34
  const callback = (command, second, third) => {
23
35
  switch (command) {
24
36
  case 'fontsize':
@@ -0,0 +1,65 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ /**
7
+ * @module plugins/font
8
+ */
9
+ import type { IJodit } from "../../types/index";
10
+ import { ViewComponent } from "../../core/component/view-component";
11
+ /**
12
+ * A font as the Google Fonts Developer API describes it
13
+ */
14
+ export interface IGoogleFont {
15
+ family: string;
16
+ category: string;
17
+ variants: string[];
18
+ }
19
+ /**
20
+ * The first family of a `font-family` value, unquoted and in lower case for
21
+ * comparing
22
+ */
23
+ export declare function firstFamily(fontFamily: string): string;
24
+ /**
25
+ * Loads fonts from Google Fonts: the whole of each font the content uses into
26
+ * the editor's document, and just the letters of its name for the font list
27
+ */
28
+ export declare class GoogleFonts extends ViewComponent<IJodit> {
29
+ className(): string;
30
+ /**
31
+ * The library by lower-case family, empty until it arrives
32
+ */
33
+ readonly fonts: Map<string, IGoogleFont>;
34
+ readonly ready: Promise<void>;
35
+ private readonly __links;
36
+ private readonly __loaded;
37
+ private readonly __previewed;
38
+ constructor(jodit: IJodit);
39
+ /**
40
+ * The library's font named first in a `font-family` value
41
+ */
42
+ find(fontFamily: string): IGoogleFont | undefined;
43
+ /**
44
+ * The `font-family` value that applies a font
45
+ */
46
+ value(font: IGoogleFont): string;
47
+ /**
48
+ * Loads the regular, italic, bold and bold italic styles that a font has
49
+ * into the editor's document
50
+ */
51
+ load(font: IGoogleFont): void;
52
+ /**
53
+ * Loads every font of the library that the content uses
54
+ */
55
+ loadUsed(): void;
56
+ /**
57
+ * Loads into the page just the letters of each font's name, so the font
58
+ * list can show the name in its font
59
+ */
60
+ preview(fonts: IGoogleFont[]): void;
61
+ private __loadedInto;
62
+ private __family;
63
+ private __append;
64
+ destruct(): void;
65
+ }