unicode-input-toolconverter 0.2.0 → 0.2.2

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