unicode-input-toolconverter 0.2.0 → 0.2.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/.nyc_output/out.json +8980 -1849
- package/CHANGES.md +15 -1
- package/README.md +6 -11
- package/_locales/en-US/messages.json +153 -6
- package/_locales/hu-HU/messages.json +153 -6
- package/_locales/pt-BR/messages.json +153 -6
- package/_locales/sv-SE/messages.json +153 -6
- package/babel.config.json +0 -1
- package/browser_action/characterSelection.js +18 -11
- package/browser_action/charrefConverters.js +286 -79
- package/browser_action/chartBuild.js +97 -27
- package/browser_action/encodingBehaviors.js +10 -7
- package/browser_action/entityBehaviors.js +42 -16
- package/browser_action/index-es.html +13 -1
- package/browser_action/index-instrumented.html +13 -1
- package/browser_action/index-pages.html +13 -1
- package/browser_action/index.html +13 -1
- package/browser_action/index.iife.min.js +9 -1
- package/browser_action/index.iife.min.js.map +1 -1
- package/browser_action/index.instrumented.iife.min.js +1 -1
- package/browser_action/index.instrumented.iife.min.js.map +1 -1
- package/browser_action/index.js +5 -5
- package/browser_action/preferences/prefDefaults.js +12 -3
- package/browser_action/service-worker/sw-activateCallback.js +1 -2
- package/browser_action/service-worker/sw-resources.json +6 -4
- package/browser_action/templateUtils/elements.js +12 -1
- package/browser_action/templateUtils/fill.js +6 -0
- package/browser_action/templateUtils/validation.js +4 -1
- package/browser_action/templates/chartBuild.js +167 -102
- package/browser_action/templates/index.js +227 -53
- package/browser_action/templatesElementCustomization/widgets.js +40 -15
- package/browser_action/unicode/UnicodeConverter.js +99 -73
- package/browser_action/unicode/charrefunicodeDb.js +100 -50
- package/browser_action/unicode/getScriptInfoForCodePoint.js +13 -4
- package/browser_action/unicode/hangul.js +27 -20
- package/browser_action/unicode/lastScriptNames.json +1 -1
- package/browser_action/unicode/parseUnihanFromTextFileStrings.js +44 -25
- package/browser_action/unicode/unicodeFieldInfo.js +112 -90
- package/browser_action/unicode/unicodeScripts.js +409 -35
- package/browser_action/unicode/unihan.js +4 -3
- package/browser_action/unicode/unihanDbPopulate.js +7 -2
- package/browser_action/unicode/unihanFields.js +34 -0
- package/browser_action/unicodecharref.js +353 -190
- package/browser_action/utils/DOMUtils.js +95 -34
- package/browser_action/utils/FetchUtils.js +7 -6
- package/browser_action/utils/TextUtils.js +4 -4
- package/browser_action/utils/TypedArrayUtils.js +9 -5
- package/browser_action/utils/semicolonSeparatedToArray.js +3 -3
- package/browser_action/utils/setupServiceWorker.js +2 -2
- package/eslint.config.js +11 -0
- package/icons/openWindow16.png +0 -0
- package/icons/openWindow24.png +0 -0
- package/lib/background.html +7 -0
- package/lib/background.js +2 -2
- package/package.json +80 -70
- package/pnpm-workspace.yaml +8 -0
- package/server.js +66 -51
- package/sw.js +97 -56
- package/tools/entities-import.js +4 -1
- package/tools/findEsResources.js +33 -10
- package/tools/fix-sw-resources-vendor-paths.js +60 -0
- package/tools/list-locales.js +2 -2
- package/tools/parseUnicodeCharts.js +126 -60
- package/tools/ucd-import.js +1 -10
- package/tools/unicode-charts.html +49 -20
- package/tools/unihan-import.js +45 -15
- package/tools/write-sw-version.js +16 -0
- package/tsconfig.json +23 -0
- package/typings/__coverage__.d.ts +5 -0
- package/typings/apple-system-profiler.d.ts +17 -0
- package/typings/commands.d.ts +19 -0
- package/typings/json-6.d.ts +4 -0
- package/typings/rollup-plugin-istanbul.d.ts +3 -0
- package/vendor/camelcase/index.d.ts +154 -0
- package/vendor/camelcase/index.js +137 -23
- package/vendor/fflate/esm/browser.d.ts +1539 -0
- package/vendor/fflate/esm/browser.js +82 -55
- package/vendor/intl-dom/dist/Formatter.d.ts +120 -0
- package/vendor/intl-dom/dist/Formatter.d.ts.map +1 -0
- package/vendor/intl-dom/dist/collation.d.ts +42 -0
- package/vendor/intl-dom/dist/collation.d.ts.map +1 -0
- package/vendor/intl-dom/dist/defaultAllSubstitutions.d.ts +52 -0
- package/vendor/intl-dom/dist/defaultAllSubstitutions.d.ts.map +1 -0
- package/vendor/intl-dom/dist/defaultInsertNodes.d.ts +96 -0
- package/vendor/intl-dom/dist/defaultInsertNodes.d.ts.map +1 -0
- package/vendor/intl-dom/dist/defaultKeyCheckerConverter.d.ts +17 -0
- package/vendor/intl-dom/dist/defaultKeyCheckerConverter.d.ts.map +1 -0
- package/vendor/intl-dom/dist/defaultLocaleResolver.d.ts +285 -0
- package/vendor/intl-dom/dist/defaultLocaleResolver.d.ts.map +1 -0
- package/vendor/intl-dom/dist/findLocaleStrings.d.ts +88 -0
- package/vendor/intl-dom/dist/findLocaleStrings.d.ts.map +1 -0
- package/vendor/intl-dom/dist/getDOMForLocaleString.d.ts +67 -0
- package/vendor/intl-dom/dist/getDOMForLocaleString.d.ts.map +1 -0
- package/vendor/intl-dom/dist/getMessageForKeyByStyle.d.ts +73 -0
- package/vendor/intl-dom/dist/getMessageForKeyByStyle.d.ts.map +1 -0
- package/vendor/intl-dom/dist/getStringFromMessageAndDefaults.d.ts +26 -0
- package/vendor/intl-dom/dist/getStringFromMessageAndDefaults.d.ts.map +1 -0
- package/vendor/intl-dom/dist/i18n.d.ts +113 -0
- package/vendor/intl-dom/dist/i18n.d.ts.map +1 -0
- package/vendor/intl-dom/dist/index.d.ts +92 -0
- package/vendor/intl-dom/dist/index.d.ts.map +1 -0
- package/vendor/intl-dom/dist/index.esm.d.ts +34 -0
- package/vendor/intl-dom/dist/index.esm.js +854 -896
- package/vendor/intl-dom/dist/index.esm.min.js +4 -0
- package/vendor/intl-dom/dist/index.esm.min.js.map +1 -0
- package/vendor/intl-dom/dist/index.umd.js +2953 -0
- package/vendor/intl-dom/dist/index.umd.min.js +4 -0
- package/vendor/intl-dom/dist/index.umd.min.js.map +1 -0
- package/vendor/intl-dom/dist/promiseChainForValues.d.ts +34 -0
- package/vendor/intl-dom/dist/promiseChainForValues.d.ts.map +1 -0
- package/vendor/intl-dom/dist/shared.d.ts +20 -0
- package/vendor/intl-dom/dist/shared.d.ts.map +1 -0
- package/vendor/intl-dom/dist/utils.d.ts +53 -0
- package/vendor/intl-dom/dist/utils.d.ts.map +1 -0
- package/vendor/jamilih/dist/jml.d.mts +468 -0
- package/vendor/jamilih/dist/{jml-es.js → jml.mjs} +1489 -343
- package/vendor/jquery/dist/jquery.js +1677 -2713
- package/vendor/json-6/dist/index.mjs +2 -0
- package/vendor/miller-columns/CHANGES.md +185 -0
- package/vendor/miller-columns/LICENSE-MIT.txt +21 -0
- package/vendor/miller-columns/README.md +249 -0
- package/vendor/miller-columns/demos/index.html +69 -0
- package/vendor/miller-columns/demos/index.js +72 -0
- package/vendor/miller-columns/dist/index-es.js +748 -0
- package/vendor/miller-columns/dist/index-es.min.d.ts +25 -0
- package/vendor/miller-columns/dist/index-es.min.js +7 -1
- package/vendor/miller-columns/dist/index-umd.js +757 -0
- package/vendor/miller-columns/dist/index-umd.min.js +7 -0
- package/vendor/miller-columns/dist/index.d.ts +25 -0
- package/vendor/miller-columns/dist/millerColumns.d.ts +10 -0
- package/vendor/miller-columns/eslint.config.js +46 -0
- package/vendor/miller-columns/fix-declarations.js +18 -0
- package/vendor/miller-columns/miller-columns.css +24 -1
- package/vendor/miller-columns/package.json +73 -0
- package/vendor/miller-columns/pnpm-workspace.yaml +2 -0
- package/vendor/miller-columns/src/index.js +642 -0
- package/vendor/miller-columns/src/millerColumns.ts +12 -0
- package/vendor/miller-columns/tsconfig-prod.json +23 -0
- package/vendor/miller-columns/tsconfig.json +21 -0
- package/vendor/simple-prefs/dist/index.esm.d.ts +1 -0
- package/vendor/simple-prefs/dist/index.esm.js +164 -216
- package/vendor/simple-prefs/dist/simple-prefs.d.ts +117 -0
- package/lgtm.yml +0 -5
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.addMillerColumnPlugin = factory());
|
|
5
|
+
})(this, (function () { 'use strict';
|
|
6
|
+
|
|
7
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {{
|
|
10
|
+
* before?: HTMLElement,
|
|
11
|
+
* after?: HTMLElement,
|
|
12
|
+
* favicon?: boolean,
|
|
13
|
+
* image?: boolean,
|
|
14
|
+
* canvas?: boolean,
|
|
15
|
+
* }} Options
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {string|
|
|
20
|
+
* (string|[stylesheetURL: string, options: Options])[]} Stylesheets
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* @param {Stylesheets} stylesheets
|
|
24
|
+
* @param {{
|
|
25
|
+
* before?: HTMLElement,
|
|
26
|
+
* after?: HTMLElement,
|
|
27
|
+
* favicon?: boolean,
|
|
28
|
+
* image?: boolean,
|
|
29
|
+
* canvas?: boolean,
|
|
30
|
+
* acceptErrors?: boolean|((info: {
|
|
31
|
+
* error: ErrorEvent,
|
|
32
|
+
* stylesheetURL: string,
|
|
33
|
+
* options: {},
|
|
34
|
+
* resolve: (value: any) => void,
|
|
35
|
+
* reject: (reason?: any) => void
|
|
36
|
+
* }) => (reason?: any) => void)
|
|
37
|
+
* }} cfg
|
|
38
|
+
* @returns {Promise<HTMLLinkElement[]>}
|
|
39
|
+
*/
|
|
40
|
+
function loadStylesheets(stylesheets, {
|
|
41
|
+
before: beforeDefault,
|
|
42
|
+
after: afterDefault,
|
|
43
|
+
favicon: faviconDefault,
|
|
44
|
+
canvas: canvasDefault,
|
|
45
|
+
image: imageDefault = true,
|
|
46
|
+
acceptErrors
|
|
47
|
+
} = {}) {
|
|
48
|
+
stylesheets = Array.isArray(stylesheets) ? stylesheets : [stylesheets];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {string|[stylesheetURL: string, options: Options]} stylesheetURLInfo
|
|
52
|
+
* @returns {Promise<HTMLLinkElement>}
|
|
53
|
+
*/
|
|
54
|
+
function setupLink(stylesheetURLInfo) {
|
|
55
|
+
/** @type {Options} */
|
|
56
|
+
let options = {};
|
|
57
|
+
|
|
58
|
+
/** @type {string} */
|
|
59
|
+
let stylesheetURL;
|
|
60
|
+
if (Array.isArray(stylesheetURLInfo)) {
|
|
61
|
+
[stylesheetURL, options = {}] = stylesheetURLInfo;
|
|
62
|
+
} else {
|
|
63
|
+
stylesheetURL = stylesheetURLInfo;
|
|
64
|
+
}
|
|
65
|
+
let {
|
|
66
|
+
favicon = faviconDefault
|
|
67
|
+
} = options;
|
|
68
|
+
const {
|
|
69
|
+
before = beforeDefault,
|
|
70
|
+
after = afterDefault,
|
|
71
|
+
canvas = canvasDefault,
|
|
72
|
+
image = imageDefault
|
|
73
|
+
} = options;
|
|
74
|
+
function addLink() {
|
|
75
|
+
if (before) {
|
|
76
|
+
before.before(link);
|
|
77
|
+
} else if (after) {
|
|
78
|
+
after.after(link);
|
|
79
|
+
} else {
|
|
80
|
+
document.head.append(link);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const link = document.createElement('link');
|
|
84
|
+
|
|
85
|
+
// eslint-disable-next-line promise/avoid-new -- No native option
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
let rej = reject;
|
|
88
|
+
if (acceptErrors) {
|
|
89
|
+
rej = typeof acceptErrors === 'function' ? error => {
|
|
90
|
+
acceptErrors({
|
|
91
|
+
error,
|
|
92
|
+
stylesheetURL,
|
|
93
|
+
options,
|
|
94
|
+
resolve,
|
|
95
|
+
reject
|
|
96
|
+
});
|
|
97
|
+
} : resolve;
|
|
98
|
+
}
|
|
99
|
+
if (stylesheetURL.endsWith('.css')) {
|
|
100
|
+
favicon = false;
|
|
101
|
+
} else if (stylesheetURL.endsWith('.ico')) {
|
|
102
|
+
favicon = true;
|
|
103
|
+
}
|
|
104
|
+
if (favicon) {
|
|
105
|
+
link.rel = 'shortcut icon';
|
|
106
|
+
link.type = 'image/x-icon';
|
|
107
|
+
if (image === false) {
|
|
108
|
+
link.href = stylesheetURL;
|
|
109
|
+
addLink();
|
|
110
|
+
resolve(link);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const cnv = document.createElement('canvas');
|
|
114
|
+
cnv.width = 16;
|
|
115
|
+
cnv.height = 16;
|
|
116
|
+
const context = cnv.getContext('2d');
|
|
117
|
+
const img = document.createElement('img');
|
|
118
|
+
// eslint-disable-next-line promise/prefer-await-to-callbacks -- No API
|
|
119
|
+
img.addEventListener('error', error => {
|
|
120
|
+
reject(error);
|
|
121
|
+
});
|
|
122
|
+
img.addEventListener('load', () => {
|
|
123
|
+
if (!context) {
|
|
124
|
+
throw new Error('Canvas context could not be found');
|
|
125
|
+
}
|
|
126
|
+
context.drawImage(img, 0, 0);
|
|
127
|
+
link.href = canvas ? cnv.toDataURL('image/x-icon') : stylesheetURL;
|
|
128
|
+
addLink();
|
|
129
|
+
resolve(link);
|
|
130
|
+
});
|
|
131
|
+
img.src = stylesheetURL;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
link.rel = 'stylesheet';
|
|
135
|
+
link.type = 'text/css';
|
|
136
|
+
link.href = stylesheetURL;
|
|
137
|
+
addLink();
|
|
138
|
+
// eslint-disable-next-line promise/prefer-await-to-callbacks -- No API
|
|
139
|
+
link.addEventListener('error', error => {
|
|
140
|
+
rej(error);
|
|
141
|
+
});
|
|
142
|
+
link.addEventListener('load', () => {
|
|
143
|
+
resolve(link);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return Promise.all(stylesheets.map(stylesheetURL => setupLink(stylesheetURL)));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* eslint-disable no-unused-vars -- Convenient */
|
|
151
|
+
/**
|
|
152
|
+
* MIT License.
|
|
153
|
+
*
|
|
154
|
+
* @copyright 2014 White Magic Software, Inc.
|
|
155
|
+
* @copyright 2018 Brett Zamir
|
|
156
|
+
*/
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @typedef {{
|
|
161
|
+
* delay: JQuery.Duration | string,
|
|
162
|
+
* outsideClickBehavior: "reset"|"select-parent"|"none",
|
|
163
|
+
* breadcrumbRoot: string,
|
|
164
|
+
* breadcrumb: (this: HTMLElement, $columns?: JQuery<HTMLElement>) => void,
|
|
165
|
+
* current: (li: JQuery<HTMLLIElement>, $columns: JQuery<HTMLElement>) => void,
|
|
166
|
+
* preview: null|((li: JQuery<HTMLLIElement>, $columns: JQuery<HTMLElement>) => void),
|
|
167
|
+
* onPreview: null|((
|
|
168
|
+
* ev: JQuery.ClickEvent<HTMLUListElement, undefined, HTMLUListElement, HTMLUListElement>,
|
|
169
|
+
* li: JQuery<HTMLUListElement>,
|
|
170
|
+
* $columns: JQuery<HTMLElement>
|
|
171
|
+
* ) => void),
|
|
172
|
+
* animation: (li: JQuery<HTMLLIElement>, $columns: JQuery<HTMLElement>) => void,
|
|
173
|
+
* reset: ($columns: JQuery<HTMLElement>, resetByUser: boolean) => void,
|
|
174
|
+
* scroll?: ($column: JQuery<HTMLElement>|null, $columns: JQuery<HTMLElement>) => void
|
|
175
|
+
* }} Settings
|
|
176
|
+
*/
|
|
177
|
+
|
|
178
|
+
const defaultCSSURL = new URL('../miller-columns.css', (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-umd.js', document.baseURI).href))).href;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {string} s
|
|
182
|
+
* @returns {string}
|
|
183
|
+
*/
|
|
184
|
+
function escapeRegex(s) {
|
|
185
|
+
return s.replaceAll(/[\-\[\]\{\}\(\)*+?.,\\^$\|#\s]/gv, String.raw`\$&`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @param {jQuery} $
|
|
190
|
+
* @param {object} cfg
|
|
191
|
+
* @param {string} [cfg.namespace]
|
|
192
|
+
* @param {Exclude<import('load-stylesheets').Stylesheets, string>} [cfg.stylesheets]
|
|
193
|
+
* @returns {Promise<jQuery>}
|
|
194
|
+
*/
|
|
195
|
+
async function addMillerColumnPlugin($, {
|
|
196
|
+
namespace = 'miller',
|
|
197
|
+
stylesheets = ['@default']
|
|
198
|
+
} = {}) {
|
|
199
|
+
/** @type {Settings} */
|
|
200
|
+
let settings;
|
|
201
|
+
const columnSelector = `ul:not(.${namespace}-no-columns),ol:not(.${namespace}-no-columns)`;
|
|
202
|
+
const itemSelector = 'li';
|
|
203
|
+
if (stylesheets) {
|
|
204
|
+
await loadStylesheets(stylesheets.map(s => {
|
|
205
|
+
return s === '@default' ? defaultCSSURL : s;
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Returns a list of the currently selected items.
|
|
211
|
+
* @returns {JQuery<HTMLElement>}
|
|
212
|
+
*/
|
|
213
|
+
function chain() {
|
|
214
|
+
return $(`.${namespace}-column > .${namespace}-selected`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Add the breadcrumb path using the chain of selected items.
|
|
219
|
+
* @param {JQuery<HTMLElement>} [$columns] - Optional columns element for root link
|
|
220
|
+
* @returns {void}
|
|
221
|
+
*/
|
|
222
|
+
function breadcrumb($columns) {
|
|
223
|
+
const $breadcrumb = $(`.${namespace}-breadcrumbs`).empty();
|
|
224
|
+
|
|
225
|
+
// Add root link if breadcrumbRoot option is set
|
|
226
|
+
if (settings.breadcrumbRoot) {
|
|
227
|
+
$(`<span class="${namespace}-breadcrumb ${namespace}-breadcrumb-root">`).text(settings.breadcrumbRoot).on('click', function () {
|
|
228
|
+
if ($columns) {
|
|
229
|
+
userReset($columns);
|
|
230
|
+
}
|
|
231
|
+
}).appendTo($breadcrumb);
|
|
232
|
+
}
|
|
233
|
+
chain().each(function () {
|
|
234
|
+
const $crumb = $(this);
|
|
235
|
+
$(`<span class="${namespace}-breadcrumb">`).text($crumb.text().trim()).on('click', function () {
|
|
236
|
+
$crumb.trigger('click');
|
|
237
|
+
}).appendTo($breadcrumb);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Ensure the viewport shows the entire newly expanded item.
|
|
243
|
+
*
|
|
244
|
+
* @param {JQuery<HTMLElement>|null} $column
|
|
245
|
+
* @param {JQuery<HTMLElement>} $columns
|
|
246
|
+
* @returns {void}
|
|
247
|
+
*/
|
|
248
|
+
function animation($column, $columns) {
|
|
249
|
+
let width = 0;
|
|
250
|
+
($column ? chain().not($column) : chain()).each(function () {
|
|
251
|
+
width += /** @type {number} */$(this).outerWidth(true);
|
|
252
|
+
});
|
|
253
|
+
$columns.stop().animate({
|
|
254
|
+
scrollLeft: width
|
|
255
|
+
}, settings.delay, function () {
|
|
256
|
+
// Why isn't this working when we instead use this `last` on the `animate` above?
|
|
257
|
+
const last = $columns.find(`.${namespace}-column:not(.${namespace}-collapse)`).last();
|
|
258
|
+
// last[0].scrollIntoView(); // Scrolls vertically also unfortunately
|
|
259
|
+
last[0].scrollLeft = width;
|
|
260
|
+
if (settings.scroll) {
|
|
261
|
+
settings.scroll.call(this, $column, $columns);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Convert nested lists into columns using breadth-first traversal.
|
|
268
|
+
*
|
|
269
|
+
* @param {JQuery<HTMLElement>} $columns
|
|
270
|
+
* @param {JQuery<HTMLElement>} [$startNode] - Optional starting node for partial unnesting
|
|
271
|
+
* @returns {void}
|
|
272
|
+
*/
|
|
273
|
+
function unnest($columns, $startNode) {
|
|
274
|
+
const queue = [];
|
|
275
|
+
let $node;
|
|
276
|
+
|
|
277
|
+
// Push the root unordered list item into the queue.
|
|
278
|
+
queue.push($startNode || $columns.children());
|
|
279
|
+
while (queue.length) {
|
|
280
|
+
$node = /** @type {JQuery<HTMLElement>} */queue.shift();
|
|
281
|
+
$node.children(itemSelector).each(function () {
|
|
282
|
+
const $this = $(this);
|
|
283
|
+
const $child = $this.children(columnSelector);
|
|
284
|
+
const $ancestor = $this.parent().parent();
|
|
285
|
+
|
|
286
|
+
// Retain item hierarchy (because it is lost after flattening).
|
|
287
|
+
// Only set ancestor if it's actually a list item and not already set
|
|
288
|
+
// eslint-disable-next-line eqeqeq, no-eq-null -- Check either without duplication
|
|
289
|
+
if ($ancestor.length && $ancestor.is(itemSelector) && $this.data(`${namespace}-ancestor`) == null) {
|
|
290
|
+
// Use addBack to reset all selection chains.
|
|
291
|
+
$(this).siblings().addBack().data(`${namespace}-ancestor`, $ancestor);
|
|
292
|
+
}
|
|
293
|
+
if ($child.length) {
|
|
294
|
+
queue.push($child);
|
|
295
|
+
$(this).data(`${namespace}-child`, $child).addClass(`${namespace}-parent`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Causes item siblings to have a flattened DOM lineage.
|
|
299
|
+
$(this).parent(columnSelector).appendTo($columns).addClass(`${namespace}-column`);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Hide columns (not the first).
|
|
306
|
+
* @returns {void}
|
|
307
|
+
*/
|
|
308
|
+
function collapse() {
|
|
309
|
+
$(`.${namespace}-column:gt(0)`).addClass(`${namespace}-collapse`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Returns the last selected item (i.e., the current cursor).
|
|
314
|
+
* @returns {JQuery<HTMLElement>}
|
|
315
|
+
*/
|
|
316
|
+
function current() {
|
|
317
|
+
return chain().last();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* @param {JQuery<HTMLElement>} $columns
|
|
322
|
+
* @returns {void}
|
|
323
|
+
*/
|
|
324
|
+
function scrollIntoView($columns) {
|
|
325
|
+
animation(null, $columns);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* @param {JQuery<HTMLElement>} $columns
|
|
330
|
+
* @returns {void}
|
|
331
|
+
*/
|
|
332
|
+
function userReset($columns) {
|
|
333
|
+
reset($columns, true);
|
|
334
|
+
scrollIntoView($columns);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Hide columns (not the first), remove selections, update breadcrumb.
|
|
339
|
+
*
|
|
340
|
+
* @param {JQuery<HTMLElement>} $columns
|
|
341
|
+
* @param {boolean} resetByUser
|
|
342
|
+
* @returns {void}
|
|
343
|
+
*/
|
|
344
|
+
function reset($columns, resetByUser) {
|
|
345
|
+
collapse();
|
|
346
|
+
chain().removeClass(`${namespace}-selected`);
|
|
347
|
+
breadcrumb($columns);
|
|
348
|
+
|
|
349
|
+
// Upon reset ensure no value is returned to the calling code.
|
|
350
|
+
settings.reset($columns, resetByUser);
|
|
351
|
+
if (settings.preview) {
|
|
352
|
+
$(`.${namespace}-preview`).remove();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Select item above current selection.
|
|
358
|
+
* @returns {void}
|
|
359
|
+
*/
|
|
360
|
+
function moveU() {
|
|
361
|
+
const elem = current().prev();
|
|
362
|
+
elem[0]?.scrollIntoView({
|
|
363
|
+
block: 'nearest'
|
|
364
|
+
});
|
|
365
|
+
elem.trigger('click');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Select item below current selection.
|
|
370
|
+
* @returns {void}
|
|
371
|
+
*/
|
|
372
|
+
function moveD() {
|
|
373
|
+
const elem = current().next();
|
|
374
|
+
elem[0]?.scrollIntoView({
|
|
375
|
+
block: 'nearest',
|
|
376
|
+
inline: 'start'
|
|
377
|
+
});
|
|
378
|
+
elem.trigger('click');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Select item left of the current selection.
|
|
383
|
+
* @returns {void}
|
|
384
|
+
*/
|
|
385
|
+
function moveL() {
|
|
386
|
+
const $current = current();
|
|
387
|
+
const $ancestor = $current.data(`${namespace}-ancestor`);
|
|
388
|
+
const $child = $current.data(`${namespace}-child`);
|
|
389
|
+
|
|
390
|
+
// If current item has children and they are visible, but we're at root level,
|
|
391
|
+
// do nothing - we're already on the parent and just expanded it
|
|
392
|
+
if ($child && !$child.hasClass(`${namespace}-collapse`) && !$ancestor) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Move to ancestor if it exists
|
|
397
|
+
if ($ancestor) {
|
|
398
|
+
$ancestor[0]?.scrollIntoView({
|
|
399
|
+
block: 'nearest'
|
|
400
|
+
});
|
|
401
|
+
$ancestor.trigger('click');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Select item right of the current selection, or down if no right item.
|
|
407
|
+
* @returns {void}
|
|
408
|
+
*/
|
|
409
|
+
function moveR() {
|
|
410
|
+
const $child = current().data(`${namespace}-child`);
|
|
411
|
+
if ($child) {
|
|
412
|
+
const elem = $child.children(itemSelector).first();
|
|
413
|
+
elem[0]?.scrollIntoView({
|
|
414
|
+
block: 'nearest'
|
|
415
|
+
});
|
|
416
|
+
elem.trigger('click');
|
|
417
|
+
} else {
|
|
418
|
+
moveD();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* @callback MillerColumnsKeyPress
|
|
424
|
+
* @param {KeyboardEvent} e
|
|
425
|
+
* @returns {void}
|
|
426
|
+
*/
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* @param {JQuery<HTMLElement>} $columns
|
|
430
|
+
* @returns {MillerColumnsKeyPress}
|
|
431
|
+
*/
|
|
432
|
+
function getKeyPress($columns) {
|
|
433
|
+
let buffer = '';
|
|
434
|
+
/** @type {number} */
|
|
435
|
+
let lastTime;
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* @param {string} key
|
|
439
|
+
* @returns {void}
|
|
440
|
+
*/
|
|
441
|
+
function checkLastPressed(key) {
|
|
442
|
+
const currTime = Date.now();
|
|
443
|
+
if (!lastTime || currTime - lastTime < 500) {
|
|
444
|
+
buffer += key;
|
|
445
|
+
} else {
|
|
446
|
+
buffer = key;
|
|
447
|
+
}
|
|
448
|
+
lastTime = currTime;
|
|
449
|
+
}
|
|
450
|
+
return function keypress(ev) {
|
|
451
|
+
// eslint-disable-next-line prefer-destructuring -- TS
|
|
452
|
+
const key = /** @type {Event & {key: string}} */ev.key;
|
|
453
|
+
// Was an attempt made to move the currently selected item (the cursor)?
|
|
454
|
+
let moved = false;
|
|
455
|
+
switch (key) {
|
|
456
|
+
case 'Escape':
|
|
457
|
+
userReset($columns);
|
|
458
|
+
break;
|
|
459
|
+
case 'ArrowUp':
|
|
460
|
+
moveU();
|
|
461
|
+
moved = true;
|
|
462
|
+
break;
|
|
463
|
+
case 'ArrowDown':
|
|
464
|
+
moveD();
|
|
465
|
+
moved = true;
|
|
466
|
+
break;
|
|
467
|
+
case 'ArrowLeft':
|
|
468
|
+
moveL();
|
|
469
|
+
moved = true;
|
|
470
|
+
break;
|
|
471
|
+
case 'ArrowRight':
|
|
472
|
+
moveR();
|
|
473
|
+
moved = true;
|
|
474
|
+
break;
|
|
475
|
+
default:
|
|
476
|
+
if (!ev.metaKey && !ev.altKey) {
|
|
477
|
+
if (key.length === 1) {
|
|
478
|
+
checkLastPressed(key);
|
|
479
|
+
const matching = $columns.find(`${itemSelector}.${namespace}-selected`).last().siblings().filter(function () {
|
|
480
|
+
return new RegExp('^' + escapeRegex(buffer), 'iv').test($(this).text().trim());
|
|
481
|
+
});
|
|
482
|
+
const elem = matching.first();
|
|
483
|
+
elem[0]?.scrollIntoView({
|
|
484
|
+
block: 'nearest'
|
|
485
|
+
});
|
|
486
|
+
elem.trigger('click');
|
|
487
|
+
}
|
|
488
|
+
moved = true;
|
|
489
|
+
}
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// If no item is selected, then jump to the first item.
|
|
494
|
+
if (moved && current().length === 0) {
|
|
495
|
+
$(`.${namespace}-column`).first().children().first().trigger('click');
|
|
496
|
+
}
|
|
497
|
+
if (moved) {
|
|
498
|
+
ev.preventDefault();
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* @param {Partial<Settings>} options
|
|
505
|
+
*/
|
|
506
|
+
$.fn.millerColumns = function (options) {
|
|
507
|
+
/** @type {Settings} */
|
|
508
|
+
const defaults = {
|
|
509
|
+
current($item) {/* noop */},
|
|
510
|
+
reset($columns) {/* noop */},
|
|
511
|
+
preview: null,
|
|
512
|
+
onPreview: null,
|
|
513
|
+
breadcrumbRoot: 'Root',
|
|
514
|
+
breadcrumb,
|
|
515
|
+
animation,
|
|
516
|
+
delay: 500,
|
|
517
|
+
outsideClickBehavior: 'select-parent'
|
|
518
|
+
};
|
|
519
|
+
settings = $.extend(defaults, options);
|
|
520
|
+
const $result = this.each(function () {
|
|
521
|
+
const $columns = $(this);
|
|
522
|
+
|
|
523
|
+
// Store original HTML for restoration
|
|
524
|
+
const originalHTML = $columns.html();
|
|
525
|
+
$columns.data(`${namespace}-original-html`, originalHTML);
|
|
526
|
+
unnest($columns);
|
|
527
|
+
collapse();
|
|
528
|
+
breadcrumb($columns); // Initialize breadcrumbs with Root link
|
|
529
|
+
|
|
530
|
+
// Store keypress handler for later removal
|
|
531
|
+
const keypressHandler = getKeyPress($columns);
|
|
532
|
+
|
|
533
|
+
// Expand the requested child node on click.
|
|
534
|
+
// Use event delegation to handle dynamically added items
|
|
535
|
+
$columns.on('click', itemSelector, function (ev) {
|
|
536
|
+
const $this = $(this);
|
|
537
|
+
reset($columns, false);
|
|
538
|
+
const $child = $this.data(`${namespace}-child`);
|
|
539
|
+
let $ancestor = $this;
|
|
540
|
+
if ($child) {
|
|
541
|
+
$child[0]?.scrollIntoView({
|
|
542
|
+
block: 'nearest'
|
|
543
|
+
});
|
|
544
|
+
$child.removeClass(`${namespace}-collapse`).children().removeClass(`${namespace}-selected`);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Reveal all ancestors
|
|
548
|
+
while ($ancestor) {
|
|
549
|
+
$ancestor.addClass(`${namespace}-selected`).parent().removeClass(`${namespace}-collapse`);
|
|
550
|
+
$ancestor = $ancestor.data(`${namespace}-ancestor`);
|
|
551
|
+
}
|
|
552
|
+
settings.animation.call(this, $this, $columns);
|
|
553
|
+
settings.breadcrumb.call(this, $columns);
|
|
554
|
+
settings.current.call(this, $this, $columns);
|
|
555
|
+
if (settings.preview) {
|
|
556
|
+
const isFinalCol = $this.hasClass(`${namespace}-selected`) && !$this.hasClass(`${namespace}-parent`);
|
|
557
|
+
if (isFinalCol) {
|
|
558
|
+
const content = settings.preview.call(this, $this, $columns);
|
|
559
|
+
const ul = /** @type {JQuery<HTMLUListElement>} */
|
|
560
|
+
$(`<ul class="${namespace}-column ${namespace}-preview">
|
|
561
|
+
<li>${content}</li>
|
|
562
|
+
</ul>`);
|
|
563
|
+
$this.parent().parent().append(ul);
|
|
564
|
+
ul[0].scrollIntoView({
|
|
565
|
+
block: 'nearest',
|
|
566
|
+
inline: 'start'
|
|
567
|
+
});
|
|
568
|
+
ul.on('click', e => {
|
|
569
|
+
e.stopPropagation();
|
|
570
|
+
settings.onPreview?.call(this, e, ul, $columns);
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Don't allow the underlying element
|
|
576
|
+
// to receive the click event.
|
|
577
|
+
ev.stopPropagation();
|
|
578
|
+
});
|
|
579
|
+
$columns[0].addEventListener('keydown', keypressHandler);
|
|
580
|
+
$columns.on('click', e => {
|
|
581
|
+
switch (settings.outsideClickBehavior) {
|
|
582
|
+
case 'reset':
|
|
583
|
+
userReset($columns);
|
|
584
|
+
break;
|
|
585
|
+
case 'select-parent':
|
|
586
|
+
{
|
|
587
|
+
const caretPosition = document.caretPositionFromPoint(e.clientX, e.clientY);
|
|
588
|
+
const node = caretPosition?.offsetNode;
|
|
589
|
+
let elem = /** @type {Element|null} */node?.nodeType === 1 ? node : node?.parentElement;
|
|
590
|
+
while (elem) {
|
|
591
|
+
if (elem.matches(`ul.${namespace}-column:not(.${namespace}-collapse)`)) {
|
|
592
|
+
$(elem).prevAll(`ul.${namespace}-column:not(.${namespace}-collapse)`).first().find(`li.${namespace}-selected`).trigger('click');
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
elem = elem.parentElement;
|
|
596
|
+
}
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
// Store handler reference for cleanup
|
|
603
|
+
$columns.data(`${namespace}-keypress-handler`, keypressHandler);
|
|
604
|
+
|
|
605
|
+
// The last set of columns on the page receives focus.
|
|
606
|
+
// $columns.focus();
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Add a new item dynamically to the miller columns structure.
|
|
611
|
+
* The item can contain nested lists which will be automatically unnested.
|
|
612
|
+
*
|
|
613
|
+
* @param {string|JQuery<HTMLLIElement>} item - HTML string or jQuery element for the new list item
|
|
614
|
+
* @param {JQuery<HTMLLIElement>} [$parent] - Optional parent item to add this as a child.
|
|
615
|
+
* If not provided, adds to root level.
|
|
616
|
+
* @returns {JQuery<HTMLLIElement>} The newly added item
|
|
617
|
+
*/
|
|
618
|
+
$result.addItem = function (item, $parent) {
|
|
619
|
+
const $item = /** @type {JQuery<HTMLLIElement>} */typeof item === 'string' ? $(item) : item;
|
|
620
|
+
const $columns = $result;
|
|
621
|
+
if (!$parent) {
|
|
622
|
+
// Add to root level (first column)
|
|
623
|
+
const $rootColumn = $columns.find(`.${namespace}-column`).first();
|
|
624
|
+
if ($rootColumn.length) {
|
|
625
|
+
// Append to existing root column
|
|
626
|
+
$rootColumn.append($item);
|
|
627
|
+
|
|
628
|
+
// If the item has nested children, process them
|
|
629
|
+
const $child = $item.children(columnSelector);
|
|
630
|
+
if ($child.length) {
|
|
631
|
+
// Set up the parent-child relationship
|
|
632
|
+
$item.data(`${namespace}-child`, $child).addClass(`${namespace}-parent`);
|
|
633
|
+
// Process the child list to unnest it
|
|
634
|
+
unnest($columns, $child);
|
|
635
|
+
}
|
|
636
|
+
} else {
|
|
637
|
+
// No columns exist yet, create initial structure
|
|
638
|
+
const $tempWrapper = $('<ul>').append($item);
|
|
639
|
+
$columns.append($tempWrapper);
|
|
640
|
+
unnest($columns, $tempWrapper);
|
|
641
|
+
}
|
|
642
|
+
} else {
|
|
643
|
+
// Add as child of existing parent
|
|
644
|
+
let $childList = $parent.data(`${namespace}-child`);
|
|
645
|
+
if (!$childList) {
|
|
646
|
+
// Parent doesn't have children yet, create a new list with the item
|
|
647
|
+
$childList = $('<ul>').append($item);
|
|
648
|
+
$parent.append($childList);
|
|
649
|
+
$parent.data(`${namespace}-child`, $childList).addClass(`${namespace}-parent`);
|
|
650
|
+
|
|
651
|
+
// Set the ancestor relationship for the new item
|
|
652
|
+
$item.data(`${namespace}-ancestor`, $parent);
|
|
653
|
+
|
|
654
|
+
// The new list needs to be processed by unnest to become a column
|
|
655
|
+
unnest($columns, $childList);
|
|
656
|
+
|
|
657
|
+
// After unnesting, get the updated reference to the child list
|
|
658
|
+
$childList = $parent.data(`${namespace}-child`);
|
|
659
|
+
} else {
|
|
660
|
+
// Parent already has children - $childList is already a column
|
|
661
|
+
// Just append the new item directly to it
|
|
662
|
+
$childList.append($item);
|
|
663
|
+
|
|
664
|
+
// Set the ancestor relationship for the new item
|
|
665
|
+
$item.data(`${namespace}-ancestor`, $parent);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// If the new item has nested children, process them
|
|
669
|
+
const $child = $item.children(columnSelector);
|
|
670
|
+
if ($child.length) {
|
|
671
|
+
// Set up the parent-child relationship
|
|
672
|
+
$item.data(`${namespace}-child`, $child).addClass(`${namespace}-parent`);
|
|
673
|
+
// Process the child list to unnest it
|
|
674
|
+
unnest($columns, $child);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return $item;
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Rebuild children for a parent item after external changes.
|
|
682
|
+
* @param {JQuery<HTMLLIElement>} $parent
|
|
683
|
+
* @param {(string|JQuery<HTMLLIElement>)[]} newItems
|
|
684
|
+
* @returns {JQuery<HTMLLIElement>}
|
|
685
|
+
*/
|
|
686
|
+
$result.refreshChildren = function ($parent, newItems) {
|
|
687
|
+
if (!$parent || !$parent.length) {
|
|
688
|
+
return $parent;
|
|
689
|
+
}
|
|
690
|
+
const $existing = $parent.data(`${namespace}-child`);
|
|
691
|
+
if ($existing && $existing.length) {
|
|
692
|
+
$existing.remove();
|
|
693
|
+
$parent.removeData(`${namespace}-child`).removeClass(`${namespace}-parent`);
|
|
694
|
+
}
|
|
695
|
+
const $liItems = newItems.map(it => typeof it === 'string' ? $(it) : it);
|
|
696
|
+
const $newList = $('<ul>').append($liItems);
|
|
697
|
+
$parent.append($newList);
|
|
698
|
+
unnest($result, $newList);
|
|
699
|
+
$parent.trigger('click');
|
|
700
|
+
return $parent;
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Destroy and restore original structure.
|
|
705
|
+
* @returns {JQuery<HTMLElement>}
|
|
706
|
+
*/
|
|
707
|
+
$result.destroy = function () {
|
|
708
|
+
const $columns = $result;
|
|
709
|
+
$columns.each(function () {
|
|
710
|
+
const $col = $(this);
|
|
711
|
+
// Remove keydown event listener
|
|
712
|
+
const keypressHandler = $col.data(`${namespace}-keypress-handler`);
|
|
713
|
+
if (keypressHandler) {
|
|
714
|
+
this.removeEventListener('keydown', keypressHandler);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Remove click event handlers
|
|
718
|
+
$col.off('click');
|
|
719
|
+
|
|
720
|
+
// Remove all miller-columns CSS classes from columns
|
|
721
|
+
$col.find(`.${namespace}-column`).removeClass(`${namespace}-column ${namespace}-collapse`);
|
|
722
|
+
|
|
723
|
+
// Remove all miller-parent classes and miller-selected classes
|
|
724
|
+
$col.find(`.${namespace}-parent`).removeClass(`${namespace}-parent`);
|
|
725
|
+
$col.find(`.${namespace}-selected`).removeClass(`${namespace}-selected`);
|
|
726
|
+
|
|
727
|
+
// Remove all data attributes
|
|
728
|
+
$col.find('li').each(function () {
|
|
729
|
+
const $item = $(this);
|
|
730
|
+
$item.removeData(`${namespace}-ancestor`);
|
|
731
|
+
$item.removeData(`${namespace}-child`);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
// Remove preview columns
|
|
735
|
+
$col.find(`.${namespace}-preview`).remove();
|
|
736
|
+
|
|
737
|
+
// Restore original HTML structure
|
|
738
|
+
const originalHTML = $col.data(`${namespace}-original-html`);
|
|
739
|
+
if (originalHTML) {
|
|
740
|
+
$col.html(originalHTML);
|
|
741
|
+
$col.removeData(`${namespace}-original-html`);
|
|
742
|
+
}
|
|
743
|
+
$col.removeData(`${namespace}-keypress-handler`);
|
|
744
|
+
});
|
|
745
|
+
delete $result.addItem;
|
|
746
|
+
delete $result.destroy;
|
|
747
|
+
delete $result.refreshChildren;
|
|
748
|
+
return $result;
|
|
749
|
+
};
|
|
750
|
+
return $result;
|
|
751
|
+
};
|
|
752
|
+
return $;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return addMillerColumnPlugin;
|
|
756
|
+
|
|
757
|
+
}));
|