studiokit-scaffolding-js 4.5.2 → 4.5.3

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 (37) hide show
  1. package/lib/components/Quill/Formats/Image.d.ts +46 -0
  2. package/lib/components/Quill/Formats/Image.js +86 -0
  3. package/lib/components/Quill/Formats/List.d.ts +15 -0
  4. package/lib/components/Quill/Formats/List.js +79 -0
  5. package/lib/components/Quill/Formats/Video.d.ts +11 -0
  6. package/lib/components/Quill/Formats/Video.js +50 -0
  7. package/lib/components/{Blots → Quill/Specs}/CustomImageSpec.d.ts +2 -1
  8. package/lib/components/{Blots → Quill/Specs}/CustomImageSpec.js +10 -1
  9. package/lib/components/{Blots → Quill/Specs}/CustomVideoSpec.d.ts +0 -0
  10. package/lib/components/{Blots → Quill/Specs}/CustomVideoSpec.js +0 -0
  11. package/lib/components/Quill/TableModule/Blots/BaseTableBlot.d.ts +29 -0
  12. package/lib/components/Quill/TableModule/Blots/BaseTableBlot.js +126 -0
  13. package/lib/components/Quill/TableModule/Blots/TableBlot.d.ts +20 -0
  14. package/lib/components/Quill/TableModule/Blots/TableBlot.js +69 -0
  15. package/lib/components/Quill/TableModule/Blots/TableBodyBlot.d.ts +18 -0
  16. package/lib/components/Quill/TableModule/Blots/TableBodyBlot.js +71 -0
  17. package/lib/components/Quill/TableModule/Blots/TableCellBlot.d.ts +34 -0
  18. package/lib/components/Quill/TableModule/Blots/TableCellBlot.js +236 -0
  19. package/lib/components/Quill/TableModule/Blots/TableContainer.d.ts +16 -0
  20. package/lib/components/Quill/TableModule/Blots/TableContainer.js +103 -0
  21. package/lib/components/Quill/TableModule/Blots/TableRowBlot.d.ts +22 -0
  22. package/lib/components/Quill/TableModule/Blots/TableRowBlot.js +91 -0
  23. package/lib/components/Quill/TableModule/constants.d.ts +34 -0
  24. package/lib/components/Quill/TableModule/constants.js +42 -0
  25. package/lib/components/Quill/TableModule/index.css +171 -0
  26. package/lib/components/Quill/TableModule/index.d.ts +23 -0
  27. package/lib/components/Quill/TableModule/index.js +312 -0
  28. package/lib/components/Quill/TableModule/utils.d.ts +9 -0
  29. package/lib/components/Quill/TableModule/utils.js +54 -0
  30. package/lib/components/Quill/accessibilityFix.d.ts +19 -0
  31. package/lib/components/Quill/accessibilityFix.js +267 -0
  32. package/lib/components/Quill/index.d.ts +1 -0
  33. package/lib/components/Quill/index.js +27 -0
  34. package/lib/utils/dom.d.ts +25 -0
  35. package/lib/utils/dom.js +201 -0
  36. package/lib/utils/sort.js +1 -1
  37. package/package.json +4 -1
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyAccessibilityHacks = exports.applyAccessibilityToIcons = void 0;
4
+ var lodash_1 = require("lodash");
5
+ var constants_1 = require("./TableModule/constants");
6
+ /**
7
+ * Apply aria-labels to the icons from the quill text editor
8
+ * Imports the icons from Quill, recursively go through each icon and get the <svg /> html.
9
+ * Then it sets a <label /> between the <svg />
10
+ * Using css, it hides the label content.
11
+ *
12
+ * @param {*} icons The object that includes all the icons from Quill
13
+ */
14
+ function applyAccessibilityToIcons(icons) {
15
+ lodash_1.forEach(icons, function (value, key) {
16
+ // if the value is an object, recursively call this function
17
+ if (lodash_1.isPlainObject(value)) {
18
+ applyAccessibilityToIcons(value);
19
+ }
20
+ // if the value is a string, i.e, "<svg></svg>", then it adds the label
21
+ if (lodash_1.isString(value)) {
22
+ var regex = /^(.*) <\/svg>/g;
23
+ var match = regex.exec(value);
24
+ if (!match) {
25
+ return;
26
+ }
27
+ var iconName = void 0;
28
+ if (key) {
29
+ iconName = key;
30
+ }
31
+ else {
32
+ iconName = 'left';
33
+ }
34
+ var label = "<label class=\"hidden\" aria-label=\"" + iconName + "\">" + iconName + "</label>";
35
+ icons[key] = match[1] + label + '</svg>';
36
+ }
37
+ });
38
+ }
39
+ exports.applyAccessibilityToIcons = applyAccessibilityToIcons;
40
+ /**
41
+ * Grabs the DOM from the toolbar reference and loop through each button, picker, and dropdowns adding accessibility values
42
+ * Help from: https://github.com/quilljs/quill/issues/1173
43
+ *
44
+ * @param toolbarRef: reference to the div containing our custom Quill toolbar
45
+ * @param editor: reference to the Quill editor
46
+ */
47
+ function applyAccessibilityHacks(toolbarRef, editor) {
48
+ // Get ref to the toolbar, its not available through the quill api ughh
49
+ if (!toolbarRef.current) {
50
+ return;
51
+ }
52
+ var toolbar = toolbarRef.current;
53
+ // apply aria labels to base buttons
54
+ var buttons = toolbar.getElementsByTagName('button');
55
+ for (var _i = 0, _a = Array.from(buttons); _i < _a.length; _i++) {
56
+ var button = _a[_i];
57
+ var className = button.classList.value
58
+ .split(' ')
59
+ .filter(function (v) { return v.includes('ql-'); })
60
+ .map(function (v) { return v.replace('ql-', ''); })[0];
61
+ switch (className) {
62
+ case 'bold':
63
+ button.setAttribute('aria-label', 'Toggle bold text');
64
+ break;
65
+ case 'italic':
66
+ button.setAttribute('aria-label', 'Toggle italic text');
67
+ break;
68
+ case 'underline':
69
+ button.setAttribute('aria-label', 'Toggle underline text');
70
+ break;
71
+ case 'script':
72
+ if (button.value === 'sub') {
73
+ button.setAttribute('aria-label', 'Toggle subscript');
74
+ }
75
+ if (button.value === 'super') {
76
+ button.setAttribute('aria-label', 'Toggle superscript');
77
+ }
78
+ break;
79
+ case 'blockquote':
80
+ button.setAttribute('aria-label', 'Toggle blockquote text');
81
+ break;
82
+ case 'list':
83
+ if (button.value === 'ordered') {
84
+ button.setAttribute('aria-label', 'Toggle ordered list');
85
+ }
86
+ if (button.value === 'bullet') {
87
+ button.setAttribute('aria-label', 'Toggle bulleted list');
88
+ }
89
+ break;
90
+ case 'header':
91
+ if (button.value === '1') {
92
+ button.setAttribute('aria-label', 'Toggle heading 1');
93
+ }
94
+ if (button.value === '2') {
95
+ button.setAttribute('aria-label', 'Toggle heading 2');
96
+ }
97
+ break;
98
+ case 'strike':
99
+ button.setAttribute('aria-label', 'Toggle strike text');
100
+ break;
101
+ case 'code-block':
102
+ button.setAttribute('aria-label', 'Toggle code block text');
103
+ break;
104
+ case 'indent':
105
+ if (button.value === '-1') {
106
+ button.setAttribute('aria-label', 'Indent left');
107
+ }
108
+ if (button.value === '+1') {
109
+ button.setAttribute('aria-label', 'Indent right');
110
+ }
111
+ break;
112
+ case 'link':
113
+ button.setAttribute('aria-label', 'Insert link');
114
+ break;
115
+ case 'image':
116
+ button.setAttribute('aria-label', 'Insert image');
117
+ break;
118
+ case 'video':
119
+ button.setAttribute('aria-label', 'Insert video');
120
+ break;
121
+ case 'formula':
122
+ button.setAttribute('aria-label', 'Insert formula');
123
+ break;
124
+ case 'clean':
125
+ button.setAttribute('aria-label', 'Clear formatting');
126
+ break;
127
+ case 'table':
128
+ switch (button.value) {
129
+ case constants_1.TABLE_ACTION.INSERT_ROW:
130
+ button.setAttribute('aria-label', 'Insert row');
131
+ break;
132
+ case constants_1.TABLE_ACTION.DELETE_ROW:
133
+ button.setAttribute('aria-label', 'Delete row');
134
+ break;
135
+ case constants_1.TABLE_ACTION.INSERT_COL:
136
+ button.setAttribute('aria-label', 'Insert column');
137
+ break;
138
+ case constants_1.TABLE_ACTION.DELETE_COL:
139
+ button.setAttribute('aria-label', 'Delete column');
140
+ break;
141
+ }
142
+ break;
143
+ default:
144
+ break;
145
+ }
146
+ var ariaLabel = button.getAttribute('aria-label');
147
+ if (ariaLabel) {
148
+ button.setAttribute('title', ariaLabel);
149
+ }
150
+ // Put title tags to the dropdowns
151
+ var dropdowns = toolbar.getElementsByTagName('select');
152
+ for (var _b = 0, _c = Array.from(dropdowns); _b < _c.length; _b++) {
153
+ var dropdown = _c[_b];
154
+ var classNameAttribute = dropdown.getAttribute('class');
155
+ if (classNameAttribute) {
156
+ dropdown.setAttribute('title', classNameAttribute.toLowerCase());
157
+ }
158
+ }
159
+ // Make pickers work with keyboard and apply aria labels
160
+ var pickers = toolbar.getElementsByClassName('ql-picker');
161
+ var _loop_1 = function (picker) {
162
+ var label = picker.getElementsByClassName('ql-picker-label')[0];
163
+ var optionsContainer = picker.getElementsByClassName('ql-picker-options')[0];
164
+ var options = optionsContainer.getElementsByClassName('ql-picker-item');
165
+ label.setAttribute('role', 'button');
166
+ label.setAttribute('aria-haspopup', 'true');
167
+ label.setAttribute('tabindex', '0');
168
+ if (!!label.parentElement && label.parentElement.classList.contains('ql-font')) {
169
+ label.setAttribute('title', 'Font face');
170
+ }
171
+ if (!!label.parentElement && label.parentElement.classList.contains('ql-size')) {
172
+ label.setAttribute('title', 'Font size');
173
+ }
174
+ if (!!label.parentElement && label.parentElement.classList.contains('ql-align')) {
175
+ label.setAttribute('title', 'Alignment');
176
+ }
177
+ if (!!label.parentElement && label.parentElement.classList.contains('ql-table')) {
178
+ label.setAttribute('title', 'Insert table');
179
+ }
180
+ optionsContainer.setAttribute('aria-hidden', 'true');
181
+ optionsContainer.setAttribute('aria-label', 'submenu');
182
+ var _loop_3 = function (item) {
183
+ item.setAttribute('tabindex', '0');
184
+ item.setAttribute('role', 'button');
185
+ if (!lodash_1.includes(picker.classList, 'ql-align')) {
186
+ // Read the css 'content' values and generate aria labels for font face/size
187
+ var content = window.getComputedStyle(item, ':before').content;
188
+ if (content) {
189
+ var contentLabels = content.split('"').join('');
190
+ item.setAttribute('aria-label', contentLabels);
191
+ }
192
+ }
193
+ else {
194
+ // Add the title labels to the justification icons
195
+ var iconLabels = item.getElementsByTagName('label');
196
+ if (iconLabels.length === 1) {
197
+ var iconLabel = iconLabels[0].getAttribute('aria-label');
198
+ if (iconLabel) {
199
+ var iconTitle = iconLabel.charAt(0).toUpperCase() + iconLabel.slice(1);
200
+ item.setAttribute('title', iconTitle);
201
+ item.setAttribute('aria-label', iconLabel);
202
+ }
203
+ }
204
+ }
205
+ item.addEventListener('keyup', function (e) {
206
+ if (e.keyCode === 13) {
207
+ ;
208
+ item.click();
209
+ optionsContainer.setAttribute('aria-hidden', 'true');
210
+ picker.classList.remove('ql-expanded');
211
+ editor.focus();
212
+ }
213
+ });
214
+ };
215
+ for (var _i = 0, _a = Array.from(options); _i < _a.length; _i++) {
216
+ var item = _a[_i];
217
+ _loop_3(item);
218
+ }
219
+ label.addEventListener('keyup', function (e) {
220
+ if (e.keyCode === 13) {
221
+ ;
222
+ label.click();
223
+ picker.classList.add('ql-expanded');
224
+ optionsContainer.setAttribute('aria-hidden', 'false');
225
+ }
226
+ });
227
+ };
228
+ for (var _d = 0, _e = Array.from(pickers); _d < _e.length; _d++) {
229
+ var picker = _e[_d];
230
+ _loop_1(picker);
231
+ }
232
+ }
233
+ var tooltips = editor.container.getElementsByClassName('ql-tooltip');
234
+ for (var _f = 0, _g = Array.from(tooltips); _f < _g.length; _f++) {
235
+ var tooltip = _g[_f];
236
+ tooltip.setAttribute('tabindex', '-1');
237
+ var embedUrls = tooltip.getElementsByTagName('input');
238
+ for (var _h = 0, _j = Array.from(embedUrls); _h < _j.length; _h++) {
239
+ var embedUrl = _j[_h];
240
+ embedUrl.setAttribute('aria-label', 'Embed Url');
241
+ }
242
+ var previews = tooltip.getElementsByClassName('ql-preview');
243
+ for (var _k = 0, _l = Array.from(previews); _k < _l.length; _k++) {
244
+ var preview = _l[_k];
245
+ preview.setAttribute('aria-label', 'Preview');
246
+ preview.setAttribute('tabindex', '0');
247
+ }
248
+ var actions = tooltip.getElementsByClassName('ql-action');
249
+ var remove = tooltip.getElementsByClassName('ql-remove');
250
+ var allActions = Array.from(actions).concat(Array.from(remove));
251
+ var _loop_2 = function (action) {
252
+ action.setAttribute('tabindex', '0');
253
+ action.setAttribute('role', 'button');
254
+ action.addEventListener('keyup', function (e) {
255
+ if (e.keyCode === 13) {
256
+ ;
257
+ action.click();
258
+ }
259
+ });
260
+ };
261
+ for (var _m = 0, allActions_1 = allActions; _m < allActions_1.length; _m++) {
262
+ var action = allActions_1[_m];
263
+ _loop_2(action);
264
+ }
265
+ }
266
+ }
267
+ exports.applyAccessibilityHacks = applyAccessibilityHacks;
@@ -0,0 +1 @@
1
+ export declare const registerQuill: () => void;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerQuill = void 0;
7
+ var quill_blot_formatter_1 = __importDefault(require("quill-blot-formatter"));
8
+ var react_quill_1 = require("react-quill");
9
+ var accessibilityFix_1 = require("./accessibilityFix");
10
+ var Image_1 = require("./Formats/Image");
11
+ var List_1 = require("./Formats/List");
12
+ var Video_1 = require("./Formats/Video");
13
+ var TableModule_1 = require("./TableModule");
14
+ var registerQuill = function () {
15
+ react_quill_1.Quill.register('modules/blotFormatter', quill_blot_formatter_1.default);
16
+ // replace image format
17
+ react_quill_1.Quill.register('formats/image', Image_1.Image, true);
18
+ // replace list format with new extended version
19
+ react_quill_1.Quill.register('formats/list/item', List_1.ListItem, true);
20
+ react_quill_1.Quill.register('formats/list', List_1.List, true);
21
+ // replace image format
22
+ react_quill_1.Quill.register('formats/video', Video_1.Video, true);
23
+ react_quill_1.Quill.register('modules/table', TableModule_1.TableModule);
24
+ // apply the accessibility label issues to each icons
25
+ accessibilityFix_1.applyAccessibilityToIcons(react_quill_1.Quill.import('ui/icons'));
26
+ };
27
+ exports.registerQuill = registerQuill;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Get the nearest vertically scrollable parent of an element
3
+ * @param { HTMLElement } element - The element to start with
4
+ * @returns The nearest vertically scrollable parent element
5
+ */
6
+ export declare const getVerticallyScrollableParent: (element: HTMLElement) => Element | null;
7
+ /**
8
+ * Get the first parent that has the given className in its classList
9
+ * @param element The target element
10
+ * @param className The className to match
11
+ */
12
+ export declare const getFirstParentElementWithClassName: (element: HTMLElement, className: string) => HTMLElement | undefined;
13
+ /**
14
+ * Return whether the device being used is one of several mainstream mobile devices
15
+ */
16
+ export declare const isMobileDevice: () => number | boolean;
17
+ /** Return whether the device is using an external keyboard vs. the native on-screen OS keyboard. */
18
+ export declare const hasExternalKeyboard: () => boolean | undefined;
19
+ /**
20
+ * Is the given element capable of text input?
21
+ *
22
+ * @param el An HTML element
23
+ */
24
+ export declare const isTextInput: (el: HTMLElement) => boolean;
25
+ export declare const setupMobileEventListeners: () => void;
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupMobileEventListeners = exports.isTextInput = exports.hasExternalKeyboard = exports.isMobileDevice = exports.getFirstParentElementWithClassName = exports.getVerticallyScrollableParent = void 0;
4
+ var logger_1 = require("./logger");
5
+ var KEYBOARD_ANIMATION_MS = 250;
6
+ /**
7
+ * Get the nearest vertically scrollable parent of an element
8
+ * @param { HTMLElement } element - The element to start with
9
+ * @returns The nearest vertically scrollable parent element
10
+ */
11
+ var getVerticallyScrollableParent = function (element) {
12
+ var style = getComputedStyle(element);
13
+ var excludeStaticParent = style.position === 'absolute';
14
+ var overflowRegex = /(auto|scroll|hidden)/;
15
+ for (var parent_1 = element; (parent_1 = parent_1.parentElement);) {
16
+ style = getComputedStyle(parent_1);
17
+ if (excludeStaticParent && style.position === 'static')
18
+ continue;
19
+ if (overflowRegex.test(style.overflow + style.overflowY) && parent_1.scrollHeight > parent_1.clientHeight)
20
+ return parent_1;
21
+ }
22
+ return document.scrollingElement;
23
+ };
24
+ exports.getVerticallyScrollableParent = getVerticallyScrollableParent;
25
+ /**
26
+ * Get the first parent that has the given className in its classList
27
+ * @param element The target element
28
+ * @param className The className to match
29
+ */
30
+ var getFirstParentElementWithClassName = function (element, className) {
31
+ for (var parent_2 = element; (parent_2 = parent_2.parentElement);) {
32
+ if (parent_2.classList.contains(className))
33
+ return parent_2;
34
+ }
35
+ return undefined;
36
+ };
37
+ exports.getFirstParentElementWithClassName = getFirstParentElementWithClassName;
38
+ /**
39
+ * Return whether the device being used is one of several mainstream mobile devices
40
+ */
41
+ var isMobileDevice = function () {
42
+ return (/android|ipad|ipod|iphone/i.test(navigator.userAgent) ||
43
+ (/macintosh/i.test(navigator.userAgent) && navigator.maxTouchPoints));
44
+ };
45
+ exports.isMobileDevice = isMobileDevice;
46
+ /** Return whether the device is using an external keyboard vs. the native on-screen OS keyboard. */
47
+ var hasExternalKeyboard = function () { return window.hasExternalKeyboard; };
48
+ exports.hasExternalKeyboard = hasExternalKeyboard;
49
+ /**
50
+ * Sets `window.hasExternalKeyboard`, returned by `hasExternalKeyboard`.
51
+ */
52
+ var setHasExternalKeyboard = function (hasExternalKeyboard) {
53
+ logger_1.getLogger().debug('hasExternalKeyboard', hasExternalKeyboard);
54
+ var windowWithoutType = window;
55
+ windowWithoutType.hasExternalKeyboard = hasExternalKeyboard;
56
+ };
57
+ /**
58
+ * 100vh is kind of b⊘rked on mobile safari, we have to set it programmatically here
59
+ * so we can calculate device-independent viewport heights
60
+ * https://allthingssmitty.com/2020/05/11/css-fix-for-100vh-in-mobile-webkit/
61
+ *
62
+ * Sets the CSS variable `--visual-viewport-height` on the `documentElement`/<html>.
63
+ *
64
+ */
65
+ var setVisualViewportHeightVar = function () {
66
+ var height = visualViewport.height;
67
+ document.documentElement.style.setProperty('--visual-viewport-height', height + "px");
68
+ };
69
+ /**
70
+ * Is the given element capable of text input?
71
+ *
72
+ * @param el An HTML element
73
+ */
74
+ var isTextInput = function (el) {
75
+ var tagName = el && el.tagName && el.tagName.toLowerCase();
76
+ if (tagName === 'textarea')
77
+ return true;
78
+ if (tagName === 'input') {
79
+ var inputEl = el;
80
+ return inputEl.type !== 'button' && inputEl.type !== 'radio' && inputEl.type !== 'checkbox';
81
+ }
82
+ // Quill editor
83
+ return tagName === 'div' && el.className.includes('ql-editor') && el.contentEditable === 'true';
84
+ };
85
+ exports.isTextInput = isTextInput;
86
+ var updateHasExternalKeyboardAfterFocusTimeout = undefined;
87
+ var scrollToElementAfterFocusTimeout = undefined;
88
+ var updateHasExternalKeyboardAfterFocus = function () {
89
+ var closedVisualViewportHeight = visualViewport.height;
90
+ // wait for the keyboard animation
91
+ updateHasExternalKeyboardAfterFocusTimeout = window.setTimeout(function () {
92
+ // find the keyboard height based on the difference in viewport height
93
+ var keyboardHeight = closedVisualViewportHeight - visualViewport.height;
94
+ // on first-load the input focus can fail
95
+ // when this happens the height is 0, and this ONLY happens when there is NOT an external keyboard
96
+ if (keyboardHeight === 0 && exports.hasExternalKeyboard() === undefined) {
97
+ setHasExternalKeyboard(false);
98
+ }
99
+ else {
100
+ // if the keyboard height is greater than 100, that means the full onscreen keyboard displayed
101
+ // otherwise, only the small context menu displayed, meaning there is an external keyboard
102
+ setHasExternalKeyboard(keyboardHeight < 100);
103
+ }
104
+ updateHasExternalKeyboardAfterFocusTimeout = undefined;
105
+ }, KEYBOARD_ANIMATION_MS);
106
+ };
107
+ var scrollToElementAfterFocus = function (el) {
108
+ // wait for the keyboard animation
109
+ scrollToElementAfterFocusTimeout = window.setTimeout(function () {
110
+ // after the keyboard is open, check for the el’s scrollable parent, as it could have just become scrollable
111
+ var scrollParent = exports.getVerticallyScrollableParent(el);
112
+ // if the el has a scrollable parent other than the document, scroll it back into view
113
+ if (scrollParent && scrollParent !== document.documentElement) {
114
+ var scrollRect = scrollParent.getBoundingClientRect();
115
+ var rect = el.getBoundingClientRect();
116
+ // only scroll into view if outside of the scroll parent bounds
117
+ if (rect.bottom < scrollRect.top || rect.top > scrollRect.bottom) {
118
+ var newScrollTop = scrollParent.scrollTop + rect.top - scrollRect.top - 10;
119
+ logger_1.getLogger().debug('scrollToEl', newScrollTop);
120
+ if (scrollParent.scrollTo) {
121
+ scrollParent.scrollTo({ top: newScrollTop, behavior: 'smooth' });
122
+ }
123
+ else {
124
+ scrollParent.scrollTop = newScrollTop;
125
+ }
126
+ }
127
+ }
128
+ scrollToElementAfterFocusTimeout = undefined;
129
+ }, KEYBOARD_ANIMATION_MS);
130
+ };
131
+ /**
132
+ * Event handler for "focusin" event.
133
+ *
134
+ * "focusin" bubbles up from all focus events, so uses `isTextInput` to only react to "keyboard" focus events.
135
+ *
136
+ * Checks for external keyboard and scrolls to the focused element after any animations, e.g. keyboard open.
137
+ *
138
+ */
139
+ var onFocusIn = function (e) {
140
+ if (!e.target || !exports.isTextInput(e.target))
141
+ return;
142
+ updateHasExternalKeyboardAfterFocus();
143
+ scrollToElementAfterFocus(e.target);
144
+ };
145
+ /**
146
+ * Event handler for "focusout" event.
147
+ *
148
+ * "focusout" bubbles up from all focus events, so uses `isTextInput` to only react to "keyboard" focus events.
149
+ *
150
+ * Clears any pending timeouts from "focusin" events.
151
+ *
152
+ */
153
+ var onFocusOut = function (e) {
154
+ if (!e.target || !exports.isTextInput(e.target))
155
+ return;
156
+ if (updateHasExternalKeyboardAfterFocusTimeout) {
157
+ window.clearTimeout(updateHasExternalKeyboardAfterFocusTimeout);
158
+ updateHasExternalKeyboardAfterFocusTimeout = undefined;
159
+ }
160
+ if (scrollToElementAfterFocusTimeout) {
161
+ window.clearTimeout(scrollToElementAfterFocusTimeout);
162
+ scrollToElementAfterFocusTimeout = undefined;
163
+ }
164
+ };
165
+ /**
166
+ * Check if the device is using an external keyboard by appending an `input` and focusing it.
167
+ *
168
+ * This will trigger the `onFocusIn` event handler, which updates the `hasExternalKeyboard` global.
169
+ */
170
+ var checkForExternalKeyboard = function () {
171
+ var el = document.createElement('input');
172
+ el.type = 'text';
173
+ el.autofocus = true;
174
+ el.addEventListener('focusin', function () {
175
+ // remove the input after the keyboard has displayed, plus a small buffer
176
+ window.setTimeout(function () {
177
+ el.blur();
178
+ el.remove();
179
+ }, KEYBOARD_ANIMATION_MS + 100);
180
+ });
181
+ document.documentElement.append(el);
182
+ };
183
+ var setupMobileEventListeners = function () {
184
+ if (!exports.isMobileDevice())
185
+ return;
186
+ // set viewport height var at setup
187
+ setVisualViewportHeightVar();
188
+ // add event listeners
189
+ visualViewport.addEventListener('resize', function () {
190
+ setVisualViewportHeightVar();
191
+ // keep the document element scrolled to the top if a ReactModal is open
192
+ if (document.body.classList.contains('ReactModal__Body--open')) {
193
+ document.documentElement.scrollTop = 0;
194
+ }
195
+ });
196
+ window.addEventListener('focusin', onFocusIn);
197
+ window.addEventListener('focusout', onFocusOut);
198
+ // check for external keyboard at setup
199
+ checkForExternalKeyboard();
200
+ };
201
+ exports.setupMobileEventListeners = setupMobileEventListeners;
package/lib/utils/sort.js CHANGED
@@ -60,7 +60,7 @@ var sortByNames = function (a, b) {
60
60
  };
