@hakumi-dev/hakumi-components 0.1.18-pre → 0.1.19-pre
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/README.md +208 -381
- package/app/javascript/hakumi_components/controllers/hakumi/admin_panel_controller.js +5 -7
- package/app/javascript/hakumi_components/controllers/hakumi/back_top_controller.js +1 -1
- package/app/javascript/hakumi_components/controllers/hakumi/button_controller.js +108 -2
- package/app/javascript/hakumi_components/controllers/hakumi/calendar_controller.js +183 -95
- package/app/javascript/hakumi_components/controllers/hakumi/color_picker_controller.js +23 -285
- package/app/javascript/hakumi_components/controllers/hakumi/date_picker_controller.js +274 -262
- package/app/javascript/hakumi_components/controllers/hakumi/float_button_group_controller.js +2 -2
- package/app/javascript/hakumi_components/controllers/hakumi/message_controller.js +4 -2
- package/app/javascript/hakumi_components/controllers/hakumi/modal_controller.js +119 -125
- package/app/javascript/hakumi_components/controllers/hakumi/table/editable.js +291 -0
- package/app/javascript/hakumi_components/controllers/hakumi/table_controller.js +166 -366
- package/app/javascript/hakumi_components/controllers/hakumi/tabs_controller.js +8 -2
- package/app/javascript/hakumi_components/controllers/hakumi/tag_controller.js +27 -25
- package/app/javascript/hakumi_components/controllers/hakumi/tag_group_controller.js +19 -18
- package/app/javascript/hakumi_components/controllers/hakumi/theme_controller.js +116 -117
- package/app/javascript/hakumi_components/controllers/hakumi/transfer_controller.js +17 -1
- package/app/javascript/hakumi_components/controllers/hakumi/tree_controller.js +363 -78
- package/app/javascript/hakumi_components/controllers/hakumi/typography_controller.js +3 -3
- package/app/javascript/hakumi_components/controllers/hakumi/upload_controller.js +320 -204
- package/app/javascript/hakumi_components/core/render_component.js +37 -11
- package/app/javascript/hakumi_components/utils/color_helper.js +262 -0
- package/app/javascript/stylesheets/_base.scss +9 -0
- package/app/javascript/stylesheets/_hakumi_components.scss +1 -0
- package/app/javascript/stylesheets/components/_breadcrumb.scss +2 -2
- package/app/javascript/stylesheets/components/_calendar.scss +13 -13
- package/app/javascript/stylesheets/components/_cascader.scss +5 -5
- package/app/javascript/stylesheets/components/_checkbox.scss +9 -11
- package/app/javascript/stylesheets/components/_color_picker.scss +11 -11
- package/app/javascript/stylesheets/components/_date_picker.scss +4 -4
- package/app/javascript/stylesheets/components/_descriptions.scss +2 -2
- package/app/javascript/stylesheets/components/_drawer.scss +3 -3
- package/app/javascript/stylesheets/components/_dropdown.scss +2 -2
- package/app/javascript/stylesheets/components/_flex.scss +1 -1
- package/app/javascript/stylesheets/components/_float_button.scss +5 -5
- package/app/javascript/stylesheets/components/_form_item.scss +92 -0
- package/app/javascript/stylesheets/components/_image.scss +15 -15
- package/app/javascript/stylesheets/components/_input.scss +23 -113
- package/app/javascript/stylesheets/components/_layout.scss +27 -26
- package/app/javascript/stylesheets/components/_menu.scss +15 -15
- package/app/javascript/stylesheets/components/_modal.scss +13 -13
- package/app/javascript/stylesheets/components/_notification.scss +3 -3
- package/app/javascript/stylesheets/components/_popover.scss +1 -1
- package/app/javascript/stylesheets/components/_segmented.scss +3 -3
- package/app/javascript/stylesheets/components/_select.scss +6 -6
- package/app/javascript/stylesheets/components/_slider.scss +1 -1
- package/app/javascript/stylesheets/components/_spin.scss +2 -2
- package/app/javascript/stylesheets/components/_steps.scss +10 -10
- package/app/javascript/stylesheets/components/_switch.scss +11 -10
- package/app/javascript/stylesheets/components/_table.scss +6 -6
- package/app/javascript/stylesheets/components/_tag.scss +2 -2
- package/app/javascript/stylesheets/components/_tooltip.scss +4 -4
- package/app/javascript/stylesheets/components/_tree_select.scss +3 -3
- package/app/javascript/stylesheets/components/_typography.scss +3 -3
- package/app/javascript/stylesheets/components/_upload.scss +4 -4
- package/package.json +2 -2
|
@@ -30,6 +30,8 @@ export default class extends RegistryController {
|
|
|
30
30
|
this.sortables = new Map()
|
|
31
31
|
this.searching = false
|
|
32
32
|
this.previousExpandedKeys = null
|
|
33
|
+
this.typeaheadBuffer = ""
|
|
34
|
+
this.typeaheadTimeout = null
|
|
33
35
|
|
|
34
36
|
this.expandedKeys = new Set(this.hasExpandedKeysValue ? this.expandedKeysValue : this.defaultExpandKeysValue)
|
|
35
37
|
this.selectedKeys = new Set(this.hasSelectedKeysValue ? this.selectedKeysValue : this.defaultSelectedKeysValue)
|
|
@@ -40,6 +42,12 @@ export default class extends RegistryController {
|
|
|
40
42
|
this.applyExpandedStates()
|
|
41
43
|
this.applySelectedStates()
|
|
42
44
|
this.applyCheckedStates(true)
|
|
45
|
+
this.syncRovingTabindex()
|
|
46
|
+
|
|
47
|
+
this.boundHandleKeydown = this.handleKeydown.bind(this)
|
|
48
|
+
this.boundHandleFocusin = this.handleFocusin.bind(this)
|
|
49
|
+
this.element.addEventListener("keydown", this.boundHandleKeydown)
|
|
50
|
+
this.element.addEventListener("focusin", this.boundHandleFocusin)
|
|
43
51
|
|
|
44
52
|
if (this.draggableValue) {
|
|
45
53
|
this.setupSortables()
|
|
@@ -47,7 +55,16 @@ export default class extends RegistryController {
|
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
teardown() {
|
|
58
|
+
if (this.boundHandleKeydown) {
|
|
59
|
+
this.element.removeEventListener("keydown", this.boundHandleKeydown)
|
|
60
|
+
this.boundHandleKeydown = null
|
|
61
|
+
}
|
|
62
|
+
if (this.boundHandleFocusin) {
|
|
63
|
+
this.element.removeEventListener("focusin", this.boundHandleFocusin)
|
|
64
|
+
this.boundHandleFocusin = null
|
|
65
|
+
}
|
|
50
66
|
this.destroySortables()
|
|
67
|
+
this.clearTypeahead()
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
disconnect() {
|
|
@@ -199,6 +216,7 @@ export default class extends RegistryController {
|
|
|
199
216
|
}
|
|
200
217
|
|
|
201
218
|
this.applyExpandedStates()
|
|
219
|
+
this.syncRovingTabindex()
|
|
202
220
|
this.dispatch("expand", { detail: { expandedKeys: Array.from(this.expandedKeys), key } })
|
|
203
221
|
}
|
|
204
222
|
|
|
@@ -296,6 +314,7 @@ export default class extends RegistryController {
|
|
|
296
314
|
setExpandedKeys(keys) {
|
|
297
315
|
this.expandedKeys = new Set(keys || [])
|
|
298
316
|
this.applyExpandedStates()
|
|
317
|
+
this.syncRovingTabindex()
|
|
299
318
|
}
|
|
300
319
|
|
|
301
320
|
expandAll() {
|
|
@@ -305,11 +324,13 @@ export default class extends RegistryController {
|
|
|
305
324
|
}
|
|
306
325
|
})
|
|
307
326
|
this.applyExpandedStates()
|
|
327
|
+
this.syncRovingTabindex()
|
|
308
328
|
}
|
|
309
329
|
|
|
310
330
|
collapseAll() {
|
|
311
331
|
this.expandedKeys.clear()
|
|
312
332
|
this.applyExpandedStates()
|
|
333
|
+
this.syncRovingTabindex()
|
|
313
334
|
}
|
|
314
335
|
|
|
315
336
|
getDescendants(key) {
|
|
@@ -331,61 +352,71 @@ export default class extends RegistryController {
|
|
|
331
352
|
|
|
332
353
|
maybeLoadAsync(key) {
|
|
333
354
|
const node = this.nodeMap.get(key)
|
|
334
|
-
if (!node) return
|
|
335
|
-
|
|
336
|
-
const isAsync = node.element.dataset.async === "true"
|
|
337
|
-
const hasChildren = node.element.querySelector(".hakumi-tree-children")
|
|
338
|
-
|
|
339
|
-
if (!isAsync || hasChildren) return
|
|
355
|
+
if (!this.shouldLoadAsyncNode(node)) return
|
|
340
356
|
|
|
341
357
|
this.setLoading(key, true)
|
|
342
358
|
this.dispatch("load", { detail: { key, node: node.element } })
|
|
343
359
|
}
|
|
344
360
|
|
|
361
|
+
shouldLoadAsyncNode(node) {
|
|
362
|
+
if (!node) return false
|
|
363
|
+
|
|
364
|
+
return node.element.dataset.async === "true" && !node.element.querySelector(".hakumi-tree-children")
|
|
365
|
+
}
|
|
366
|
+
|
|
345
367
|
setLoading(key, loading) {
|
|
346
368
|
const node = this.nodeMap.get(key)
|
|
347
369
|
if (!node) return
|
|
348
370
|
|
|
349
|
-
node.element.dataset.loading = loading ? "true" : "false"
|
|
350
|
-
node.element.classList.toggle("hakumi-tree-node-loading", loading)
|
|
351
|
-
|
|
352
|
-
|
|
353
371
|
if (loading) {
|
|
354
372
|
this.setError(key, false)
|
|
355
373
|
}
|
|
356
374
|
|
|
375
|
+
this.applyLoadingState(node.element, loading)
|
|
376
|
+
this.syncLoadingSwitcher(node.element, loading)
|
|
377
|
+
}
|
|
357
378
|
|
|
358
|
-
|
|
379
|
+
applyLoadingState(element, loading) {
|
|
380
|
+
element.dataset.loading = loading ? "true" : "false"
|
|
381
|
+
element.classList.toggle("hakumi-tree-node-loading", loading)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
syncLoadingSwitcher(element, loading) {
|
|
385
|
+
const switcher = element.querySelector(".hakumi-tree-switcher")
|
|
359
386
|
if (!switcher) return
|
|
360
387
|
|
|
361
388
|
if (loading) {
|
|
362
|
-
|
|
363
|
-
const switcherIcon = switcher.querySelector(".hakumi-tree-switcher-icon")
|
|
364
|
-
if (switcherIcon) {
|
|
365
|
-
switcherIcon.style.display = "none"
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (!switcher.querySelector(".hakumi-tree-loading-icon")) {
|
|
370
|
-
const loadingIcon = document.createElement("span")
|
|
371
|
-
loadingIcon.className = "hakumi-tree-loading-icon"
|
|
372
|
-
loadingIcon.innerHTML = `<svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor">
|
|
373
|
-
<path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"/>
|
|
374
|
-
</svg>`
|
|
375
|
-
switcher.appendChild(loadingIcon)
|
|
376
|
-
}
|
|
389
|
+
this.showLoadingSwitcher(switcher)
|
|
377
390
|
} else {
|
|
391
|
+
this.hideLoadingSwitcher(switcher)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
378
394
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
395
|
+
showLoadingSwitcher(switcher) {
|
|
396
|
+
const switcherIcon = switcher.querySelector(".hakumi-tree-switcher-icon")
|
|
397
|
+
if (switcherIcon) {
|
|
398
|
+
switcherIcon.style.display = "none"
|
|
399
|
+
}
|
|
383
400
|
|
|
401
|
+
if (!switcher.querySelector(".hakumi-tree-loading-icon")) {
|
|
402
|
+
const loadingIcon = document.createElement("span")
|
|
403
|
+
loadingIcon.className = "hakumi-tree-loading-icon"
|
|
404
|
+
loadingIcon.innerHTML = `<svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor">
|
|
405
|
+
<path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"/>
|
|
406
|
+
</svg>`
|
|
407
|
+
switcher.appendChild(loadingIcon)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
384
410
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
411
|
+
hideLoadingSwitcher(switcher) {
|
|
412
|
+
const switcherIcon = switcher.querySelector(".hakumi-tree-switcher-icon")
|
|
413
|
+
if (switcherIcon) {
|
|
414
|
+
switcherIcon.style.display = ""
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const loadingIcon = switcher.querySelector(".hakumi-tree-loading-icon")
|
|
418
|
+
if (loadingIcon) {
|
|
419
|
+
loadingIcon.remove()
|
|
389
420
|
}
|
|
390
421
|
}
|
|
391
422
|
|
|
@@ -420,7 +451,6 @@ export default class extends RegistryController {
|
|
|
420
451
|
}
|
|
421
452
|
|
|
422
453
|
addNodes(parentKey, nodes) {
|
|
423
|
-
|
|
424
454
|
if (!nodes || nodes.length === 0) {
|
|
425
455
|
this.markAsLeaf(parentKey)
|
|
426
456
|
this.setLoading(parentKey, false)
|
|
@@ -430,50 +460,58 @@ export default class extends RegistryController {
|
|
|
430
460
|
const parent = this.nodeMap.get(parentKey)
|
|
431
461
|
if (!parent) return
|
|
432
462
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
switcher.classList.remove("hakumi-tree-switcher-leaf")
|
|
439
|
-
switcher.dataset.action = "click->hakumi--tree#toggle"
|
|
440
|
-
if (switcher && !switcher.querySelector(".hakumi-tree-switcher-icon")) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
463
|
+
this.ensureAsyncParentCanContainChildren(parent)
|
|
464
|
+
const container = this.ensureChildrenContainer(parent.element)
|
|
465
|
+
this.appendNodeElements(container, nodes, parent, parentKey)
|
|
466
|
+
this.refreshAfterNodesAdded()
|
|
467
|
+
}
|
|
446
468
|
|
|
469
|
+
ensureAsyncParentCanContainChildren(parent) {
|
|
470
|
+
if (parent.element.dataset.leaf !== "true") return
|
|
447
471
|
|
|
472
|
+
parent.element.dataset.leaf = "false"
|
|
473
|
+
parent.element.classList.remove("hakumi-tree-node-leaf")
|
|
474
|
+
const switcher = parent.element.querySelector(".hakumi-tree-switcher")
|
|
475
|
+
if (!switcher) return
|
|
448
476
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
477
|
+
switcher.classList.remove("hakumi-tree-switcher-leaf")
|
|
478
|
+
switcher.dataset.action = "click->hakumi--tree#toggle"
|
|
479
|
+
if (!switcher.querySelector(".hakumi-tree-switcher-icon")) {
|
|
480
|
+
const tmpl = this.nodeTemplateTarget.content.cloneNode(true)
|
|
481
|
+
const tmplSwitcher = tmpl.querySelector(".hakumi-tree-switcher")
|
|
482
|
+
const tmplIcon = tmplSwitcher.querySelector(".hakumi-tree-switcher-icon")
|
|
483
|
+
if (tmplIcon) switcher.appendChild(tmplIcon.cloneNode(true))
|
|
455
484
|
}
|
|
485
|
+
}
|
|
456
486
|
|
|
457
|
-
|
|
487
|
+
ensureChildrenContainer(parentElement) {
|
|
488
|
+
let container = parentElement.querySelector(".hakumi-tree-children")
|
|
458
489
|
if (!container) {
|
|
459
490
|
container = document.createElement("div")
|
|
460
491
|
container.className = "hakumi-tree-children"
|
|
461
492
|
container.setAttribute("role", "group")
|
|
462
493
|
container.dataset.hakumiTreeTarget = "children"
|
|
463
|
-
|
|
494
|
+
parentElement.appendChild(container)
|
|
464
495
|
}
|
|
465
496
|
|
|
497
|
+
return container
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
appendNodeElements(container, nodes, parent, parentKey) {
|
|
466
501
|
const parentLevel = parseInt(parent.element.dataset.level || "0", 10)
|
|
467
502
|
nodes.forEach((node, index) => {
|
|
468
503
|
const hasNext = index < nodes.length - 1
|
|
469
504
|
const nodeElement = this.createNodeElement(node, parentLevel + 1, parentKey, hasNext)
|
|
470
505
|
container.appendChild(nodeElement)
|
|
471
506
|
})
|
|
507
|
+
}
|
|
472
508
|
|
|
509
|
+
refreshAfterNodesAdded() {
|
|
473
510
|
this.buildNodeMap()
|
|
474
511
|
this.applyExpandedStates()
|
|
475
512
|
this.applySelectedStates()
|
|
476
513
|
this.applyCheckedStates(true)
|
|
514
|
+
this.syncRovingTabindex()
|
|
477
515
|
if (this.draggableValue) this.setupSortables()
|
|
478
516
|
}
|
|
479
517
|
|
|
@@ -588,13 +626,7 @@ export default class extends RegistryController {
|
|
|
588
626
|
setupSortables() {
|
|
589
627
|
this.destroySortables()
|
|
590
628
|
|
|
591
|
-
|
|
592
|
-
if (this.hasListTarget) {
|
|
593
|
-
containers.add(this.listTarget)
|
|
594
|
-
}
|
|
595
|
-
this.childrenTargets.forEach((child) => containers.add(child))
|
|
596
|
-
|
|
597
|
-
containers.forEach((container) => {
|
|
629
|
+
this.sortableContainers().forEach((container) => {
|
|
598
630
|
const sortable = Sortable.create(container, {
|
|
599
631
|
animation: 150,
|
|
600
632
|
group: "hakumi-tree",
|
|
@@ -603,27 +635,43 @@ export default class extends RegistryController {
|
|
|
603
635
|
ghostClass: "hakumi-tree-node-ghost",
|
|
604
636
|
chosenClass: "hakumi-tree-node-chosen",
|
|
605
637
|
dragClass: "hakumi-tree-node-drag",
|
|
606
|
-
onEnd: (evt) =>
|
|
607
|
-
const item = evt.item
|
|
608
|
-
const parentNode = item.closest(".hakumi-tree-node")
|
|
609
|
-
const parentKey = parentNode ? parentNode.dataset.key : null
|
|
610
|
-
item.dataset.parentKey = parentKey || ""
|
|
611
|
-
this.buildNodeMap()
|
|
612
|
-
this.dispatch("dragEnd", {
|
|
613
|
-
detail: {
|
|
614
|
-
key: item.dataset.key,
|
|
615
|
-
parentKey,
|
|
616
|
-
oldIndex: evt.oldIndex,
|
|
617
|
-
newIndex: evt.newIndex
|
|
618
|
-
}
|
|
619
|
-
})
|
|
620
|
-
}
|
|
638
|
+
onEnd: (evt) => this.handleDragEnd(evt)
|
|
621
639
|
})
|
|
622
640
|
|
|
623
641
|
this.sortables.set(container, sortable)
|
|
624
642
|
})
|
|
625
643
|
}
|
|
626
644
|
|
|
645
|
+
sortableContainers() {
|
|
646
|
+
const containers = new Set()
|
|
647
|
+
if (this.hasListTarget) {
|
|
648
|
+
containers.add(this.listTarget)
|
|
649
|
+
}
|
|
650
|
+
this.childrenTargets.forEach((child) => containers.add(child))
|
|
651
|
+
return containers
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
handleDragEnd(event) {
|
|
655
|
+
const item = event.item
|
|
656
|
+
const parentKey = this.dragParentKey(event, item)
|
|
657
|
+
item.dataset.parentKey = parentKey || ""
|
|
658
|
+
this.buildNodeMap()
|
|
659
|
+
this.dispatch("dragEnd", {
|
|
660
|
+
detail: {
|
|
661
|
+
key: item.dataset.key,
|
|
662
|
+
parentKey,
|
|
663
|
+
oldIndex: event.oldIndex,
|
|
664
|
+
newIndex: event.newIndex
|
|
665
|
+
}
|
|
666
|
+
})
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
dragParentKey(event, item) {
|
|
670
|
+
const parentContainer = event.to || item.parentElement
|
|
671
|
+
const parentNode = parentContainer ? parentContainer.closest(".hakumi-tree-node") : null
|
|
672
|
+
return parentNode ? parentNode.dataset.key : null
|
|
673
|
+
}
|
|
674
|
+
|
|
627
675
|
destroySortables() {
|
|
628
676
|
this.sortables.forEach((sortable) => sortable.destroy())
|
|
629
677
|
this.sortables.clear()
|
|
@@ -636,6 +684,7 @@ export default class extends RegistryController {
|
|
|
636
684
|
|
|
637
685
|
applySearch(query) {
|
|
638
686
|
const term = query.trim().toLowerCase()
|
|
687
|
+
this.clearTypeahead()
|
|
639
688
|
|
|
640
689
|
if (!term) {
|
|
641
690
|
if (this.searching && this.previousExpandedKeys) {
|
|
@@ -649,6 +698,7 @@ export default class extends RegistryController {
|
|
|
649
698
|
if (titleEl) titleEl.textContent = titleEl.dataset.title || ""
|
|
650
699
|
})
|
|
651
700
|
this.applyExpandedStates()
|
|
701
|
+
this.syncRovingTabindex()
|
|
652
702
|
return
|
|
653
703
|
}
|
|
654
704
|
|
|
@@ -698,9 +748,244 @@ export default class extends RegistryController {
|
|
|
698
748
|
})
|
|
699
749
|
|
|
700
750
|
this.applyExpandedStates()
|
|
751
|
+
this.syncRovingTabindex()
|
|
701
752
|
}
|
|
702
753
|
|
|
703
754
|
closestNode(event) {
|
|
704
755
|
return event.currentTarget.closest(".hakumi-tree-node")
|
|
705
756
|
}
|
|
757
|
+
|
|
758
|
+
handleFocusin(event) {
|
|
759
|
+
const node = event.target.closest(".hakumi-tree-node")
|
|
760
|
+
if (!node || !this.element.contains(node) || !this.isVisibleNode(node)) return
|
|
761
|
+
|
|
762
|
+
this.setFocusedNode(node, { focus: false })
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
handleKeydown(event) {
|
|
766
|
+
const currentNode = event.target.closest(".hakumi-tree-node")
|
|
767
|
+
if (!currentNode || !this.element.contains(currentNode)) return
|
|
768
|
+
|
|
769
|
+
if (!["ArrowDown", "ArrowUp", "ArrowRight", "ArrowLeft", "Home", "End", "Enter", " "].includes(event.key) && !this.isTypeaheadKey(event)) return
|
|
770
|
+
|
|
771
|
+
if (event.key === "ArrowDown") {
|
|
772
|
+
event.preventDefault()
|
|
773
|
+
this.focusRelativeNode(currentNode, 1)
|
|
774
|
+
return
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (event.key === "ArrowUp") {
|
|
778
|
+
event.preventDefault()
|
|
779
|
+
this.focusRelativeNode(currentNode, -1)
|
|
780
|
+
return
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (event.key === "Home") {
|
|
784
|
+
event.preventDefault()
|
|
785
|
+
this.focusFirstVisibleNode()
|
|
786
|
+
return
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (event.key === "End") {
|
|
790
|
+
event.preventDefault()
|
|
791
|
+
this.focusLastVisibleNode()
|
|
792
|
+
return
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (event.key === "ArrowRight") {
|
|
796
|
+
event.preventDefault()
|
|
797
|
+
this.expandOrFocusChild(currentNode)
|
|
798
|
+
return
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (event.key === "ArrowLeft") {
|
|
802
|
+
event.preventDefault()
|
|
803
|
+
this.collapseOrFocusParent(currentNode)
|
|
804
|
+
return
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (this.isTypeaheadKey(event)) {
|
|
808
|
+
event.preventDefault()
|
|
809
|
+
this.focusTypeaheadMatch(currentNode, event.key)
|
|
810
|
+
return
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
event.preventDefault()
|
|
814
|
+
this.selectKeyboardNode(currentNode)
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
focusRelativeNode(currentNode, offset) {
|
|
818
|
+
const visibleNodes = this.visibleNodes()
|
|
819
|
+
const currentIndex = visibleNodes.indexOf(currentNode)
|
|
820
|
+
if (currentIndex === -1) return
|
|
821
|
+
|
|
822
|
+
const nextNode = visibleNodes[currentIndex + offset]
|
|
823
|
+
if (nextNode) this.setFocusedNode(nextNode)
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
focusFirstVisibleNode() {
|
|
827
|
+
const firstNode = this.visibleNodes()[0]
|
|
828
|
+
if (firstNode) this.setFocusedNode(firstNode)
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
focusLastVisibleNode() {
|
|
832
|
+
const visibleNodes = this.visibleNodes()
|
|
833
|
+
const lastNode = visibleNodes[visibleNodes.length - 1]
|
|
834
|
+
if (lastNode) this.setFocusedNode(lastNode)
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
focusTypeaheadMatch(currentNode, key) {
|
|
838
|
+
this.typeaheadBuffer += key.toLowerCase()
|
|
839
|
+
this.scheduleTypeaheadReset()
|
|
840
|
+
|
|
841
|
+
const visibleNodes = this.visibleNodes()
|
|
842
|
+
if (visibleNodes.length === 0) return
|
|
843
|
+
|
|
844
|
+
const currentIndex = visibleNodes.indexOf(currentNode)
|
|
845
|
+
const startIndex = currentIndex >= 0 ? currentIndex + 1 : 0
|
|
846
|
+
const candidates = visibleNodes.slice(startIndex).concat(visibleNodes.slice(0, startIndex))
|
|
847
|
+
const match = candidates.find((node) => this.nodeTitle(node).toLowerCase().startsWith(this.typeaheadBuffer))
|
|
848
|
+
|
|
849
|
+
if (match) this.setFocusedNode(match)
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
isTypeaheadKey(event) {
|
|
853
|
+
return event.key.length === 1 && event.key !== " " && !event.altKey && !event.ctrlKey && !event.metaKey
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
scheduleTypeaheadReset() {
|
|
857
|
+
if (this.typeaheadTimeout) clearTimeout(this.typeaheadTimeout)
|
|
858
|
+
|
|
859
|
+
this.typeaheadTimeout = setTimeout(() => {
|
|
860
|
+
this.typeaheadBuffer = ""
|
|
861
|
+
this.typeaheadTimeout = null
|
|
862
|
+
}, 500)
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
clearTypeahead() {
|
|
866
|
+
if (this.typeaheadTimeout) {
|
|
867
|
+
clearTimeout(this.typeaheadTimeout)
|
|
868
|
+
this.typeaheadTimeout = null
|
|
869
|
+
}
|
|
870
|
+
this.typeaheadBuffer = ""
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
nodeTitle(node) {
|
|
874
|
+
return node.dataset.title || node.querySelector(".hakumi-tree-title-text")?.textContent || ""
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
expandOrFocusChild(node) {
|
|
878
|
+
if (node.dataset.leaf === "true") return
|
|
879
|
+
|
|
880
|
+
const key = node.dataset.key
|
|
881
|
+
if (!this.expandedKeys.has(key)) {
|
|
882
|
+
this.toggleNodeByKey(key)
|
|
883
|
+
this.setFocusedNode(node)
|
|
884
|
+
return
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const firstChild = this.firstVisibleChild(node)
|
|
888
|
+
if (firstChild) this.setFocusedNode(firstChild)
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
collapseOrFocusParent(node) {
|
|
892
|
+
if (node.dataset.leaf !== "true" && this.expandedKeys.has(node.dataset.key)) {
|
|
893
|
+
this.toggleNodeByKey(node.dataset.key)
|
|
894
|
+
this.setFocusedNode(node)
|
|
895
|
+
return
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
const parent = this.parentNode(node)
|
|
899
|
+
if (parent) this.setFocusedNode(parent)
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
selectKeyboardNode(node) {
|
|
903
|
+
if (!this.canSelectNode(node)) return
|
|
904
|
+
|
|
905
|
+
this.selectNodeByKey(node.dataset.key)
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
canSelectNode(node) {
|
|
909
|
+
return (
|
|
910
|
+
this.selectableValue &&
|
|
911
|
+
node.dataset.selectable !== "false" &&
|
|
912
|
+
node.dataset.disabled !== "true" &&
|
|
913
|
+
!this.disabledValue
|
|
914
|
+
)
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
visibleNodes() {
|
|
918
|
+
return this.navigationNodeTargets().filter((node) => this.isVisibleNode(node))
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
navigationNodeTargets() {
|
|
922
|
+
if (this.hasHeightValue) return this.virtualHeightNodeTargets()
|
|
923
|
+
|
|
924
|
+
return this.nodeTargets
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
virtualHeightNodeTargets() {
|
|
928
|
+
return this.nodeTargets
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
isVisibleNode(node) {
|
|
932
|
+
if (node.classList.contains("hakumi-tree-node-hidden")) return false
|
|
933
|
+
|
|
934
|
+
let parent = this.parentNode(node)
|
|
935
|
+
while (parent) {
|
|
936
|
+
if (parent.classList.contains("hakumi-tree-node-hidden")) return false
|
|
937
|
+
if (!this.expandedKeys.has(parent.dataset.key)) return false
|
|
938
|
+
parent = this.parentNode(parent)
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
return true
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
firstVisibleChild(node) {
|
|
945
|
+
const entry = this.nodeMap.get(node.dataset.key)
|
|
946
|
+
if (!entry) return null
|
|
947
|
+
|
|
948
|
+
const childKey = entry.children.find((key) => {
|
|
949
|
+
const child = this.nodeMap.get(key)
|
|
950
|
+
return child && this.isVisibleNode(child.element)
|
|
951
|
+
})
|
|
952
|
+
|
|
953
|
+
return childKey ? this.nodeMap.get(childKey).element : null
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
parentNode(node) {
|
|
957
|
+
const parentKey = node.dataset.parentKey || null
|
|
958
|
+
if (!parentKey || !this.nodeMap.has(parentKey)) return null
|
|
959
|
+
|
|
960
|
+
return this.nodeMap.get(parentKey).element
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
syncRovingTabindex() {
|
|
964
|
+
const visibleNodes = this.visibleNodes()
|
|
965
|
+
const current = visibleNodes.find((node) => node.getAttribute("tabindex") === "0")
|
|
966
|
+
const selected = visibleNodes.find((node) => this.selectedKeys.has(node.dataset.key))
|
|
967
|
+
const focused = current || selected || visibleNodes[0] || null
|
|
968
|
+
|
|
969
|
+
this.nodeTargets.forEach((node) => {
|
|
970
|
+
node.setAttribute("tabindex", node === focused ? "0" : "-1")
|
|
971
|
+
this.syncNodeControlTabindex(node)
|
|
972
|
+
})
|
|
973
|
+
|
|
974
|
+
return focused
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
setFocusedNode(node, { focus = true } = {}) {
|
|
978
|
+
this.nodeTargets.forEach((target) => {
|
|
979
|
+
target.setAttribute("tabindex", target === node ? "0" : "-1")
|
|
980
|
+
this.syncNodeControlTabindex(target)
|
|
981
|
+
})
|
|
982
|
+
|
|
983
|
+
if (focus) node.focus()
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
syncNodeControlTabindex(node) {
|
|
987
|
+
node.querySelectorAll(".hakumi-tree-switcher, .hakumi-tree-checkbox, .hakumi-tree-title").forEach((control) => {
|
|
988
|
+
control.setAttribute("tabindex", "-1")
|
|
989
|
+
})
|
|
990
|
+
}
|
|
706
991
|
}
|
|
@@ -30,11 +30,11 @@ export default class extends RegistryController {
|
|
|
30
30
|
|
|
31
31
|
async copy(event) {
|
|
32
32
|
|
|
33
|
-
const copyIcon = event
|
|
33
|
+
const copyIcon = event?.target?.closest?.('.hakumi-typography-copy') || this.element.querySelector('.hakumi-typography-copy')
|
|
34
34
|
if (!copyIcon) return
|
|
35
35
|
|
|
36
|
-
event
|
|
37
|
-
event
|
|
36
|
+
event?.preventDefault?.()
|
|
37
|
+
event?.stopPropagation?.()
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
const clone = this.element.cloneNode(true)
|