maquina-components 0.4.2 → 0.4.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.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b709941be68c552761043a38b7359796b7bc644af13827fbd47b2f40eab90fb9
|
|
4
|
+
data.tar.gz: 931d38949a68e47881e9dc417786a412e5fb16ed02a23539d44259f452249913
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3b063e95e3ea3c7d4fd649b75cd00696ac33ac88025c3ebab14bea256f7456005a81b8e0c7f3b70f8904f73cc987c4d5cc22a707cbef48f3d10dafa21a7d9ec1
|
|
7
|
+
data.tar.gz: 80b31cd01a92351baedec4a87509b494c6dbfe09cb42bb71c7a59444cca0cc48e4b020684d9c450c1b02568a352f0b2781654c5333a84a0c5af7ea38940f91a6
|
|
@@ -107,6 +107,29 @@
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
/* ===== Responsive Behavior ===== */
|
|
110
|
+
/* Force nowrap when responsive controller is active so scrollWidth detects overflow */
|
|
111
|
+
[data-controller="breadcrumb"] [data-breadcrumb-part="list"] {
|
|
112
|
+
@apply flex-nowrap overflow-hidden;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Items must not shrink or wrap text, otherwise flex compresses them and scrollWidth never exceeds clientWidth */
|
|
116
|
+
[data-controller="breadcrumb"] [data-breadcrumb-part="item"],
|
|
117
|
+
[data-controller="breadcrumb"] [data-breadcrumb-part="separator"] {
|
|
118
|
+
@apply shrink-0 whitespace-nowrap;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Allow the last item (current page) to truncate gracefully instead of hard-clipping */
|
|
122
|
+
[data-controller="breadcrumb"] [data-breadcrumb-part="item"]:last-child {
|
|
123
|
+
@apply shrink min-w-0 overflow-hidden;
|
|
124
|
+
text-overflow: ellipsis;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
[data-controller="breadcrumb"] [data-breadcrumb-part="item"]:last-child [data-breadcrumb-part="page"],
|
|
128
|
+
[data-controller="breadcrumb"] [data-breadcrumb-part="item"]:last-child [data-breadcrumb-part="link"] {
|
|
129
|
+
@apply overflow-hidden;
|
|
130
|
+
text-overflow: ellipsis;
|
|
131
|
+
}
|
|
132
|
+
|
|
110
133
|
/* Items hidden by Stimulus controller */
|
|
111
134
|
[data-controller="breadcrumb"] [data-breadcrumb-target="item"].hidden,
|
|
112
135
|
[data-controller="breadcrumb"] [data-breadcrumb-target="ellipsisSeparator"].hidden {
|
|
@@ -4,6 +4,13 @@ export default class extends Controller {
|
|
|
4
4
|
static targets = ["item", "ellipsis", "ellipsisSeparator"]
|
|
5
5
|
|
|
6
6
|
connect() {
|
|
7
|
+
this._dropdown = null
|
|
8
|
+
this._clickOutsideHandler = this._closeDropdown.bind(this)
|
|
9
|
+
this._escapeHandler = this._handleEscape.bind(this)
|
|
10
|
+
this._teardownHandler = this._teardown.bind(this)
|
|
11
|
+
|
|
12
|
+
document.addEventListener("turbo:before-cache", this._teardownHandler)
|
|
13
|
+
|
|
7
14
|
this.windowResizeHandler = this.handleResize.bind(this)
|
|
8
15
|
window.addEventListener('resize', this.windowResizeHandler)
|
|
9
16
|
this.handleResize()
|
|
@@ -11,61 +18,185 @@ export default class extends Controller {
|
|
|
11
18
|
|
|
12
19
|
disconnect() {
|
|
13
20
|
window.removeEventListener('resize', this.windowResizeHandler)
|
|
21
|
+
this._teardown()
|
|
22
|
+
document.removeEventListener("turbo:before-cache", this._teardownHandler)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
ellipsisTargetConnected(element) {
|
|
26
|
+
const trigger = element.querySelector('[data-breadcrumb-part="ellipsis"]')
|
|
27
|
+
if (trigger) {
|
|
28
|
+
this._ellipsisTrigger = trigger
|
|
29
|
+
this._toggleHandler = this._toggleDropdown.bind(this)
|
|
30
|
+
trigger.addEventListener('click', this._toggleHandler)
|
|
31
|
+
trigger.style.cursor = 'pointer'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ellipsisTargetDisconnected(_element) {
|
|
36
|
+
if (this._ellipsisTrigger && this._toggleHandler) {
|
|
37
|
+
this._ellipsisTrigger.removeEventListener('click', this._toggleHandler)
|
|
38
|
+
this._ellipsisTrigger = null
|
|
39
|
+
this._toggleHandler = null
|
|
40
|
+
}
|
|
14
41
|
}
|
|
15
42
|
|
|
16
43
|
handleResize() {
|
|
17
|
-
|
|
18
|
-
|
|
44
|
+
const list = this.element.querySelector('[data-breadcrumb-part="list"]')
|
|
45
|
+
if (!list) return
|
|
46
|
+
|
|
19
47
|
const items = this.itemTargets
|
|
20
48
|
const ellipsis = this.hasEllipsisTarget ? this.ellipsisTarget : null
|
|
21
49
|
const ellipsisSeparator = this.hasEllipsisSeparatorTarget ? this.ellipsisSeparatorTarget : null
|
|
22
50
|
|
|
23
|
-
|
|
24
|
-
if (items.length < 3 || !ellipsis) {
|
|
25
|
-
return; // Not enough items to collapse or no ellipsis element
|
|
26
|
-
}
|
|
51
|
+
if (items.length < 1 || !ellipsis) return
|
|
27
52
|
|
|
28
|
-
// Reset
|
|
29
|
-
|
|
53
|
+
// Reset all items and their adjacent separators to visible
|
|
54
|
+
ellipsis.classList.add('hidden')
|
|
30
55
|
if (ellipsisSeparator) ellipsisSeparator.classList.add('hidden')
|
|
31
|
-
|
|
32
56
|
items.forEach(item => {
|
|
33
57
|
item.classList.remove('hidden')
|
|
58
|
+
const sep = this._adjacentSeparator(item)
|
|
59
|
+
if (sep) sep.classList.remove('hidden')
|
|
34
60
|
})
|
|
35
61
|
|
|
36
|
-
// Check
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
totalWidth += item.offsetWidth
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
if (totalWidth > containerWidth) {
|
|
43
|
-
// We need to collapse items - show ellipsis
|
|
44
|
-
if (ellipsis) ellipsis.classList.remove('hidden')
|
|
62
|
+
// Check overflow using scrollWidth vs clientWidth
|
|
63
|
+
if (list.scrollWidth > list.clientWidth) {
|
|
64
|
+
ellipsis.classList.remove('hidden')
|
|
45
65
|
if (ellipsisSeparator) ellipsisSeparator.classList.remove('hidden')
|
|
46
66
|
|
|
47
|
-
//
|
|
48
|
-
for (let i = items.length -
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
// Hide middle items one at a time until it fits
|
|
68
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
69
|
+
items[i].classList.add('hidden')
|
|
70
|
+
const sep = this._adjacentSeparator(items[i])
|
|
71
|
+
if (sep) sep.classList.add('hidden')
|
|
72
|
+
if (list.scrollWidth <= list.clientWidth) break
|
|
73
|
+
}
|
|
74
|
+
}
|
|
51
75
|
|
|
52
|
-
|
|
53
|
-
|
|
76
|
+
this._updateDropdown()
|
|
77
|
+
}
|
|
54
78
|
|
|
55
|
-
|
|
56
|
-
|
|
79
|
+
// Find the next sibling separator <li> (not the managed ellipsisSeparator)
|
|
80
|
+
_adjacentSeparator(item) {
|
|
81
|
+
const next = item.nextElementSibling
|
|
82
|
+
if (next && next.dataset.breadcrumbPart === "separator" && !next.dataset.breadcrumbTarget) {
|
|
83
|
+
return next
|
|
84
|
+
}
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Collect hidden items and update dropdown content
|
|
89
|
+
_updateDropdown() {
|
|
90
|
+
const hiddenItems = this.itemTargets.filter(item => item.classList.contains('hidden'))
|
|
57
91
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
})
|
|
92
|
+
if (hiddenItems.length === 0) {
|
|
93
|
+
this._removeDropdown()
|
|
94
|
+
return
|
|
95
|
+
}
|
|
63
96
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
97
|
+
// Build list of links from hidden items
|
|
98
|
+
this._hiddenLinks = hiddenItems.map(item => {
|
|
99
|
+
const link = item.querySelector('[data-breadcrumb-part="link"]')
|
|
100
|
+
if (link) {
|
|
101
|
+
return { href: link.getAttribute('href'), text: link.textContent.trim() }
|
|
68
102
|
}
|
|
103
|
+
return null
|
|
104
|
+
}).filter(Boolean)
|
|
105
|
+
|
|
106
|
+
// If dropdown is currently open, rebuild its content
|
|
107
|
+
if (this._dropdown && this._dropdown.dataset.state === "open") {
|
|
108
|
+
this._buildDropdownContent()
|
|
69
109
|
}
|
|
70
110
|
}
|
|
111
|
+
|
|
112
|
+
_toggleDropdown(event) {
|
|
113
|
+
event.stopPropagation()
|
|
114
|
+
|
|
115
|
+
if (this._dropdown && this._dropdown.dataset.state === "open") {
|
|
116
|
+
this._closeDropdown()
|
|
117
|
+
} else {
|
|
118
|
+
this._openDropdown()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_openDropdown() {
|
|
123
|
+
if (!this._hiddenLinks || this._hiddenLinks.length === 0) return
|
|
124
|
+
|
|
125
|
+
if (!this._dropdown) {
|
|
126
|
+
this._dropdown = document.createElement('div')
|
|
127
|
+
this._dropdown.setAttribute('role', 'menu')
|
|
128
|
+
this._dropdown.dataset.dropdownMenuPart = 'content'
|
|
129
|
+
this._dropdown.style.position = 'fixed'
|
|
130
|
+
this._dropdown.style.zIndex = '50'
|
|
131
|
+
document.body.appendChild(this._dropdown)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this._buildDropdownContent()
|
|
135
|
+
this._positionDropdown()
|
|
136
|
+
this._dropdown.dataset.state = 'open'
|
|
137
|
+
|
|
138
|
+
if (this._ellipsisTrigger) {
|
|
139
|
+
this._ellipsisTrigger.dataset.state = 'open'
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Defer listeners so the current click doesn't immediately close
|
|
143
|
+
requestAnimationFrame(() => {
|
|
144
|
+
document.addEventListener('click', this._clickOutsideHandler)
|
|
145
|
+
document.addEventListener('keydown', this._escapeHandler)
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
_closeDropdown() {
|
|
150
|
+
if (this._dropdown) {
|
|
151
|
+
this._dropdown.dataset.state = 'closed'
|
|
152
|
+
}
|
|
153
|
+
if (this._ellipsisTrigger) {
|
|
154
|
+
delete this._ellipsisTrigger.dataset.state
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
document.removeEventListener('click', this._clickOutsideHandler)
|
|
158
|
+
document.removeEventListener('keydown', this._escapeHandler)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
_removeDropdown() {
|
|
162
|
+
this._closeDropdown()
|
|
163
|
+
if (this._dropdown) {
|
|
164
|
+
this._dropdown.remove()
|
|
165
|
+
this._dropdown = null
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
_buildDropdownContent() {
|
|
170
|
+
if (!this._dropdown || !this._hiddenLinks) return
|
|
171
|
+
|
|
172
|
+
this._dropdown.innerHTML = ''
|
|
173
|
+
this._hiddenLinks.forEach(({ href, text }) => {
|
|
174
|
+
const link = document.createElement('a')
|
|
175
|
+
link.setAttribute('href', href)
|
|
176
|
+
link.setAttribute('role', 'menuitem')
|
|
177
|
+
link.dataset.dropdownMenuPart = 'item'
|
|
178
|
+
link.setAttribute('tabindex', '-1')
|
|
179
|
+
link.textContent = text
|
|
180
|
+
this._dropdown.appendChild(link)
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
_positionDropdown() {
|
|
185
|
+
if (!this._dropdown || !this._ellipsisTrigger) return
|
|
186
|
+
|
|
187
|
+
const rect = this._ellipsisTrigger.getBoundingClientRect()
|
|
188
|
+
this._dropdown.style.top = `${rect.bottom + 4}px`
|
|
189
|
+
this._dropdown.style.left = `${rect.left}px`
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
_handleEscape(event) {
|
|
193
|
+
if (event.key === 'Escape') {
|
|
194
|
+
this._closeDropdown()
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
_teardown() {
|
|
199
|
+
this._removeDropdown()
|
|
200
|
+
this._hiddenLinks = null
|
|
201
|
+
}
|
|
71
202
|
}
|