Skip to content

Commit e5f1633

Browse files
committed
1 parent 512fcea commit e5f1633

8 files changed

Lines changed: 180 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
> - :house: [Internal]
1010
> - :nail_care: [Polish]
1111

12+
## 4.13.9
13+
14+
#### :bug: Bug Fix
15+
16+
- **Mobile / adaptive toolbar**: with a custom `buttons` list and the default `buttonsMD`/`buttonsSM`/`buttonsXS`, resizing the editor narrower surfaced buttons that were never requested — the breakpoint sets are group-based defaults independent of `buttons`. The responsive breakpoint set is now constrained to `buttons`: resizing only ever drops buttons, never adds ones outside `buttons`. Set `buttonsMD`/`buttonsSM`/`buttonsXS` explicitly if you want a different per-breakpoint set. The default configuration (where `buttons` is the full superset) is unchanged. Fixes [#1389](https://github.com/xdan/jodit/issues/1389).
17+
1218
## 4.13.8
1319

1420
#### :house: Internal

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "jodit",
3-
"version": "4.13.8",
3+
"version": "4.13.9",
44
"description": "Jodit is an awesome and useful wysiwyg editor with filebrowser",
55
"main": "build/jodit.min.js",
66
"types": "./types/index.d.ts",

src/plugins/mobile/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,37 @@ const editor = Jodit.make('#editor', {
152152
});
153153
```
154154

155+
### Breakpoint sets are constrained to `buttons`
156+
157+
`buttons` is treated as the full set of buttons the editor may ever show. The
158+
responsive breakpoint sets (`buttonsMD`/`buttonsSM`/`buttonsXS`) are intersected
159+
with `buttons`, so resizing the editor can only ever **drop** buttons on smaller
160+
widths — it never adds a button that is not in `buttons`.
161+
162+
This matters when you customise only `buttons` and leave the breakpoint sets at
163+
their (group-based) defaults:
164+
165+
```typescript
166+
const editor = Jodit.make('#editor', {
167+
// only four buttons wanted, at every width
168+
buttons: ['bold', 'italic', 'underline', 'strikethrough']
169+
});
170+
```
171+
172+
On a narrow editor this still shows just those four buttons. Previously the
173+
editor fell back to the default `buttonsMD`/`buttonsSM`/`buttonsXS` groups and
174+
displayed many more buttons than were requested.
175+
176+
If you actually want a *different* set at some breakpoint, set that breakpoint
177+
option explicitly (its buttons should be a subset of `buttons`):
178+
179+
```typescript
180+
const editor = Jodit.make('#editor', {
181+
buttons: ['bold', 'italic', 'underline', 'strikethrough', 'ul', 'ol'],
182+
buttonsXS: ['bold', 'italic', 'dots']
183+
});
184+
```
185+
155186
### Disable Adaptive Toolbar
156187

157188
```typescript

src/plugins/mobile/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,26 @@ declare module 'jodit/config' {
3535

3636
/**
3737
* The list of buttons that appear in the editor's toolbar for medium-sized spaces (≥ options.sizeMD).
38+
*
39+
* The set is constrained to `buttons`: resizing may only drop buttons on
40+
* smaller widths, never surface a button that is not in `buttons`. So if
41+
* you customise only `buttons` and leave this at its default, a narrow
42+
* editor still shows just your `buttons`. Set this explicitly (as a subset
43+
* of `buttons`) to get a different medium-width set.
3844
*/
3945
buttonsMD: ButtonsOption;
4046

4147
/**
4248
* The list of buttons that appear in the editor's toolbar for small-sized spaces (≥ options.sizeSM).
49+
*
50+
* Constrained to `buttons` — see {@link buttonsMD}.
4351
*/
4452
buttonsSM: ButtonsOption;
4553

4654
/**
4755
* The list of buttons that appear in the editor's toolbar for extra-small spaces (less than options.sizeSM).
56+
*
57+
* Constrained to `buttons` — see {@link buttonsMD}.
4858
*/
4959
buttonsXS: ButtonsOption;
5060
}

src/plugins/mobile/mobile.test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,37 @@ describe('Test mobile mode', function () {
3535
expect(count()).to.eq(window.toolbarButtonsCountXS);
3636
});
3737

38+
describe('When only a custom `buttons` list is set (no buttonsMD/SM/XS)', function () {
39+
// #1389: resizing narrower switched to the group-based buttonsMD/SM/XS
40+
// defaults, surfacing buttons the user never requested. Resizing must
41+
// only ever drop buttons, never add ones outside `buttons`.
42+
it('Should never show buttons that were not requested on resize', function () {
43+
getBox().style.width = '1000px';
44+
const editor = getJodit({
45+
disablePlugins: ['speech-recognize'],
46+
buttons: ['bold', 'italic', 'underline', 'strikethrough']
47+
});
48+
49+
const count = () =>
50+
editor.container.querySelectorAll(
51+
'.jodit-toolbar__box .jodit-toolbar-button'
52+
).length;
53+
54+
expect(count()).equals(4);
55+
expect(getButton('image', editor)).to.be.null;
56+
57+
[790, 690, 390].forEach(width => {
58+
getBox().style.width = width + 'px';
59+
simulateEvent('resize', window);
60+
61+
expect(count()).equals(4);
62+
expect(getButton('bold', editor)).to.be.not.null;
63+
expect(getButton('image', editor)).to.be.null;
64+
expect(getButton('ul', editor)).to.be.null;
65+
});
66+
});
67+
});
68+
3869
describe('Disable plugins', () => {
3970
it('Should remove buttons from these plugins for all sizes', () => {
4071
getBox().style.width = '1000px';

src/plugins/mobile/mobile.ts

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,96 @@
1010
* @module plugins/mobile
1111
*/
1212

13-
import type { ButtonsGroups, IJodit, IToolbarCollection } from 'jodit/types';
13+
import type {
14+
ButtonsGroups,
15+
IControlType,
16+
IJodit,
17+
IToolbarCollection
18+
} from 'jodit/types';
1419
import { pluginSystem } from 'jodit/core/global';
1520
import { splitArray, toArray } from 'jodit/core/helpers/';
16-
import { flatButtonsSet } from 'jodit/core/ui/helpers/buttons';
21+
import { flatButtonsSet, isButtonGroup } from 'jodit/core/ui/helpers/buttons';
1722

1823
import './config';
1924

25+
const isButtonSeparator = (item: unknown): boolean =>
26+
item === '|' || item === '---' || item === '\n';
27+
28+
/**
29+
* Constrain a responsive breakpoint set (`buttonsMD/SM/XS`) to the buttons the
30+
* user actually asked for via `buttons`. Those breakpoint defaults are
31+
* group-based supersets, so a custom (smaller) `buttons` list would otherwise
32+
* see *extra* buttons appear on resize when the toolbar switched to a breakpoint
33+
* set — resizing must only ever drop buttons, never add ones outside `buttons`.
34+
*
35+
* When the breakpoint set introduces nothing outside `buttons` (the default
36+
* case, where `buttons` is the full superset), the list is returned untouched so
37+
* the standard grouped mobile layout — groups, separators and `dots` — is
38+
* preserved. See #1389.
39+
*/
40+
function fitToButtons(list: ButtonsGroups, editor: IJodit): ButtonsGroups {
41+
const allowed = flatButtonsSet(splitArray(editor.o.buttons), editor);
42+
const groups = editor.getRegisteredButtonGroups();
43+
44+
let changed = false;
45+
let hadDots = false;
46+
const flat: Array<string | IControlType> = [];
47+
48+
for (const item of list) {
49+
if (isButtonGroup(item)) {
50+
const members = [...item.buttons, ...(groups[item.group] ?? [])];
51+
const kept = members.filter(button => allowed.has(button));
52+
53+
if (kept.length !== members.length) {
54+
changed = true;
55+
}
56+
57+
flat.push(...kept);
58+
} else if (isButtonSeparator(item)) {
59+
flat.push(item);
60+
} else if (item === 'dots') {
61+
hadDots = true;
62+
} else if (allowed.has(item)) {
63+
flat.push(item);
64+
} else {
65+
changed = true;
66+
}
67+
}
68+
69+
if (!changed) {
70+
return list;
71+
}
72+
73+
// Drop separators left dangling once the buttons around them were removed.
74+
const cleaned: Array<string | IControlType> = [];
75+
for (const item of flat) {
76+
if (
77+
isButtonSeparator(item) &&
78+
(cleaned.length === 0 ||
79+
isButtonSeparator(cleaned[cleaned.length - 1]))
80+
) {
81+
continue;
82+
}
83+
84+
cleaned.push(item);
85+
}
86+
while (cleaned.length && isButtonSeparator(cleaned[cleaned.length - 1])) {
87+
cleaned.pop();
88+
}
89+
90+
// Keep the "show all" overflow button only if some requested button is still
91+
// hidden at this breakpoint.
92+
if (hadDots) {
93+
const shown = flatButtonsSet(cleaned, editor);
94+
95+
if (toArray(allowed).some(button => !shown.has(button))) {
96+
cleaned.push('dots');
97+
}
98+
}
99+
100+
return cleaned;
101+
}
102+
20103
/**
21104
* Rebuild toolbar in depends on editor's width
22105
*/
@@ -76,20 +159,29 @@ export function mobile(editor: IJodit): void {
76159
editor.container.parentElement ?? editor.container
77160
).offsetWidth;
78161

79-
const newStore = ((): ReturnType<typeof splitArray> => {
162+
const newStore = ((): ButtonsGroups => {
80163
if (editor.isFullSize || width >= editor.o.sizeLG) {
81164
return splitArray(editor.o.buttons);
82165
}
83166

84167
if (width >= editor.o.sizeMD) {
85-
return splitArray(editor.o.buttonsMD);
168+
return fitToButtons(
169+
splitArray(editor.o.buttonsMD),
170+
editor
171+
);
86172
}
87173

88174
if (width >= editor.o.sizeSM) {
89-
return splitArray(editor.o.buttonsSM);
175+
return fitToButtons(
176+
splitArray(editor.o.buttonsSM),
177+
editor
178+
);
90179
}
91180

92-
return splitArray(editor.o.buttonsXS);
181+
return fitToButtons(
182+
splitArray(editor.o.buttonsXS),
183+
editor
184+
);
93185
})();
94186

95187
if (newStore.toString() !== store.toString()) {

statoscope/reference.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)