61
61
  exports.sortByNames = sortByNames;
62
62
  var sortByNameNatural = function (a, b) {
63
- return a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' });
63
+ return a.name.trim().localeCompare(b.name.trim(), undefined, { numeric: true, sensitivity: 'base' });
64
64
  };
65
65
  exports.sortByNameNatural = sortByNameNatural;
66
66
  var sort = function (array, attributeName, shouldSortAscending) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "studiokit-scaffolding-js",
3
- "version": "4.5.2",
3
+ "version": "4.5.3",
4
4
  "description": "Common scaffolding for Studio apps at Purdue",
5
5
  "repository": "https://gitlab.com/purdue-informatics/studiokit/studiokit-scaffolding-js",
6
6
  "license": "MIT",
@@ -123,9 +123,11 @@
123
123
  "local-storage-fallback": "^4.1.1",
124
124
  "lodash": "^4.17.20",
125
125
  "moment-timezone": "^0.5.32",
126
+ "parchment": "^1.1.4",
126
127
  "pluralize": "^8.0.0",
127
128
  "prop-types": "^15.7.2",
128
129
  "query-string": "^6.13.8",
130
+ "quill": "^1.3.7",
129
131
  "quill-blot-formatter": "purdue-tlt/quill-blot-formatter#fix/accessibility-error-built",
130
132
  "react": "^17.0.1",
131
133
  "react-bootstrap": "^1.4.3",
@@ -133,6 +135,7 @@
133
135
  "react-ga": "^3.3.0",
134
136
  "react-helmet": "^6.1.0",
135
137
  "react-modal": "^3.12.1",
138
+ "react-quill": "^1.3.5",
136
139
  "react-redux": "^7.2.2",
137
140
  "react-router": "^5.2.0",
138
141
  "react-router-bootstrap": "^0.25.0",