@limetech/lime-elements 39.44.5 → 39.45.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.
- package/CHANGELOG.md +22 -0
- package/dist/cjs/{keycodes-B75-uKOK.js → keycodes-k-EdzcMX.js} +4 -0
- package/dist/cjs/lime-elements.cjs.js +1 -1
- package/dist/cjs/limel-breadcrumbs_8.cjs.entry.js +20 -1
- package/dist/cjs/limel-chip-set.cjs.entry.js +1 -1
- package/dist/cjs/limel-chip_2.cjs.entry.js +1 -1
- package/dist/cjs/limel-file-viewer.cjs.entry.js +84 -8
- package/dist/cjs/limel-markdown.cjs.entry.js +1 -1
- package/dist/cjs/limel-picker.cjs.entry.js +1 -1
- package/dist/cjs/limel-popover_2.cjs.entry.js +1 -1
- package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +778 -395
- package/dist/cjs/limel-select.cjs.entry.js +377 -13
- package/dist/cjs/limel-text-editor-link-menu.cjs.entry.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{markdown-parser-ChnRAxpZ.js → markdown-parser-YH5HSSgF.js} +36 -56
- package/dist/collection/components/list/list.js +19 -0
- package/dist/collection/components/select/select.js +215 -12
- package/dist/collection/components/select/select.template.js +17 -3
- package/dist/collection/components/text-editor/prosemirror-adapter/editor-config.js +12 -1
- package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-command-utils/active-state-utils.js +36 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-command-utils/list-utils.js +270 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-command-utils/node-utils.js +15 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-commands.js +81 -69
- package/dist/collection/components/text-editor/prosemirror-adapter/plugins/list-key-handler.js +44 -0
- package/dist/collection/components/text-editor/prosemirror-adapter/prosemirror-adapter.js +4 -3
- package/dist/collection/util/keycodes.js +3 -0
- package/dist/collection/util/typeahead.js +159 -0
- package/dist/esm/{keycodes-rI0IeKpx.js → keycodes-C4_9qUMP.js} +4 -1
- package/dist/esm/lime-elements.js +1 -1
- package/dist/esm/limel-breadcrumbs_8.entry.js +20 -1
- package/dist/esm/limel-chip-set.entry.js +1 -1
- package/dist/esm/limel-chip_2.entry.js +1 -1
- package/dist/esm/limel-file-viewer.entry.js +84 -8
- package/dist/esm/limel-markdown.entry.js +1 -1
- package/dist/esm/limel-picker.entry.js +1 -1
- package/dist/esm/limel-popover_2.entry.js +1 -1
- package/dist/esm/limel-prosemirror-adapter.entry.js +779 -396
- package/dist/esm/limel-select.entry.js +378 -14
- package/dist/esm/limel-text-editor-link-menu.entry.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{markdown-parser-BSQKleVw.js → markdown-parser-DrxxGLF5.js} +36 -56
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/{p-a089b580.entry.js → p-03fd2181.entry.js} +1 -1
- package/dist/lime-elements/{p-fc43fb46.entry.js → p-12fe8fe1.entry.js} +6 -6
- package/dist/lime-elements/p-1f29f482.entry.js +1 -0
- package/dist/lime-elements/{p-bf3c78a8.entry.js → p-2b01c1f2.entry.js} +1 -1
- package/dist/lime-elements/{p-758939be.entry.js → p-2e5d5e79.entry.js} +1 -1
- package/dist/lime-elements/{p-3ea159fd.entry.js → p-38393fb4.entry.js} +1 -1
- package/dist/lime-elements/p-9152d7e1.entry.js +23 -0
- package/dist/lime-elements/{p-1e3cdfa1.entry.js → p-9a04a686.entry.js} +1 -1
- package/dist/lime-elements/p-C4_9qUMP.js +1 -0
- package/dist/lime-elements/{p-B0qhUema.js → p-C9xHOCE_.js} +2 -2
- package/dist/lime-elements/{p-6fbf20c6.entry.js → p-e8df7a2e.entry.js} +1 -1
- package/dist/lime-elements/p-f51b5651.entry.js +1 -0
- package/dist/types/components/select/select.d.ts +76 -1
- package/dist/types/components/select/select.template.d.ts +21 -1
- package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-command-utils/active-state-utils.d.ts +6 -0
- package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-command-utils/list-utils.d.ts +68 -0
- package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-command-utils/node-utils.d.ts +10 -0
- package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-commands.d.ts +10 -0
- package/dist/types/components/text-editor/prosemirror-adapter/plugins/list-key-handler.d.ts +14 -0
- package/dist/types/components.d.ts +36 -0
- package/dist/types/util/keycodes.d.ts +3 -0
- package/dist/types/util/typeahead.d.ts +101 -0
- package/package.json +3 -3
- package/dist/lime-elements/p-552d0733.entry.js +0 -1
- package/dist/lime-elements/p-845cfda7.entry.js +0 -1
- package/dist/lime-elements/p-8f4273e4.entry.js +0 -23
- package/dist/lime-elements/p-rI0IeKpx.js +0 -1
|
@@ -33093,7 +33093,7 @@ function rehypeRaw(options) {
|
|
|
33093
33093
|
|
|
33094
33094
|
// http://www.w3.org/TR/CSS21/grammar.html
|
|
33095
33095
|
// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
|
|
33096
|
-
var COMMENT_REGEX = /\/\*[^*]
|
|
33096
|
+
var COMMENT_REGEX = /\/\*(?:[^*]|\*(?!\/))*\*\//g;
|
|
33097
33097
|
|
|
33098
33098
|
var NEWLINE_REGEX = /\n/g;
|
|
33099
33099
|
var WHITESPACE_REGEX = /^\s*/;
|
|
@@ -33101,12 +33101,10 @@ var WHITESPACE_REGEX = /^\s*/;
|
|
|
33101
33101
|
// declaration
|
|
33102
33102
|
var PROPERTY_REGEX = /^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/;
|
|
33103
33103
|
var COLON_REGEX = /^:\s*/;
|
|
33104
|
-
var VALUE_REGEX =
|
|
33104
|
+
var VALUE_REGEX =
|
|
33105
|
+
/^((?:'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"|url\((?:'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"|[^)]*)\)|[^};])+)/;
|
|
33105
33106
|
var SEMICOLON_REGEX = /^[;\s]*/;
|
|
33106
33107
|
|
|
33107
|
-
// https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill
|
|
33108
|
-
var TRIM_REGEX = /^\s+|\s+$/g;
|
|
33109
|
-
|
|
33110
33108
|
// strings
|
|
33111
33109
|
var NEWLINE = '\n';
|
|
33112
33110
|
var FORWARD_SLASH = '/';
|
|
@@ -33230,16 +33228,13 @@ function index (style, options) {
|
|
|
33230
33228
|
/**
|
|
33231
33229
|
* Parse comments.
|
|
33232
33230
|
*
|
|
33233
|
-
* @param {Object[]}
|
|
33231
|
+
* @param {Object[]} rules
|
|
33234
33232
|
* @return {Object[]}
|
|
33235
33233
|
*/
|
|
33236
33234
|
function comments(rules) {
|
|
33237
33235
|
var c;
|
|
33238
|
-
rules = rules || [];
|
|
33239
33236
|
while ((c = comment())) {
|
|
33240
|
-
|
|
33241
|
-
rules.push(c);
|
|
33242
|
-
}
|
|
33237
|
+
rules.push(c);
|
|
33243
33238
|
}
|
|
33244
33239
|
return rules;
|
|
33245
33240
|
}
|
|
@@ -33301,9 +33296,9 @@ function index (style, options) {
|
|
|
33301
33296
|
|
|
33302
33297
|
var ret = pos({
|
|
33303
33298
|
type: TYPE_DECLARATION,
|
|
33304
|
-
property:
|
|
33299
|
+
property: prop[0].replace(COMMENT_REGEX, EMPTY_STRING).trim(),
|
|
33305
33300
|
value: val
|
|
33306
|
-
?
|
|
33301
|
+
? val[0].replace(COMMENT_REGEX, EMPTY_STRING).trim()
|
|
33307
33302
|
: EMPTY_STRING
|
|
33308
33303
|
});
|
|
33309
33304
|
|
|
@@ -33326,10 +33321,8 @@ function index (style, options) {
|
|
|
33326
33321
|
// declarations
|
|
33327
33322
|
var decl;
|
|
33328
33323
|
while ((decl = declaration())) {
|
|
33329
|
-
|
|
33330
|
-
|
|
33331
|
-
comments(decls);
|
|
33332
|
-
}
|
|
33324
|
+
decls.push(decl);
|
|
33325
|
+
comments(decls);
|
|
33333
33326
|
}
|
|
33334
33327
|
|
|
33335
33328
|
return decls;
|
|
@@ -33339,47 +33332,34 @@ function index (style, options) {
|
|
|
33339
33332
|
return declarations();
|
|
33340
33333
|
}
|
|
33341
33334
|
|
|
33342
|
-
|
|
33343
|
-
|
|
33344
|
-
|
|
33345
|
-
|
|
33346
|
-
|
|
33347
|
-
|
|
33348
|
-
|
|
33349
|
-
|
|
33350
|
-
|
|
33351
|
-
|
|
33352
|
-
|
|
33353
|
-
|
|
33354
|
-
|
|
33355
|
-
|
|
33356
|
-
|
|
33357
|
-
* @returns - Style object or null.
|
|
33358
|
-
*
|
|
33359
|
-
* @example Parsing inline style to object:
|
|
33360
|
-
*
|
|
33361
|
-
* ```js
|
|
33362
|
-
* import parse from 'style-to-object';
|
|
33363
|
-
* parse('line-height: 42;'); // { 'line-height': '42' }
|
|
33364
|
-
* ```
|
|
33365
|
-
*/
|
|
33335
|
+
//#region src/index.ts
|
|
33336
|
+
/**
|
|
33337
|
+
* Parses inline style to object.
|
|
33338
|
+
*
|
|
33339
|
+
* @param style - Inline style.
|
|
33340
|
+
* @param iterator - Iterator.
|
|
33341
|
+
* @returns - Style object or null.
|
|
33342
|
+
*
|
|
33343
|
+
* @example Parsing inline style to object:
|
|
33344
|
+
*
|
|
33345
|
+
* ```js
|
|
33346
|
+
* import parse from 'style-to-object';
|
|
33347
|
+
* parse('line-height: 42;'); // { 'line-height': '42' }
|
|
33348
|
+
* ```
|
|
33349
|
+
*/
|
|
33366
33350
|
function StyleToObject(style, iterator) {
|
|
33367
|
-
|
|
33368
|
-
|
|
33369
|
-
|
|
33370
|
-
|
|
33371
|
-
|
|
33372
|
-
|
|
33373
|
-
|
|
33374
|
-
|
|
33375
|
-
|
|
33376
|
-
|
|
33377
|
-
|
|
33378
|
-
|
|
33379
|
-
styleObject[property] = value;
|
|
33380
|
-
}
|
|
33381
|
-
});
|
|
33382
|
-
return styleObject;
|
|
33351
|
+
let styleObject = null;
|
|
33352
|
+
if (!style || typeof style !== "string") return styleObject;
|
|
33353
|
+
const declarations = index(style);
|
|
33354
|
+
declarations.forEach((declaration) => {
|
|
33355
|
+
if (declaration.type !== "declaration") return;
|
|
33356
|
+
const { property, value } = declaration;
|
|
33357
|
+
if (value) {
|
|
33358
|
+
styleObject = styleObject ?? {};
|
|
33359
|
+
styleObject[property] = value;
|
|
33360
|
+
}
|
|
33361
|
+
});
|
|
33362
|
+
return styleObject;
|
|
33383
33363
|
}
|
|
33384
33364
|
|
|
33385
33365
|
var colorName$1;
|
|
@@ -50,6 +50,25 @@ export class List {
|
|
|
50
50
|
this.listElement.removeEventListener('mousedown', this.handleItemMouseDown);
|
|
51
51
|
this.listElement.addEventListener('mousedown', this.handleItemMouseDown);
|
|
52
52
|
this.mdcList = new MDCList(element);
|
|
53
|
+
// NOTE: This has no effect. It is kept as a marker rather than
|
|
54
|
+
// removed, so that its absence isn't mistaken for an oversight.
|
|
55
|
+
//
|
|
56
|
+
// MDC indexes each row for typeahead by looking for
|
|
57
|
+
// `.mdc-deprecated-list-item__primary-text` inside it, while
|
|
58
|
+
// `limel-list-item` renders its label as `<span class="label">`. MDC
|
|
59
|
+
// therefore builds an empty index, and typing does nothing.
|
|
60
|
+
//
|
|
61
|
+
// Do not "fix" this by adding MDC's class name to `limel-list-item`.
|
|
62
|
+
// That revives MDC's typeahead along with three behaviors that cannot
|
|
63
|
+
// be configured from the outside: a hard coded 300ms buffer, `Enter`
|
|
64
|
+
// being ignored for as long as that buffer lives (its `notifyAction`
|
|
65
|
+
// is guarded by `isTypeaheadInProgress`), and the space bar being
|
|
66
|
+
// excluded from matches. It would also collide with `limel-menu`,
|
|
67
|
+
// where single characters are used as item hotkeys.
|
|
68
|
+
//
|
|
69
|
+
// `limel-select` implements its own typeahead instead, see
|
|
70
|
+
// `src/util/typeahead.ts`. It intercepts typed characters in the
|
|
71
|
+
// capture phase on this very element, so they never reach MDC.
|
|
53
72
|
this.mdcList.hasTypeahead = true;
|
|
54
73
|
};
|
|
55
74
|
/**
|
|
@@ -2,11 +2,23 @@ import { MDCFloatingLabel } from "@material/floating-label";
|
|
|
2
2
|
import { MDCSelectHelperText } from "@material/select/helper-text";
|
|
3
3
|
import { h, } from "@stencil/core";
|
|
4
4
|
import { isMobileDevice } from "../../util/device";
|
|
5
|
-
import { ENTER,
|
|
5
|
+
import { ENTER, SPACEBAR } from "../../util/keycodes";
|
|
6
6
|
import { isMultiple } from "../../util/multiple";
|
|
7
7
|
import { createRandomString } from "../../util/random-string";
|
|
8
|
-
import {
|
|
8
|
+
import { findTypeaheadMatch, isTypeaheadKey, NO_TYPEAHEAD_MATCH, TypeaheadBuffer, } from "../../util/typeahead";
|
|
9
|
+
import { createMenuItems, SelectTemplate, triggerIconColorWarning, } from "./select.template";
|
|
9
10
|
/**
|
|
11
|
+
* ## Keyboard
|
|
12
|
+
*
|
|
13
|
+
* Typing characters jumps to the option whose text starts with them, the way
|
|
14
|
+
* a native `<select>` does. Characters accumulate for a short while, so typing
|
|
15
|
+
* `n`, `e` finds "Netherlands" rather than the next option starting with `n`.
|
|
16
|
+
* Pressing the same character repeatedly cycles through all options starting
|
|
17
|
+
* with it. This works both while the dropdown is open, and while the closed
|
|
18
|
+
* component has focus — in which case the dropdown opens with the match
|
|
19
|
+
* highlighted. Typing only moves the highlight; the value is not changed until
|
|
20
|
+
* the option is picked with `Enter` or a click.
|
|
21
|
+
*
|
|
10
22
|
* @exampleComponent limel-example-select-basic
|
|
11
23
|
* @exampleComponent limel-example-select-with-icons
|
|
12
24
|
* @exampleComponent limel-example-select-with-separators
|
|
@@ -26,6 +38,15 @@ export class Select {
|
|
|
26
38
|
updateHasPrimaryComponent() {
|
|
27
39
|
this.hasPrimaryComponentMemo = this.computeHasPrimaryComponent();
|
|
28
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* `options` and `required` are the inputs that decide which rows the
|
|
43
|
+
* dropdown renders, and therefore which row an index refers to. When
|
|
44
|
+
* either changes, a buffered typeahead match no longer points at what the
|
|
45
|
+
* user was aiming for.
|
|
46
|
+
*/
|
|
47
|
+
resetTypeaheadOnItemsChange() {
|
|
48
|
+
this.resetTypeahead();
|
|
49
|
+
}
|
|
29
50
|
constructor() {
|
|
30
51
|
/**
|
|
31
52
|
* Set to `true` to make the field disabled.
|
|
@@ -57,10 +78,51 @@ export class Select {
|
|
|
57
78
|
this.hasChanged = false;
|
|
58
79
|
this.hasPrimaryComponentMemo = false;
|
|
59
80
|
this.checkValid = false;
|
|
81
|
+
this.typeaheadBuffer = new TypeaheadBuffer();
|
|
82
|
+
this.handleMenuTriggerKeyDown = (event) => {
|
|
83
|
+
// Typeahead runs first, so that a space extends an ongoing typeahead
|
|
84
|
+
// instead of just opening the dropdown. A *bare* space is declined by
|
|
85
|
+
// `handleTypeaheadKey`, and keeps its usual meaning below.
|
|
86
|
+
if (this.handleTypeaheadKey(event, NO_TYPEAHEAD_MATCH)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const isEnter = event.key === ENTER;
|
|
90
|
+
const isSpace = event.key === SPACEBAR;
|
|
91
|
+
if (!this.menuOpen && (isSpace || isEnter)) {
|
|
92
|
+
event.stopPropagation();
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
// `preventDefault` cancels the activation click that the trigger
|
|
95
|
+
// `button` would otherwise synthesize, so the menu has to be
|
|
96
|
+
// opened here rather than through the click handler.
|
|
97
|
+
this.openMenu();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
this.setListElement = (element) => {
|
|
101
|
+
var _a, _b;
|
|
102
|
+
if (this.list === element) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
(_a = this.list) === null || _a === void 0 ? void 0 : _a.removeEventListener('keydown', this.handleListKeyDownCapture, true);
|
|
106
|
+
this.list = element;
|
|
107
|
+
(_b = this.list) === null || _b === void 0 ? void 0 : _b.addEventListener('keydown', this.handleListKeyDownCapture, true);
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Key handler for the dropdown, in the capture phase.
|
|
111
|
+
*
|
|
112
|
+
* `MDCList` listens for `keydown` on the `ul` inside `limel-list`'s shadow
|
|
113
|
+
* root, so capturing on the `limel-list` element itself is the only place
|
|
114
|
+
* a typed character can be stopped before MDC sees it. It has to be
|
|
115
|
+
* stopped: MDC would add it to its own typeahead buffer, which suppresses
|
|
116
|
+
* selection with `Enter` for as long as that buffer lives, and it treats
|
|
117
|
+
* the space bar as a selection.
|
|
118
|
+
*
|
|
119
|
+
* @param event - the key that was pressed
|
|
120
|
+
*/
|
|
121
|
+
this.handleListKeyDownCapture = (event) => {
|
|
122
|
+
this.handleTypeaheadKey(event, this.getFocusedMenuItemIndex());
|
|
123
|
+
};
|
|
60
124
|
this.handleMenuChange = this.handleMenuChange.bind(this);
|
|
61
125
|
this.handleNativeChange = this.handleNativeChange.bind(this);
|
|
62
|
-
this.handleMenuTriggerKeyPress =
|
|
63
|
-
this.handleMenuTriggerKeyPress.bind(this);
|
|
64
126
|
this.openMenu = this.openMenu.bind(this);
|
|
65
127
|
this.closeMenu = this.closeMenu.bind(this);
|
|
66
128
|
this.portalId = createRandomString();
|
|
@@ -96,6 +158,7 @@ export class Select {
|
|
|
96
158
|
}
|
|
97
159
|
disconnectedCallback() {
|
|
98
160
|
this.cancelPendingFocus();
|
|
161
|
+
this.resetTypeahead();
|
|
99
162
|
if (this.mdcFloatingLabel) {
|
|
100
163
|
this.mdcFloatingLabel.destroy();
|
|
101
164
|
}
|
|
@@ -110,7 +173,7 @@ export class Select {
|
|
|
110
173
|
}
|
|
111
174
|
render() {
|
|
112
175
|
const dropdownZIndex = getComputedStyle(this.host).getPropertyValue('--dropdown-z-index');
|
|
113
|
-
return (h(SelectTemplate, { key: '
|
|
176
|
+
return (h(SelectTemplate, { key: '97e6b0b033a487e26ab7e4abe7165837ebe08911', id: this.portalId, disabled: this.disabled || this.readonly, readonly: this.readonly, required: this.required, invalid: this.invalid, label: this.label, helperText: this.helperText, value: this.value, options: this.options, onMenuChange: this.handleMenuChange, onNativeChange: this.handleNativeChange, onTriggerKeyDown: this.handleMenuTriggerKeyDown, listRef: this.setListElement, multiple: this.multiple, isOpen: this.menuOpen, open: this.openMenu, close: this.closeMenu, checkValid: this.checkValid, native: this.shouldRenderNative(), dropdownZIndex: dropdownZIndex, anchor: this.getAnchorElement() }));
|
|
114
177
|
}
|
|
115
178
|
watchOpen(newValue, oldValue) {
|
|
116
179
|
if (this.checkValid) {
|
|
@@ -145,6 +208,21 @@ export class Select {
|
|
|
145
208
|
if (!this.menuOpen) {
|
|
146
209
|
return;
|
|
147
210
|
}
|
|
211
|
+
// Consumed on read, so that a later, unrelated re-render
|
|
212
|
+
// cannot resurrect a stale index and yank focus.
|
|
213
|
+
const typeaheadIndex = this.pendingTypeaheadIndex;
|
|
214
|
+
this.pendingTypeaheadIndex = undefined;
|
|
215
|
+
if (typeaheadIndex !== undefined &&
|
|
216
|
+
this.focusMenuItemAtIndex(list, typeaheadIndex)) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Picking an option while `multiple` re-renders the dropdown
|
|
220
|
+
// without closing it, which brings us back here with a row
|
|
221
|
+
// already focused. Moving to the first option then would lose
|
|
222
|
+
// the user's place in the list after every pick.
|
|
223
|
+
if (this.getFocusedMenuItemIndex() !== NO_TYPEAHEAD_MATCH) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
148
226
|
this.focusFirstMenuItem(list);
|
|
149
227
|
});
|
|
150
228
|
this.focusObserver.observe(list);
|
|
@@ -167,6 +245,30 @@ export class Select {
|
|
|
167
245
|
firstItem.focus({ preventScroll: true });
|
|
168
246
|
}
|
|
169
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Focus the row at a given index in the dropdown.
|
|
250
|
+
*
|
|
251
|
+
* The rows only become focusable once `limel-list` has set up `MDCList`,
|
|
252
|
+
* which is what gives them a `tabindex`. Callers use the return value to
|
|
253
|
+
* fall back to `pendingTypeaheadIndex` when that has not happened yet.
|
|
254
|
+
*
|
|
255
|
+
* @param list - the `limel-list` of the dropdown
|
|
256
|
+
* @param index - the index of the row, as rendered in its `data-index`
|
|
257
|
+
* @returns whether the row could be focused
|
|
258
|
+
*/
|
|
259
|
+
focusMenuItemAtIndex(list, index) {
|
|
260
|
+
var _a;
|
|
261
|
+
const item = (_a = list === null || list === void 0 ? void 0 : list.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector(`[data-index="${index}"]`);
|
|
262
|
+
if (!item) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
// Scrolled explicitly, and only within the dropdown: letting `focus()`
|
|
266
|
+
// scroll would scroll the page, since the dropdown is rendered into a
|
|
267
|
+
// portal that is absolutely positioned on the `body`.
|
|
268
|
+
item.focus({ preventScroll: true });
|
|
269
|
+
item.scrollIntoView({ block: 'nearest' });
|
|
270
|
+
return list.shadowRoot.activeElement === item;
|
|
271
|
+
}
|
|
170
272
|
setTriggerFocus() {
|
|
171
273
|
const trigger = this.host.shadowRoot.querySelector('.limel-select-trigger');
|
|
172
274
|
trigger.focus();
|
|
@@ -218,6 +320,7 @@ export class Select {
|
|
|
218
320
|
this.change.emit(option);
|
|
219
321
|
this.menuOpen = false;
|
|
220
322
|
this.cancelPendingFocus();
|
|
323
|
+
this.resetTypeahead();
|
|
221
324
|
this.setTriggerFocus();
|
|
222
325
|
}
|
|
223
326
|
openMenu() {
|
|
@@ -251,17 +354,111 @@ export class Select {
|
|
|
251
354
|
closeMenu() {
|
|
252
355
|
this.menuOpen = false;
|
|
253
356
|
this.cancelPendingFocus();
|
|
357
|
+
this.resetTypeahead();
|
|
254
358
|
this.setTriggerFocus();
|
|
255
359
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
360
|
+
/**
|
|
361
|
+
* Move the highlight to the option matching the characters typed so far,
|
|
362
|
+
* the way a native `<select>` does. Never changes the value — the option
|
|
363
|
+
* still has to be picked with `Enter` or a click.
|
|
364
|
+
*
|
|
365
|
+
* @param event - the key that was pressed
|
|
366
|
+
* @param focusedIndex - the row that currently has focus, if any
|
|
367
|
+
* @returns whether the key was handled as typeahead
|
|
368
|
+
*/
|
|
369
|
+
handleTypeaheadKey(event, focusedIndex) {
|
|
370
|
+
// The native dropdown on mobile devices does its own typeahead. Note
|
|
371
|
+
// that `setMenuFocus` bails out on mobile too, so a pending index
|
|
372
|
+
// would never be consumed there.
|
|
373
|
+
if (this.isMobileDevice || !isTypeaheadKey(event)) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
// A space only continues an ongoing typeahead. On its own it keeps its
|
|
377
|
+
// usual meaning of opening the dropdown, or selecting the focused
|
|
378
|
+
// option.
|
|
379
|
+
if (event.key === SPACEBAR && this.typeaheadBuffer.isEmpty) {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
event.preventDefault();
|
|
383
|
+
event.stopPropagation();
|
|
384
|
+
// Derived per key press rather than cached, so that the indices always
|
|
385
|
+
// refer to the rows the dropdown renders right now.
|
|
386
|
+
const items = createMenuItems(this.options, this.value, this.required);
|
|
387
|
+
const candidates = items.map((item) => ('separator' in item ? null : item));
|
|
388
|
+
const buffer = this.typeaheadBuffer.append(event.key);
|
|
389
|
+
const currentIndex = this.resolveTypeaheadIndex(items, focusedIndex);
|
|
390
|
+
const index = findTypeaheadMatch(candidates, buffer, currentIndex);
|
|
391
|
+
if (index !== NO_TYPEAHEAD_MATCH) {
|
|
392
|
+
this.focusTypeaheadMatch(index);
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* @param items - the items of the dropdown
|
|
398
|
+
* @param focusedIndex - the row that currently has focus, if any
|
|
399
|
+
* @returns the index a typeahead search should start from
|
|
400
|
+
*/
|
|
401
|
+
resolveTypeaheadIndex(items, focusedIndex) {
|
|
402
|
+
if (focusedIndex !== NO_TYPEAHEAD_MATCH) {
|
|
403
|
+
return focusedIndex;
|
|
404
|
+
}
|
|
405
|
+
// Typed again before focus had time to land in the dropdown.
|
|
406
|
+
if (this.pendingTypeaheadIndex !== undefined) {
|
|
407
|
+
return this.pendingTypeaheadIndex;
|
|
408
|
+
}
|
|
409
|
+
// Falls back to the selected option, so that typing continues from
|
|
410
|
+
// wherever the highlight already is. `findIndex` returning `-1` for a
|
|
411
|
+
// select without a value is exactly the "no current row" that
|
|
412
|
+
// `findTypeaheadMatch` expects.
|
|
413
|
+
return items.findIndex((item) => !('separator' in item) && item.selected);
|
|
414
|
+
}
|
|
415
|
+
focusTypeaheadMatch(index) {
|
|
416
|
+
// Overwrites any index still pending from an earlier character, even
|
|
417
|
+
// when this one can be focused right away. Opening the dropdown queues
|
|
418
|
+
// a `setMenuFocus` that waits for it to become visible, and someone
|
|
419
|
+
// typing quickly gets the next character in before that resolves;
|
|
420
|
+
// leaving the earlier index in place would let the queued focus apply
|
|
421
|
+
// it on top of this newer match.
|
|
422
|
+
this.pendingTypeaheadIndex = index;
|
|
423
|
+
if (this.menuOpen && this.focusMenuItemAtIndex(this.list, index)) {
|
|
424
|
+
// Focus landed synchronously, so this index has already done its
|
|
425
|
+
// job. Clearing it — rather than leaving it for `setMenuFocus` to
|
|
426
|
+
// consume later — stops it from being replayed by some unrelated
|
|
427
|
+
// future re-render, such as picking an option in a `multiple`
|
|
428
|
+
// select, after the user has since moved focus elsewhere with the
|
|
429
|
+
// arrow keys. A `setMenuFocus` still queued from opening the
|
|
430
|
+
// dropdown a moment ago is unaffected: it finds this row already
|
|
431
|
+
// focused and leaves it alone, below.
|
|
432
|
+
this.pendingTypeaheadIndex = undefined;
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (this.menuOpen) {
|
|
436
|
+
// Already open, so no re-render is coming that would trigger
|
|
437
|
+
// `componentDidUpdate`. Schedule the focus explicitly.
|
|
438
|
+
this.setMenuFocus();
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
this.openMenu();
|
|
263
442
|
}
|
|
264
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* @returns the index of the focused row of the dropdown, or
|
|
446
|
+
* `NO_TYPEAHEAD_MATCH` when no row has focus
|
|
447
|
+
*/
|
|
448
|
+
getFocusedMenuItemIndex() {
|
|
449
|
+
var _a, _b, _c;
|
|
450
|
+
// Deliberately not `event.target`: a listener on the `limel-list`
|
|
451
|
+
// element sees events from inside its shadow root retargeted to the
|
|
452
|
+
// element itself, never to the row that was actually focused.
|
|
453
|
+
const focused = (_b = (_a = this.list) === null || _a === void 0 ? void 0 : _a.shadowRoot) === null || _b === void 0 ? void 0 : _b.activeElement;
|
|
454
|
+
const row = focused === null || focused === void 0 ? void 0 : focused.closest('[data-index]');
|
|
455
|
+
const index = Number.parseInt((_c = row === null || row === void 0 ? void 0 : row.dataset.index) !== null && _c !== void 0 ? _c : '', 10);
|
|
456
|
+
return Number.isNaN(index) ? NO_TYPEAHEAD_MATCH : index;
|
|
457
|
+
}
|
|
458
|
+
resetTypeahead() {
|
|
459
|
+
this.typeaheadBuffer.clear();
|
|
460
|
+
this.pendingTypeaheadIndex = undefined;
|
|
461
|
+
}
|
|
265
462
|
handleNativeChange(event) {
|
|
266
463
|
event.stopPropagation();
|
|
267
464
|
const element = this.host.shadowRoot.querySelector('select.limel-select__native-control');
|
|
@@ -543,6 +740,12 @@ export class Select {
|
|
|
543
740
|
}, {
|
|
544
741
|
"propName": "options",
|
|
545
742
|
"methodName": "updateHasPrimaryComponent"
|
|
743
|
+
}, {
|
|
744
|
+
"propName": "options",
|
|
745
|
+
"methodName": "resetTypeaheadOnItemsChange"
|
|
746
|
+
}, {
|
|
747
|
+
"propName": "required",
|
|
748
|
+
"methodName": "resetTypeaheadOnItemsChange"
|
|
546
749
|
}, {
|
|
547
750
|
"propName": "menuOpen",
|
|
548
751
|
"methodName": "watchOpen"
|
|
@@ -52,7 +52,7 @@ const SelectValue = (props) => {
|
|
|
52
52
|
'limel-select-trigger': true,
|
|
53
53
|
'limel-select--focused': props.isOpen,
|
|
54
54
|
};
|
|
55
|
-
return (h("button", { slot: "content", class: anchorClassList, onClick: props.open,
|
|
55
|
+
return (h("button", { slot: "content", class: anchorClassList, onClick: props.open, onKeyDown: props.onTriggerKeyDown, "aria-haspopup": "listbox", "aria-expanded": props.isOpen, "aria-controls": props.id, "aria-labelledby": "s-label s-selected-text", "aria-required": props.required, disabled: props.disabled || props.readonly }, h("span", { class: "mdc-select__selected-text-container limel-select__selected-option" }, getSelectedIcon(props.value), getSelectedPrimaryComponent(props.value), h("span", { id: "s-selected-text", class: "mdc-select__selected-text limel-select__selected-option__text" }, getSelectedText(props.value))), h(ShowIcon, Object.assign({}, props, { isValid: props.isValid })), h("span", { class: "mdc-select__dropdown-icon" }, h("svg", { class: "mdc-select__dropdown-icon-graphic", viewBox: "7 10 10 5", focusable: "false" }, h("polygon", { stroke: "none", "fill-rule": "evenodd", points: "7 10 12 15 17 10" })))));
|
|
56
56
|
};
|
|
57
57
|
const ShowIcon = (props) => {
|
|
58
58
|
if (props.isValid) {
|
|
@@ -80,7 +80,7 @@ const MenuDropdown = (props) => {
|
|
|
80
80
|
display: 'flex',
|
|
81
81
|
'min-width': '100%',
|
|
82
82
|
width: 'fit-content',
|
|
83
|
-
} }, h("limel-list", { items: items, type: props.multiple ? 'checkbox' : 'selectable', onChange: props.onMenuChange }))));
|
|
83
|
+
} }, h("limel-list", { ref: props.listRef, items: items, type: props.multiple ? 'checkbox' : 'selectable', onChange: props.onMenuChange }))));
|
|
84
84
|
};
|
|
85
85
|
const NativeDropdown = (props) => {
|
|
86
86
|
const options = props.options
|
|
@@ -103,7 +103,21 @@ function isSelected(option, value) {
|
|
|
103
103
|
}
|
|
104
104
|
return option.value === value.value;
|
|
105
105
|
}
|
|
106
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Create the items for the dropdown.
|
|
108
|
+
*
|
|
109
|
+
* The returned array is index-aligned with the `data-index` attribute that
|
|
110
|
+
* `limel-list` renders on each row. Separators are included, and occupy an
|
|
111
|
+
* index. Anything that needs to address a row by index — such as the
|
|
112
|
+
* typeahead in `select.tsx` — must derive its indices from this same array.
|
|
113
|
+
*
|
|
114
|
+
* @param options - the options of the select
|
|
115
|
+
* @param value - the currently selected option or options
|
|
116
|
+
* @param selectIsRequired - whether the select requires a value, in which
|
|
117
|
+
* case the "empty" option is left out
|
|
118
|
+
* @returns the items to render in the dropdown
|
|
119
|
+
*/
|
|
120
|
+
export function createMenuItems(options, value, selectIsRequired = false) {
|
|
107
121
|
const menuOptionFilter = getMenuOptionFilter(selectIsRequired);
|
|
108
122
|
return options.filter(menuOptionFilter).map((option) => {
|
|
109
123
|
if ('separator' in option) {
|
|
@@ -11,6 +11,7 @@ import { createImageInserterPlugin } from "./plugins/image/inserter";
|
|
|
11
11
|
import { createImageViewPlugin } from "./plugins/image/view";
|
|
12
12
|
import { createMenuStateTrackingPlugin } from "./plugins/menu-state-tracking-plugin";
|
|
13
13
|
import { createActionBarInteractionPlugin } from "./plugins/menu-action-interaction-plugin";
|
|
14
|
+
import { createListKeyHandlerPlugin } from "./plugins/list-key-handler";
|
|
14
15
|
import { createTriggerPlugin } from "./plugins/trigger/factory";
|
|
15
16
|
import { getTableNodes, getTableEditingPlugins } from "./plugins/table-plugin";
|
|
16
17
|
import { getImageNode } from "./plugins/image/node";
|
|
@@ -62,7 +63,16 @@ export function buildEditorSchema(options) {
|
|
|
62
63
|
export function buildEditorPlugins(options) {
|
|
63
64
|
const { schema, menuCommandFactory, contentConverter, language, contentType, triggerCharacters, inlineImages, onNewLinkSelection, onImagePasted, onActiveItemsChange, } = options;
|
|
64
65
|
return [
|
|
65
|
-
|
|
66
|
+
// exampleSetup binds Shift-Ctrl-8/9 to a plain wrapInList. On
|
|
67
|
+
// Windows/Linux those are the same physical keys as our
|
|
68
|
+
// Mod-Shift-8/7 list commands, and exampleSetup's keymap runs
|
|
69
|
+
// first, so its bindings are suppressed to let the context-aware
|
|
70
|
+
// list commands own the shortcuts on every platform.
|
|
71
|
+
...exampleSetup({
|
|
72
|
+
schema: schema,
|
|
73
|
+
menuBar: false,
|
|
74
|
+
mapKeys: { 'Shift-Ctrl-8': false, 'Shift-Ctrl-9': false },
|
|
75
|
+
}),
|
|
66
76
|
keymap(menuCommandFactory.buildKeymap()),
|
|
67
77
|
createTriggerPlugin(triggerCharacters, contentConverter),
|
|
68
78
|
createLinkPlugin(onNewLinkSelection),
|
|
@@ -70,6 +80,7 @@ export function buildEditorPlugins(options) {
|
|
|
70
80
|
createImageViewPlugin(language),
|
|
71
81
|
createMenuStateTrackingPlugin(editorMenuTypesArray, menuCommandFactory, onActiveItemsChange),
|
|
72
82
|
createActionBarInteractionPlugin(menuCommandFactory),
|
|
83
|
+
createListKeyHandlerPlugin(schema),
|
|
73
84
|
...getTableEditingPlugins(contentType === 'html'),
|
|
74
85
|
];
|
|
75
86
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { LevelMapping } from "../types";
|
|
2
|
+
export const setActiveMethodForMark = (command, markType) => {
|
|
3
|
+
command.active = (state) => {
|
|
4
|
+
const { from, $from, to, empty } = state.selection;
|
|
5
|
+
if (empty) {
|
|
6
|
+
return !!markType.isInSet(state.storedMarks || $from.marks());
|
|
7
|
+
}
|
|
8
|
+
return state.doc.rangeHasMark(from, to, markType);
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export const setActiveMethodForNode = (command, nodeType, level) => {
|
|
12
|
+
command.active = (state) => {
|
|
13
|
+
const { $from } = state.selection;
|
|
14
|
+
const node = $from.node($from.depth);
|
|
15
|
+
if (node && node.type.name === nodeType.name) {
|
|
16
|
+
if (nodeType.name === LevelMapping.Heading && level) {
|
|
17
|
+
return node.attrs.level === level;
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export const setActiveMethodForWrap = (command, nodeType) => {
|
|
25
|
+
command.active = (state) => {
|
|
26
|
+
const { from, to } = state.selection;
|
|
27
|
+
let found = false;
|
|
28
|
+
state.doc.nodesBetween(from, to, (node) => {
|
|
29
|
+
if (node.type === nodeType) {
|
|
30
|
+
found = true;
|
|
31
|
+
}
|
|
32
|
+
return !found;
|
|
33
|
+
});
|
|
34
|
+
return found;
|
|
35
|
+
};
|
|
36
|
+
};
|