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.
- package/lib/components/Quill/Formats/Image.d.ts +46 -0
- package/lib/components/Quill/Formats/Image.js +86 -0
- package/lib/components/Quill/Formats/List.d.ts +15 -0
- package/lib/components/Quill/Formats/List.js +79 -0
- package/lib/components/Quill/Formats/Video.d.ts +11 -0
- package/lib/components/Quill/Formats/Video.js +50 -0
- package/lib/components/{Blots → Quill/Specs}/CustomImageSpec.d.ts +2 -1
- package/lib/components/{Blots → Quill/Specs}/CustomImageSpec.js +10 -1
- package/lib/components/{Blots → Quill/Specs}/CustomVideoSpec.d.ts +0 -0
- package/lib/components/{Blots → Quill/Specs}/CustomVideoSpec.js +0 -0
- package/lib/components/Quill/TableModule/Blots/BaseTableBlot.d.ts +29 -0
- package/lib/components/Quill/TableModule/Blots/BaseTableBlot.js +126 -0
- package/lib/components/Quill/TableModule/Blots/TableBlot.d.ts +20 -0
- package/lib/components/Quill/TableModule/Blots/TableBlot.js +69 -0
- package/lib/components/Quill/TableModule/Blots/TableBodyBlot.d.ts +18 -0
- package/lib/components/Quill/TableModule/Blots/TableBodyBlot.js +71 -0
- package/lib/components/Quill/TableModule/Blots/TableCellBlot.d.ts +34 -0
- package/lib/components/Quill/TableModule/Blots/TableCellBlot.js +236 -0
- package/lib/components/Quill/TableModule/Blots/TableContainer.d.ts +16 -0
- package/lib/components/Quill/TableModule/Blots/TableContainer.js +103 -0
- package/lib/components/Quill/TableModule/Blots/TableRowBlot.d.ts +22 -0
- package/lib/components/Quill/TableModule/Blots/TableRowBlot.js +91 -0
- package/lib/components/Quill/TableModule/constants.d.ts +34 -0
- package/lib/components/Quill/TableModule/constants.js +42 -0
- package/lib/components/Quill/TableModule/index.css +171 -0
- package/lib/components/Quill/TableModule/index.d.ts +23 -0
- package/lib/components/Quill/TableModule/index.js +312 -0
- package/lib/components/Quill/TableModule/utils.d.ts +9 -0
- package/lib/components/Quill/TableModule/utils.js +54 -0
- package/lib/components/Quill/accessibilityFix.d.ts +19 -0
- package/lib/components/Quill/accessibilityFix.js +267 -0
- package/lib/components/Quill/index.d.ts +1 -0
- package/lib/components/Quill/index.js +27 -0
- package/lib/utils/dom.d.ts +25 -0
- package/lib/utils/dom.js +201 -0
- package/lib/utils/sort.js +1 -1
- 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;
|
package/lib/utils/dom.js
ADDED
|
@@ -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.
|
|
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",
|