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.
Files changed (143) hide show
  1. package/.nyc_output/out.json +8980 -1849
  2. package/CHANGES.md +15 -1
  3. package/README.md +6 -11
  4. package/_locales/en-US/messages.json +153 -6
  5. package/_locales/hu-HU/messages.json +153 -6
  6. package/_locales/pt-BR/messages.json +153 -6
  7. package/_locales/sv-SE/messages.json +153 -6
  8. package/babel.config.json +0 -1
  9. package/browser_action/characterSelection.js +18 -11
  10. package/browser_action/charrefConverters.js +286 -79
  11. package/browser_action/chartBuild.js +97 -27
  12. package/browser_action/encodingBehaviors.js +10 -7
  13. package/browser_action/entityBehaviors.js +42 -16
  14. package/browser_action/index-es.html +13 -1
  15. package/browser_action/index-instrumented.html +13 -1
  16. package/browser_action/index-pages.html +13 -1
  17. package/browser_action/index.html +13 -1
  18. package/browser_action/index.iife.min.js +9 -1
  19. package/browser_action/index.iife.min.js.map +1 -1
  20. package/browser_action/index.instrumented.iife.min.js +1 -1
  21. package/browser_action/index.instrumented.iife.min.js.map +1 -1
  22. package/browser_action/index.js +5 -5
  23. package/browser_action/preferences/prefDefaults.js +12 -3
  24. package/browser_action/service-worker/sw-activateCallback.js +1 -2
  25. package/browser_action/service-worker/sw-resources.json +6 -4
  26. package/browser_action/templateUtils/elements.js +12 -1
  27. package/browser_action/templateUtils/fill.js +6 -0
  28. package/browser_action/templateUtils/validation.js +4 -1
  29. package/browser_action/templates/chartBuild.js +167 -102
  30. package/browser_action/templates/index.js +227 -53
  31. package/browser_action/templatesElementCustomization/widgets.js +40 -15
  32. package/browser_action/unicode/UnicodeConverter.js +99 -73
  33. package/browser_action/unicode/charrefunicodeDb.js +100 -50
  34. package/browser_action/unicode/getScriptInfoForCodePoint.js +13 -4
  35. package/browser_action/unicode/hangul.js +27 -20
  36. package/browser_action/unicode/lastScriptNames.json +1 -1
  37. package/browser_action/unicode/parseUnihanFromTextFileStrings.js +44 -25
  38. package/browser_action/unicode/unicodeFieldInfo.js +112 -90
  39. package/browser_action/unicode/unicodeScripts.js +409 -35
  40. package/browser_action/unicode/unihan.js +4 -3
  41. package/browser_action/unicode/unihanDbPopulate.js +7 -2
  42. package/browser_action/unicode/unihanFields.js +34 -0
  43. package/browser_action/unicodecharref.js +353 -190
  44. package/browser_action/utils/DOMUtils.js +95 -34
  45. package/browser_action/utils/FetchUtils.js +7 -6
  46. package/browser_action/utils/TextUtils.js +4 -4
  47. package/browser_action/utils/TypedArrayUtils.js +9 -5
  48. package/browser_action/utils/semicolonSeparatedToArray.js +3 -3
  49. package/browser_action/utils/setupServiceWorker.js +2 -2
  50. package/eslint.config.js +11 -0
  51. package/icons/openWindow16.png +0 -0
  52. package/icons/openWindow24.png +0 -0
  53. package/lib/background.html +7 -0
  54. package/lib/background.js +2 -2
  55. package/package.json +80 -70
  56. package/pnpm-workspace.yaml +8 -0
  57. package/server.js +66 -51
  58. package/sw.js +97 -56
  59. package/tools/entities-import.js +4 -1
  60. package/tools/findEsResources.js +33 -10
  61. package/tools/fix-sw-resources-vendor-paths.js +60 -0
  62. package/tools/list-locales.js +2 -2
  63. package/tools/parseUnicodeCharts.js +126 -60
  64. package/tools/ucd-import.js +1 -10
  65. package/tools/unicode-charts.html +49 -20
  66. package/tools/unihan-import.js +45 -15
  67. package/tools/write-sw-version.js +16 -0
  68. package/tsconfig.json +23 -0
  69. package/typings/__coverage__.d.ts +5 -0
  70. package/typings/apple-system-profiler.d.ts +17 -0
  71. package/typings/commands.d.ts +19 -0
  72. package/typings/json-6.d.ts +4 -0
  73. package/typings/rollup-plugin-istanbul.d.ts +3 -0
  74. package/vendor/camelcase/index.d.ts +154 -0
  75. package/vendor/camelcase/index.js +137 -23
  76. package/vendor/fflate/esm/browser.d.ts +1539 -0
  77. package/vendor/fflate/esm/browser.js +82 -55
  78. package/vendor/intl-dom/dist/Formatter.d.ts +120 -0
  79. package/vendor/intl-dom/dist/Formatter.d.ts.map +1 -0
  80. package/vendor/intl-dom/dist/collation.d.ts +42 -0
  81. package/vendor/intl-dom/dist/collation.d.ts.map +1 -0
  82. package/vendor/intl-dom/dist/defaultAllSubstitutions.d.ts +52 -0
  83. package/vendor/intl-dom/dist/defaultAllSubstitutions.d.ts.map +1 -0
  84. package/vendor/intl-dom/dist/defaultInsertNodes.d.ts +96 -0
  85. package/vendor/intl-dom/dist/defaultInsertNodes.d.ts.map +1 -0
  86. package/vendor/intl-dom/dist/defaultKeyCheckerConverter.d.ts +17 -0
  87. package/vendor/intl-dom/dist/defaultKeyCheckerConverter.d.ts.map +1 -0
  88. package/vendor/intl-dom/dist/defaultLocaleResolver.d.ts +285 -0
  89. package/vendor/intl-dom/dist/defaultLocaleResolver.d.ts.map +1 -0
  90. package/vendor/intl-dom/dist/findLocaleStrings.d.ts +88 -0
  91. package/vendor/intl-dom/dist/findLocaleStrings.d.ts.map +1 -0
  92. package/vendor/intl-dom/dist/getDOMForLocaleString.d.ts +67 -0
  93. package/vendor/intl-dom/dist/getDOMForLocaleString.d.ts.map +1 -0
  94. package/vendor/intl-dom/dist/getMessageForKeyByStyle.d.ts +73 -0
  95. package/vendor/intl-dom/dist/getMessageForKeyByStyle.d.ts.map +1 -0
  96. package/vendor/intl-dom/dist/getStringFromMessageAndDefaults.d.ts +26 -0
  97. package/vendor/intl-dom/dist/getStringFromMessageAndDefaults.d.ts.map +1 -0
  98. package/vendor/intl-dom/dist/i18n.d.ts +113 -0
  99. package/vendor/intl-dom/dist/i18n.d.ts.map +1 -0
  100. package/vendor/intl-dom/dist/index.d.ts +92 -0
  101. package/vendor/intl-dom/dist/index.d.ts.map +1 -0
  102. package/vendor/intl-dom/dist/index.esm.d.ts +34 -0
  103. package/vendor/intl-dom/dist/index.esm.js +854 -896
  104. package/vendor/intl-dom/dist/index.esm.min.js +4 -0
  105. package/vendor/intl-dom/dist/index.esm.min.js.map +1 -0
  106. package/vendor/intl-dom/dist/index.umd.js +2953 -0
  107. package/vendor/intl-dom/dist/index.umd.min.js +4 -0
  108. package/vendor/intl-dom/dist/index.umd.min.js.map +1 -0
  109. package/vendor/intl-dom/dist/promiseChainForValues.d.ts +34 -0
  110. package/vendor/intl-dom/dist/promiseChainForValues.d.ts.map +1 -0
  111. package/vendor/intl-dom/dist/shared.d.ts +20 -0
  112. package/vendor/intl-dom/dist/shared.d.ts.map +1 -0
  113. package/vendor/intl-dom/dist/utils.d.ts +53 -0
  114. package/vendor/intl-dom/dist/utils.d.ts.map +1 -0
  115. package/vendor/jamilih/dist/jml.d.mts +468 -0
  116. package/vendor/jamilih/dist/{jml-es.js → jml.mjs} +1489 -343
  117. package/vendor/jquery/dist/jquery.js +1677 -2713
  118. package/vendor/json-6/dist/index.mjs +2 -0
  119. package/vendor/miller-columns/CHANGES.md +185 -0
  120. package/vendor/miller-columns/LICENSE-MIT.txt +21 -0
  121. package/vendor/miller-columns/README.md +249 -0
  122. package/vendor/miller-columns/demos/index.html +69 -0
  123. package/vendor/miller-columns/demos/index.js +72 -0
  124. package/vendor/miller-columns/dist/index-es.js +748 -0
  125. package/vendor/miller-columns/dist/index-es.min.d.ts +25 -0
  126. package/vendor/miller-columns/dist/index-es.min.js +7 -1
  127. package/vendor/miller-columns/dist/index-umd.js +757 -0
  128. package/vendor/miller-columns/dist/index-umd.min.js +7 -0
  129. package/vendor/miller-columns/dist/index.d.ts +25 -0
  130. package/vendor/miller-columns/dist/millerColumns.d.ts +10 -0
  131. package/vendor/miller-columns/eslint.config.js +46 -0
  132. package/vendor/miller-columns/fix-declarations.js +18 -0
  133. package/vendor/miller-columns/miller-columns.css +24 -1
  134. package/vendor/miller-columns/package.json +73 -0
  135. package/vendor/miller-columns/pnpm-workspace.yaml +2 -0
  136. package/vendor/miller-columns/src/index.js +642 -0
  137. package/vendor/miller-columns/src/millerColumns.ts +12 -0
  138. package/vendor/miller-columns/tsconfig-prod.json +23 -0
  139. package/vendor/miller-columns/tsconfig.json +21 -0
  140. package/vendor/simple-prefs/dist/index.esm.d.ts +1 -0
  141. package/vendor/simple-prefs/dist/index.esm.js +164 -216
  142. package/vendor/simple-prefs/dist/simple-prefs.d.ts +117 -0
  143. 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
+ }));