@kerebron/extension-menu 0.4.25 → 0.4.27

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.
package/esm/menu.js DELETED
@@ -1,318 +0,0 @@
1
- import { getIcon } from './icons.js';
2
- /// An icon or label that, when clicked, executes a command.
3
- export class MenuItem {
4
- spec;
5
- CSS_PREFIX = 'kb-menu__button';
6
- /// Create a menu item.
7
- constructor(
8
- /// The spec used to create this item.
9
- spec) {
10
- this.spec = spec;
11
- }
12
- /// Renders the icon according to its [display
13
- /// spec](#menu.MenuItemSpec.display), and adds an event handler which
14
- /// executes the command when the representation is clicked.
15
- render(view) {
16
- let spec = this.spec;
17
- let dom = spec.render ? spec.render(view) : null;
18
- if (!dom) {
19
- // Create a proper button element for better accessibility
20
- dom = document.createElement('button');
21
- dom.setAttribute('type', 'button');
22
- // Add our new CSS classes while maintaining backward compatibility
23
- dom.classList.add(this.CSS_PREFIX);
24
- if (spec.icon) {
25
- const icon = getIcon(view.root, spec.icon);
26
- dom.appendChild(icon);
27
- dom.classList.add(this.CSS_PREFIX + '--icon-only');
28
- }
29
- if (spec.label) {
30
- const labelSpan = document.createElement('span');
31
- labelSpan.appendChild(document.createTextNode(translate(view, spec.label)));
32
- dom.appendChild(labelSpan);
33
- if (spec.icon) {
34
- dom.classList.remove(this.CSS_PREFIX + '--icon-only');
35
- }
36
- }
37
- if (!spec.icon && !spec.label) {
38
- throw new RangeError('MenuItem without icon or label property');
39
- }
40
- }
41
- if (spec.title) {
42
- const title = typeof spec.title === 'function'
43
- ? spec.title(view.state)
44
- : spec.title;
45
- dom.setAttribute('title', translate(view, title));
46
- dom.setAttribute('aria-label', translate(view, title));
47
- }
48
- if (spec.class)
49
- dom.classList.add(spec.class);
50
- if (spec.css)
51
- dom.style.cssText += spec.css;
52
- dom.addEventListener('mousedown', (e) => {
53
- e.preventDefault();
54
- if (!dom.classList.contains(this.CSS_PREFIX + '--disabled')) {
55
- spec.run(view.state, view.dispatch);
56
- }
57
- });
58
- const update = (state) => {
59
- if (spec.select) {
60
- let selected = spec.select(state);
61
- dom.style.display = selected ? '' : 'none';
62
- if (!selected)
63
- return false;
64
- }
65
- let enabled = true;
66
- if (spec.enable) {
67
- enabled = spec.enable(state) || false;
68
- setClass(dom, this.CSS_PREFIX + '--disabled', !enabled);
69
- dom.setAttribute('aria-disabled', (!enabled).toString());
70
- }
71
- if (spec.active) {
72
- let active = enabled && spec.active(state) || false;
73
- setClass(dom, this.CSS_PREFIX + '--active', active);
74
- dom.setAttribute('aria-pressed', active.toString());
75
- }
76
- return true;
77
- };
78
- return { dom, update };
79
- }
80
- }
81
- function translate(view, text) {
82
- return view._props.translate
83
- ? view._props.translate(text)
84
- : text;
85
- }
86
- let lastMenuEvent = {
87
- time: 0,
88
- node: null,
89
- };
90
- function markMenuEvent(e) {
91
- lastMenuEvent.time = Date.now();
92
- lastMenuEvent.node = e.target;
93
- }
94
- function isMenuEvent(wrapper) {
95
- return Date.now() - 100 < lastMenuEvent.time &&
96
- lastMenuEvent.node && wrapper.contains(lastMenuEvent.node);
97
- }
98
- /// A drop-down menu, displayed as a label with a downwards-pointing
99
- /// triangle to the right of it.
100
- export class Dropdown {
101
- options;
102
- CSS_PREFIX = 'kb-dropdown';
103
- /// @internal
104
- content;
105
- /// Create a dropdown wrapping the elements.
106
- constructor(content,
107
- /// @internal
108
- options = {}) {
109
- this.options = options;
110
- this.options = options || {};
111
- this.content = Array.isArray(content) ? content : [content];
112
- }
113
- /// Render the dropdown menu and sub-items.
114
- render(view) {
115
- let content = renderDropdownItems(this.content, view);
116
- let win = view.dom.ownerDocument.defaultView;
117
- // Create a button element instead of div for better accessibility
118
- let label = document.createElement('button');
119
- label.setAttribute('type', 'button');
120
- label.classList.add(this.CSS_PREFIX + '__label');
121
- if (this.options.class) {
122
- label.classList.add(this.options.class);
123
- }
124
- if (this.options.css)
125
- label.setAttribute('style', this.options.css);
126
- label.appendChild(document.createTextNode(translate(view, this.options.label || '')));
127
- if (this.options.title) {
128
- const title = translate(view, this.options.title);
129
- label.setAttribute('title', title);
130
- label.setAttribute('aria-label', title);
131
- }
132
- // Set ARIA attributes for accessibility
133
- label.setAttribute('aria-haspopup', 'true');
134
- label.setAttribute('aria-expanded', 'false');
135
- let wrap = document.createElement('div');
136
- wrap.classList.add(this.CSS_PREFIX);
137
- wrap.appendChild(label);
138
- let open = null;
139
- let listeningOnClose = null;
140
- let close = () => {
141
- if (open && open.close()) {
142
- open = null;
143
- win?.removeEventListener('mousedown', listeningOnClose);
144
- }
145
- };
146
- label.addEventListener('mousedown', (e) => {
147
- e.preventDefault();
148
- markMenuEvent(e);
149
- if (open) {
150
- close();
151
- wrap.classList.remove('kb-dropdown--open');
152
- }
153
- else {
154
- open = this.expand(wrap, content.dom);
155
- wrap.classList.add('kb-dropdown--open');
156
- win?.addEventListener('mousedown', listeningOnClose = () => {
157
- if (!isMenuEvent(wrap))
158
- close();
159
- });
160
- }
161
- });
162
- function update(state) {
163
- let inner = content.update(state);
164
- wrap.style.display = inner ? '' : 'none';
165
- return inner;
166
- }
167
- return { dom: wrap, update };
168
- }
169
- /// @internal
170
- expand(dom, items) {
171
- const menuDOM = document.createElement('div');
172
- menuDOM.classList.add('kb-dropdown__menu');
173
- menuDOM.setAttribute('role', 'menu');
174
- if (this.options.class) {
175
- menuDOM.classList.add(this.options.class);
176
- }
177
- items.forEach((item) => menuDOM.appendChild(item));
178
- let done = false;
179
- function close() {
180
- if (done)
181
- return false;
182
- done = true;
183
- dom.removeChild(menuDOM);
184
- return true;
185
- }
186
- dom.appendChild(menuDOM);
187
- return { close, node: menuDOM };
188
- }
189
- }
190
- function renderDropdownItems(items, view) {
191
- let rendered = [], updates = [];
192
- for (let i = 0; i < items.length; i++) {
193
- let { dom, update } = items[i].render(view);
194
- const item = document.createElement('div');
195
- item.classList.add('kb-dropdown__item');
196
- item.appendChild(dom);
197
- rendered.push(item);
198
- updates.push(update);
199
- }
200
- return { dom: rendered, update: combineUpdates(updates, rendered) };
201
- }
202
- function combineUpdates(updates, nodes) {
203
- return (state) => {
204
- let something = false;
205
- for (let i = 0; i < updates.length; i++) {
206
- let up = updates[i](state);
207
- nodes[i].style.display = up ? '' : 'none';
208
- if (up)
209
- something = true;
210
- }
211
- return something;
212
- };
213
- }
214
- /// Represents a submenu wrapping a group of elements that start
215
- /// hidden and expand to the right when hovered over or tapped.
216
- export class DropdownSubmenu {
217
- options;
218
- /// @internal
219
- content;
220
- /// Creates a submenu for the given group of menu elements. The
221
- /// following options are recognized:
222
- constructor(content,
223
- /// @internal
224
- options = {}) {
225
- this.options = options;
226
- this.content = Array.isArray(content) ? content : [content];
227
- }
228
- /// Renders the submenu.
229
- render(view) {
230
- const CSS_PREFIX = 'kb-submenu';
231
- const items = renderDropdownItems(this.content, view);
232
- const win = view.dom.ownerDocument.defaultView;
233
- const wrap = document.createElement('div');
234
- wrap.classList.add(CSS_PREFIX);
235
- const label = document.createElement('div');
236
- label.classList.add(CSS_PREFIX + '__label');
237
- label.appendChild(document.createTextNode(translate(view, this.options.label || '')));
238
- wrap.appendChild(label);
239
- const submenu = document.createElement('div');
240
- submenu.classList.add(CSS_PREFIX + '__content');
241
- items.dom.forEach((item) => submenu.appendChild(item));
242
- wrap.appendChild(submenu);
243
- let listeningOnClose = null;
244
- label.addEventListener('mousedown', (e) => {
245
- e.preventDefault();
246
- markMenuEvent(e);
247
- const isOpen = wrap.classList.contains(CSS_PREFIX + '--open');
248
- setClass(wrap, CSS_PREFIX + '--open', !isOpen);
249
- if (!isOpen && !listeningOnClose) {
250
- win?.addEventListener('mousedown', listeningOnClose = () => {
251
- if (!isMenuEvent(wrap)) {
252
- wrap.classList.remove(CSS_PREFIX + '--open');
253
- win?.removeEventListener('mousedown', listeningOnClose);
254
- listeningOnClose = null;
255
- }
256
- });
257
- }
258
- });
259
- function update(state) {
260
- let inner = items.update(state);
261
- wrap.style.display = inner ? '' : 'none';
262
- return inner;
263
- }
264
- return { dom: wrap, update };
265
- }
266
- }
267
- /// Render the given, possibly nested, array of menu elements into a
268
- /// document fragment, placing separators between them (and ensuring no
269
- /// superfluous separators appear when some of the groups turn out to
270
- /// be empty).
271
- export function renderGrouped(view, content) {
272
- let result = document.createDocumentFragment();
273
- let updates = [], separators = [];
274
- for (let i = 0; i < content.length; i++) {
275
- let items = content[i], localUpdates = [], localNodes = [];
276
- for (let j = 0; j < items.length; j++) {
277
- let { dom, update } = items[j].render(view);
278
- let span = document.createElement('span');
279
- span.classList.add('kb-menu__item');
280
- span.appendChild(dom);
281
- result.appendChild(span);
282
- localNodes.push(span);
283
- localUpdates.push(update);
284
- }
285
- if (localUpdates.length) {
286
- updates.push(combineUpdates(localUpdates, localNodes));
287
- if (i < content.length - 1) {
288
- separators.push(result.appendChild(separator()));
289
- }
290
- }
291
- }
292
- function update(state) {
293
- let something = false, needSep = false;
294
- for (let i = 0; i < updates.length; i++) {
295
- let hasContent = updates[i](state);
296
- if (i) {
297
- separators[i - 1].style.display = needSep && hasContent ? '' : 'none';
298
- }
299
- needSep = hasContent;
300
- if (hasContent)
301
- something = true;
302
- }
303
- return something;
304
- }
305
- return { dom: result, update };
306
- }
307
- function separator() {
308
- const elem = document.createElement('div');
309
- elem.classList.add('kb-menu__separator');
310
- return elem;
311
- }
312
- // Work around classList.toggle being broken in IE11
313
- function setClass(dom, cls, on) {
314
- if (on)
315
- dom.classList.add(cls);
316
- else
317
- dom.classList.remove(cls);
318
- }
package/esm/mod.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export { Dropdown, DropdownSubmenu, type MenuElement, MenuItem, type MenuItemSpec, } from './menu.js';
2
- export * from './ExtensionCustomMenu.js';
3
- //# sourceMappingURL=mod.d.ts.map
package/esm/mod.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,eAAe,EACf,KAAK,WAAW,EAChB,QAAQ,EACR,KAAK,YAAY,GAClB,MAAM,WAAW,CAAC;AAEnB,cAAc,0BAA0B,CAAC"}
package/esm/mod.js DELETED
@@ -1,2 +0,0 @@
1
- export { Dropdown, DropdownSubmenu, MenuItem, } from './menu.js';
2
- export * from './ExtensionCustomMenu.js';
package/esm/package.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "type": "module"
3
- }
package/esm/prompt.d.ts DELETED
@@ -1,36 +0,0 @@
1
- import { Attrs } from 'prosemirror-model';
2
- export declare function openPrompt(options: {
3
- title: string;
4
- fields: {
5
- [name: string]: Field;
6
- };
7
- callback: (attrs: Attrs) => void;
8
- }): void;
9
- export declare abstract class Field {
10
- readonly options: {
11
- value?: any;
12
- label: string;
13
- required?: boolean;
14
- validate?: (value: any) => string | null;
15
- clean?: (value: any) => any;
16
- };
17
- constructor(options: {
18
- value?: any;
19
- label: string;
20
- required?: boolean;
21
- validate?: (value: any) => string | null;
22
- clean?: (value: any) => any;
23
- });
24
- abstract render(): HTMLElement;
25
- read(dom: HTMLElement): any;
26
- validateType(value: any): string | null;
27
- validate(value: any): string | null;
28
- clean(value: any): any;
29
- }
30
- export declare class TextField extends Field {
31
- render(): any;
32
- }
33
- export declare class SelectField extends Field {
34
- render(): any;
35
- }
36
- //# sourceMappingURL=prompt.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAI1C,wBAAgB,UAAU,CAAC,OAAO,EAAE;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAA;KAAE,CAAC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,QA0EA;AA+BD,8BAAsB,KAAK;IAKvB,QAAQ,CAAC,OAAO,EAAE;QAEhB,KAAK,CAAC,EAAE,GAAG,CAAC;QAGZ,KAAK,EAAE,MAAM,CAAC;QAGd,QAAQ,CAAC,EAAE,OAAO,CAAC;QAInB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,GAAG,IAAI,CAAC;QAGzC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC;KAC7B;gBAhBQ,OAAO,EAAE;QAEhB,KAAK,CAAC,EAAE,GAAG,CAAC;QAGZ,KAAK,EAAE,MAAM,CAAC;QAGd,QAAQ,CAAC,EAAE,OAAO,CAAC;QAInB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,GAAG,IAAI,CAAC;QAGzC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC;KAC7B;IAIH,QAAQ,CAAC,MAAM,IAAI,WAAW;IAG9B,IAAI,CAAC,GAAG,EAAE,WAAW;IAKrB,YAAY,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI;IAKvC,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI;IAQnC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG;CAGvB;AAGD,qBAAa,SAAU,SAAQ,KAAK;IAClC,MAAM;CAQP;AAMD,qBAAa,WAAY,SAAQ,KAAK;IACpC,MAAM;CAWP"}
package/esm/prompt.js DELETED
@@ -1,154 +0,0 @@
1
- const CSS_PREFIX = 'kb-prompt';
2
- export function openPrompt(options) {
3
- let wrapper = document.body.appendChild(document.createElement('div'));
4
- wrapper.className = CSS_PREFIX;
5
- let mouseOutside = (e) => {
6
- if (!wrapper.contains(e.target))
7
- close();
8
- };
9
- setTimeout(() => globalThis.addEventListener('mousedown', mouseOutside), 50);
10
- let close = () => {
11
- globalThis.removeEventListener('mousedown', mouseOutside);
12
- if (wrapper.parentNode)
13
- wrapper.parentNode.removeChild(wrapper);
14
- };
15
- let domFields = [];
16
- for (let name in options.fields) {
17
- domFields.push(options.fields[name].render());
18
- }
19
- let submitButton = document.createElement('button');
20
- submitButton.type = 'submit';
21
- submitButton.className = CSS_PREFIX + '--submit';
22
- submitButton.textContent = 'OK';
23
- let cancelButton = document.createElement('button');
24
- cancelButton.type = 'button';
25
- cancelButton.className = CSS_PREFIX + '--cancel';
26
- cancelButton.textContent = 'Cancel';
27
- cancelButton.addEventListener('click', close);
28
- let form = wrapper.appendChild(document.createElement('form'));
29
- if (options.title) {
30
- form.appendChild(document.createElement('h5')).textContent = options.title;
31
- }
32
- domFields.forEach((field) => {
33
- form.appendChild(document.createElement('div')).appendChild(field);
34
- });
35
- let buttons = form.appendChild(document.createElement('div'));
36
- buttons.className = CSS_PREFIX + '__buttons';
37
- buttons.appendChild(submitButton);
38
- buttons.appendChild(document.createTextNode(' '));
39
- buttons.appendChild(cancelButton);
40
- let box = wrapper.getBoundingClientRect();
41
- wrapper.style.top = ((globalThis.innerHeight - box.height) / 2) + 'px';
42
- wrapper.style.left = ((globalThis.innerWidth - box.width) / 2) + 'px';
43
- let submit = () => {
44
- let params = getValues(options.fields, domFields);
45
- if (params) {
46
- close();
47
- options.callback(params);
48
- }
49
- };
50
- form.addEventListener('submit', (e) => {
51
- e.preventDefault();
52
- submit();
53
- });
54
- form.addEventListener('keydown', (e) => {
55
- if (e.keyCode == 27) {
56
- e.preventDefault();
57
- close();
58
- }
59
- else if (e.keyCode == 13 && !(e.ctrlKey || e.metaKey || e.shiftKey)) {
60
- e.preventDefault();
61
- submit();
62
- }
63
- else if (e.keyCode == 9) {
64
- globalThis.setTimeout(() => {
65
- if (!wrapper.contains(document.activeElement))
66
- close();
67
- }, 500);
68
- }
69
- });
70
- let input = form.elements[0];
71
- if (input)
72
- input.focus();
73
- }
74
- function getValues(fields, domFields) {
75
- let result = Object.create(null), i = 0;
76
- for (let name in fields) {
77
- let field = fields[name], dom = domFields[i++];
78
- let value = field.read(dom), bad = field.validate(value);
79
- if (bad) {
80
- reportInvalid(dom, bad);
81
- return null;
82
- }
83
- result[name] = field.clean(value);
84
- }
85
- return result;
86
- }
87
- function reportInvalid(dom, message) {
88
- // FIXME this is awful and needs a lot more work
89
- let parent = dom.parentNode;
90
- let msg = parent.appendChild(document.createElement('div'));
91
- msg.style.left = (dom.offsetLeft + dom.offsetWidth + 2) + 'px';
92
- msg.style.top = (dom.offsetTop - 5) + 'px';
93
- msg.className = 'kb-invalid';
94
- msg.textContent = message;
95
- setTimeout(() => parent.removeChild(msg), 1500);
96
- }
97
- /// The type of field that `openPrompt` expects to be passed to it.
98
- export class Field {
99
- options;
100
- /// Create a field with the given options. Options support by all
101
- /// field types are:
102
- constructor(
103
- /// @internal
104
- options) {
105
- this.options = options;
106
- }
107
- /// Read the field's value from its DOM node.
108
- read(dom) {
109
- return dom.value;
110
- }
111
- /// A field-type-specific validation function.
112
- validateType(value) {
113
- return null;
114
- }
115
- /// @internal
116
- validate(value) {
117
- if (!value && this.options.required) {
118
- return 'Required field';
119
- }
120
- return this.validateType(value) ||
121
- (this.options.validate ? this.options.validate(value) : null);
122
- }
123
- clean(value) {
124
- return this.options.clean ? this.options.clean(value) : value;
125
- }
126
- }
127
- /// A field class for single-line text fields.
128
- export class TextField extends Field {
129
- render() {
130
- let input = document.createElement('input');
131
- input.type = 'text';
132
- input.placeholder = this.options.label;
133
- input.value = this.options.value || '';
134
- input.autocomplete = 'off';
135
- return input;
136
- }
137
- }
138
- /// A field class for dropdown fields based on a plain `<select>`
139
- /// tag. Expects an option `options`, which should be an array of
140
- /// `{value: string, label: string}` objects, or a function taking a
141
- /// `ProseMirror` instance and returning such an array.
142
- export class SelectField extends Field {
143
- render() {
144
- let select = document.createElement('select');
145
- this.options.options
146
- .forEach((o) => {
147
- let opt = select.appendChild(document.createElement('option'));
148
- opt.value = o.value;
149
- opt.selected = o.value == this.options.value;
150
- opt.label = o.label;
151
- });
152
- return select;
153
- }
154
- }