@rhavenside/baseline 2.0.0
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 +472 -0
- package/dist/base-line.css +5 -0
- package/dist/base-line.css.map +1 -0
- package/dist/fonts/GoogleSansCode-Bold.ttf +0 -0
- package/dist/fonts/GoogleSansCode-BoldItalic.ttf +0 -0
- package/dist/fonts/GoogleSansCode-ExtraBold.ttf +0 -0
- package/dist/fonts/GoogleSansCode-ExtraBoldItalic.ttf +0 -0
- package/dist/fonts/GoogleSansCode-Italic-VariableFont_wght.ttf +0 -0
- package/dist/fonts/GoogleSansCode-Italic.ttf +0 -0
- package/dist/fonts/GoogleSansCode-Light.ttf +0 -0
- package/dist/fonts/GoogleSansCode-LightItalic.ttf +0 -0
- package/dist/fonts/GoogleSansCode-Medium.ttf +0 -0
- package/dist/fonts/GoogleSansCode-MediumItalic.ttf +0 -0
- package/dist/fonts/GoogleSansCode-Regular.ttf +0 -0
- package/dist/fonts/GoogleSansCode-SemiBold.ttf +0 -0
- package/dist/fonts/GoogleSansCode-SemiBoldItalic.ttf +0 -0
- package/dist/fonts/GoogleSansCode-VariableFont_wght.ttf +0 -0
- package/dist/fonts/RobotoCondensed-Black.ttf +0 -0
- package/dist/fonts/RobotoCondensed-BlackItalic.ttf +0 -0
- package/dist/fonts/RobotoCondensed-Bold.ttf +0 -0
- package/dist/fonts/RobotoCondensed-BoldItalic.ttf +0 -0
- package/dist/fonts/RobotoCondensed-ExtraBold.ttf +0 -0
- package/dist/fonts/RobotoCondensed-ExtraBoldItalic.ttf +0 -0
- package/dist/fonts/RobotoCondensed-ExtraLight.ttf +0 -0
- package/dist/fonts/RobotoCondensed-ExtraLightItalic.ttf +0 -0
- package/dist/fonts/RobotoCondensed-Italic-VariableFont_wght.ttf +0 -0
- package/dist/fonts/RobotoCondensed-Italic.ttf +0 -0
- package/dist/fonts/RobotoCondensed-Light.ttf +0 -0
- package/dist/fonts/RobotoCondensed-LightItalic.ttf +0 -0
- package/dist/fonts/RobotoCondensed-Medium.ttf +0 -0
- package/dist/fonts/RobotoCondensed-MediumItalic.ttf +0 -0
- package/dist/fonts/RobotoCondensed-Regular.ttf +0 -0
- package/dist/fonts/RobotoCondensed-SemiBold.ttf +0 -0
- package/dist/fonts/RobotoCondensed-SemiBoldItalic.ttf +0 -0
- package/dist/fonts/RobotoCondensed-Thin.ttf +0 -0
- package/dist/fonts/RobotoCondensed-ThinItalic.ttf +0 -0
- package/dist/fonts/RobotoCondensed-VariableFont_wght.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-Black.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-BlackItalic.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-Bold.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-BoldItalic.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-ExtraBold.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-ExtraBoldItalic.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-ExtraLight.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-ExtraLightItalic.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-Italic-VariableFont_wght.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-Italic.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-Light.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-LightItalic.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-Medium.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-MediumItalic.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-Regular.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-SemiBold.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-SemiBoldItalic.ttf +0 -0
- package/dist/fonts/ZalandoSansExpanded-VariableFont_wght.ttf +0 -0
- package/dist/fonts/baseline-icons.woff +0 -0
- package/dist/fonts/baseline-icons.woff2 +0 -0
- package/dist/js/accordion.js +103 -0
- package/dist/js/alert.js +91 -0
- package/dist/js/base.js +146 -0
- package/dist/js/button.js +80 -0
- package/dist/js/carousel.js +427 -0
- package/dist/js/collapse.js +233 -0
- package/dist/js/color-modes.js +70 -0
- package/dist/js/component.js +114 -0
- package/dist/js/dropdown.js +348 -0
- package/dist/js/index.js +108 -0
- package/dist/js/modal.js +440 -0
- package/dist/js/offcanvas.js +356 -0
- package/dist/js/popover.js +241 -0
- package/dist/js/swipe.js +143 -0
- package/dist/js/tab.js +285 -0
- package/dist/js/toast.js +228 -0
- package/dist/js/tooltip.js +716 -0
- package/dist/js/util/backdrop.js +133 -0
- package/dist/js/util/component-functions.js +111 -0
- package/dist/js/util/focustrap.js +101 -0
- package/dist/js/util/scrollbar.js +111 -0
- package/dist/js/util.js +564 -0
- package/package.json +47 -0
package/dist/js/util.js
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base-Line JavaScript Utilities
|
|
3
|
+
* Baseline 2.0 compatible utilities with Base-Line naming
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Properly escape IDs selectors to handle weird IDs
|
|
8
|
+
* @param {string} selector
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
export function parseSelector(selector) {
|
|
12
|
+
if (selector && window.CSS && window.CSS.escape) {
|
|
13
|
+
// document.querySelector needs escaping to handle IDs (html5+) containing for instance /
|
|
14
|
+
selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return selector
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get element by selector or return element if already an element
|
|
22
|
+
* Baseline-compatible: accepts both selectors and elements
|
|
23
|
+
*/
|
|
24
|
+
export function getElement(object, parent = document) {
|
|
25
|
+
// it's a jQuery object or a node element
|
|
26
|
+
if (isElement(object)) {
|
|
27
|
+
return object.jquery ? object[0] : object
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (typeof object === 'string' && object.length > 0) {
|
|
31
|
+
return parent.querySelector(parseSelector(object))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get elements by selector
|
|
39
|
+
*/
|
|
40
|
+
export function getElements(selector, parent = document) {
|
|
41
|
+
return Array.from(parent.querySelectorAll(selector))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get element from jQuery-like object or element
|
|
46
|
+
*/
|
|
47
|
+
export function getElementFromElement(element) {
|
|
48
|
+
if (element && element.jquery) {
|
|
49
|
+
return element[0]
|
|
50
|
+
}
|
|
51
|
+
return element
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Type check functions
|
|
56
|
+
*/
|
|
57
|
+
export function isElement(element) {
|
|
58
|
+
return element && element.nodeType === Node.ELEMENT_NODE
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isVisible(element) {
|
|
62
|
+
if (!isElement(element)) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
const style = window.getComputedStyle(element)
|
|
66
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isDisabled(element) {
|
|
70
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (element.classList && element.classList.contains('is-disabled')) {
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (typeof element.disabled !== 'undefined') {
|
|
79
|
+
return element.disabled
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get transition duration from element
|
|
87
|
+
*/
|
|
88
|
+
export function getTransitionDurationFromElement(element) {
|
|
89
|
+
if (!isElement(element)) {
|
|
90
|
+
return 0
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const transitionDuration = window.getComputedStyle(element).transitionDuration
|
|
94
|
+
const floatTransitionDuration = parseFloat(transitionDuration)
|
|
95
|
+
|
|
96
|
+
if (!floatTransitionDuration) {
|
|
97
|
+
return 0
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return transitionDuration.includes('ms') ? floatTransitionDuration : floatTransitionDuration * 1000
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Trigger transition end event
|
|
105
|
+
*/
|
|
106
|
+
export function triggerTransitionEnd(element) {
|
|
107
|
+
const event = new Event('transitionend', { bubbles: true, cancelable: true })
|
|
108
|
+
element.dispatchEvent(event)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Reflow - force reflow
|
|
113
|
+
*/
|
|
114
|
+
export function reflow(element) {
|
|
115
|
+
element.offsetHeight
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get jQuery element collection
|
|
120
|
+
*/
|
|
121
|
+
export function getjQuery() {
|
|
122
|
+
if (window.jQuery && !document.body.hasAttribute('data-bl-no-jquery')) {
|
|
123
|
+
return window.jQuery
|
|
124
|
+
}
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* DOMContentLoaded helper
|
|
130
|
+
*/
|
|
131
|
+
export function onDOMContentLoaded(callback) {
|
|
132
|
+
if (document.readyState === 'loading') {
|
|
133
|
+
document.addEventListener('DOMContentLoaded', callback)
|
|
134
|
+
} else {
|
|
135
|
+
callback()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Define property helper
|
|
141
|
+
*/
|
|
142
|
+
export function defineJQueryPlugin(plugin) {
|
|
143
|
+
const $ = getjQuery()
|
|
144
|
+
if ($) {
|
|
145
|
+
const name = plugin.NAME
|
|
146
|
+
const JQUERY_NO_CONFLICT = $.fn[name]
|
|
147
|
+
$.fn[name] = plugin.jQueryInterface
|
|
148
|
+
$.fn[name].Constructor = plugin
|
|
149
|
+
$.fn[name].noConflict = () => {
|
|
150
|
+
$.fn[name] = JQUERY_NO_CONFLICT
|
|
151
|
+
return plugin.jQueryInterface
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Execute callback after transition
|
|
158
|
+
*/
|
|
159
|
+
export function executeAfterTransition(callback, transitionElement, waitForTransition = true) {
|
|
160
|
+
if (!waitForTransition) {
|
|
161
|
+
callback()
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const duration = getTransitionDurationFromElement(transitionElement)
|
|
166
|
+
const emulatedDuration = duration + 5
|
|
167
|
+
|
|
168
|
+
let called = false
|
|
169
|
+
|
|
170
|
+
const handler = ({ target }) => {
|
|
171
|
+
if (target !== transitionElement) {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
called = true
|
|
176
|
+
transitionElement.removeEventListener('transitionend', handler)
|
|
177
|
+
callback()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
transitionElement.addEventListener('transitionend', handler)
|
|
181
|
+
setTimeout(() => {
|
|
182
|
+
if (!called) {
|
|
183
|
+
triggerTransitionEnd(transitionElement)
|
|
184
|
+
}
|
|
185
|
+
}, emulatedDuration)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get next active element
|
|
190
|
+
*/
|
|
191
|
+
export function getNextActiveElement(list, activeElement, shouldGetNext, isCycleAllowed) {
|
|
192
|
+
const listLength = list.length
|
|
193
|
+
let index = list.indexOf(activeElement)
|
|
194
|
+
|
|
195
|
+
if (index === -1) {
|
|
196
|
+
return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
index += shouldGetNext ? 1 : -1
|
|
200
|
+
|
|
201
|
+
if (isCycleAllowed) {
|
|
202
|
+
index = (index + listLength) % listLength
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return list[Math.max(0, Math.min(index, listLength - 1))]
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get event type from namespaced event name
|
|
210
|
+
* Example: 'click.bs.button' -> 'click'
|
|
211
|
+
*/
|
|
212
|
+
function getTypeEvent(event) {
|
|
213
|
+
// Remove everything after the first dot (namespace)
|
|
214
|
+
return event.replace(/\..*/, '')
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Event handler helpers
|
|
219
|
+
*/
|
|
220
|
+
export function on(element, event, selectorOrHandler, handler) {
|
|
221
|
+
if (!element) {
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Extract event type from namespaced event name (e.g., 'click.bs.button' -> 'click')
|
|
226
|
+
const typeEvent = getTypeEvent(event)
|
|
227
|
+
|
|
228
|
+
if (typeof selectorOrHandler === 'string') {
|
|
229
|
+
// Delegated event: on(document, 'click', '.selector', handler)
|
|
230
|
+
// Baseline-compatible delegation handler using closest()
|
|
231
|
+
const selector = selectorOrHandler
|
|
232
|
+
const delegationHandler = function(e) {
|
|
233
|
+
// Check if target is an Element (not TextNode, etc.)
|
|
234
|
+
if (!e.target || !e.target.closest) {
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const target = e.target.closest(selector)
|
|
239
|
+
if (target && element.contains(target)) {
|
|
240
|
+
// Set delegateTarget for Baseline-compatibility
|
|
241
|
+
if (!e.delegateTarget) {
|
|
242
|
+
Object.defineProperty(e, 'delegateTarget', {
|
|
243
|
+
value: target,
|
|
244
|
+
writable: false,
|
|
245
|
+
configurable: true
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
// Call handler with target as this context
|
|
249
|
+
return handler.call(target, e)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
element.addEventListener(typeEvent, delegationHandler)
|
|
254
|
+
} else {
|
|
255
|
+
// Direct event: on(element, 'click', handler)
|
|
256
|
+
element.addEventListener(typeEvent, selectorOrHandler)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function off(element, event, handler) {
|
|
261
|
+
if (!element) {
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
element.removeEventListener(event, handler)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function one(element, event, handler) {
|
|
268
|
+
const onceHandler = (e) => {
|
|
269
|
+
handler(e)
|
|
270
|
+
off(element, event, onceHandler)
|
|
271
|
+
}
|
|
272
|
+
on(element, event, onceHandler)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get selector from element (data-c-target or href)
|
|
277
|
+
* @param {Element} element
|
|
278
|
+
* @returns {string|null}
|
|
279
|
+
*/
|
|
280
|
+
function getSelector(element) {
|
|
281
|
+
let selector = element.getAttribute('data-c-target')
|
|
282
|
+
|
|
283
|
+
if (!selector || selector === '#') {
|
|
284
|
+
let hrefAttribute = element.getAttribute('href')
|
|
285
|
+
|
|
286
|
+
// The only valid content that could double as a selector are IDs or classes,
|
|
287
|
+
// so everything starting with `#` or `.`. If a "real" URL is used as the selector,
|
|
288
|
+
// `document.querySelector` will rightfully complain it is invalid.
|
|
289
|
+
// See https://github.com//Baseline/issues/32273
|
|
290
|
+
if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {
|
|
291
|
+
return null
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Just in case some CMS puts out a full URL with the anchor appended
|
|
295
|
+
if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
|
|
296
|
+
hrefAttribute = `#${hrefAttribute.split('#')[1]}`
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get element from selector (Baseline-compatible)
|
|
307
|
+
* Uses data-c-target or href attribute to find target element
|
|
308
|
+
* @param {Element} element
|
|
309
|
+
* @returns {Element|null}
|
|
310
|
+
*/
|
|
311
|
+
export function getElementFromSelector(element) {
|
|
312
|
+
const selector = getSelector(element)
|
|
313
|
+
|
|
314
|
+
return selector ? getElement(selector) : null
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Check if document is in RTL mode
|
|
319
|
+
* @returns {boolean}
|
|
320
|
+
*/
|
|
321
|
+
export function isRTL() {
|
|
322
|
+
return document.documentElement.dir === 'rtl'
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* SelectorEngine helpers for finding elements
|
|
327
|
+
*/
|
|
328
|
+
export const SelectorEngine = {
|
|
329
|
+
/**
|
|
330
|
+
* Find all elements matching selector
|
|
331
|
+
* @param {string} selector
|
|
332
|
+
* @param {Element} element
|
|
333
|
+
* @returns {Array<Element>}
|
|
334
|
+
*/
|
|
335
|
+
find(selector, element = document.documentElement) {
|
|
336
|
+
return Array.from(element.querySelectorAll(selector))
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Find first element matching selector
|
|
341
|
+
* @param {string} selector
|
|
342
|
+
* @param {Element} element
|
|
343
|
+
* @returns {Element|null}
|
|
344
|
+
*/
|
|
345
|
+
findOne(selector, element = document.documentElement) {
|
|
346
|
+
return element.querySelector(selector)
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Find next sibling element matching selector
|
|
351
|
+
* @param {Element} element
|
|
352
|
+
* @param {string} selector
|
|
353
|
+
* @returns {Array<Element>}
|
|
354
|
+
*/
|
|
355
|
+
next(element, selector) {
|
|
356
|
+
let next = element.nextElementSibling
|
|
357
|
+
while (next) {
|
|
358
|
+
if (next.matches(selector)) {
|
|
359
|
+
return [next]
|
|
360
|
+
}
|
|
361
|
+
next = next.nextElementSibling
|
|
362
|
+
}
|
|
363
|
+
return []
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Find previous sibling element matching selector
|
|
368
|
+
* @param {Element} element
|
|
369
|
+
* @param {string} selector
|
|
370
|
+
* @returns {Array<Element>}
|
|
371
|
+
*/
|
|
372
|
+
prev(element, selector) {
|
|
373
|
+
let previous = element.previousElementSibling
|
|
374
|
+
while (previous) {
|
|
375
|
+
if (previous.matches(selector)) {
|
|
376
|
+
return [previous]
|
|
377
|
+
}
|
|
378
|
+
previous = previous.previousElementSibling
|
|
379
|
+
}
|
|
380
|
+
return []
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Find parent elements matching selector
|
|
385
|
+
* @param {Element} element
|
|
386
|
+
* @param {string} selector
|
|
387
|
+
* @returns {Array<Element>}
|
|
388
|
+
*/
|
|
389
|
+
parents(element, selector) {
|
|
390
|
+
const parents = []
|
|
391
|
+
let ancestor = element.parentNode?.closest?.(selector)
|
|
392
|
+
|
|
393
|
+
while (ancestor) {
|
|
394
|
+
parents.push(ancestor)
|
|
395
|
+
ancestor = ancestor.parentNode?.closest?.(selector)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return parents
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Get focusable children of an element
|
|
403
|
+
* @param {Element} element
|
|
404
|
+
* @returns {Array<Element>}
|
|
405
|
+
*/
|
|
406
|
+
focusableChildren(element) {
|
|
407
|
+
const focusables = [
|
|
408
|
+
'a',
|
|
409
|
+
'button',
|
|
410
|
+
'input',
|
|
411
|
+
'textarea',
|
|
412
|
+
'select',
|
|
413
|
+
'details',
|
|
414
|
+
'[tabindex]',
|
|
415
|
+
'[contenteditable="true"]'
|
|
416
|
+
].map(selector => `${selector}:not([tabindex^="-"])`).join(',')
|
|
417
|
+
|
|
418
|
+
return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Get element from selector (from data-c-target or href)
|
|
423
|
+
* @param {Element} element
|
|
424
|
+
* @returns {Element|null}
|
|
425
|
+
*/
|
|
426
|
+
getElementFromSelector(element) {
|
|
427
|
+
return getElementFromSelector(element)
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* EventHandler - Baseline-compatible event handling
|
|
433
|
+
* Wrapper around on/off/one functions for compatibility
|
|
434
|
+
*/
|
|
435
|
+
export const EventHandler = {
|
|
436
|
+
on(element, event, handler, delegationFunction) {
|
|
437
|
+
if (typeof handler === 'string' && delegationFunction) {
|
|
438
|
+
// Delegated event: EventHandler.on(element, event, selector, handler)
|
|
439
|
+
on(element, event, handler, delegationFunction)
|
|
440
|
+
} else if (typeof handler === 'function') {
|
|
441
|
+
// Direct event: EventHandler.on(element, event, handler)
|
|
442
|
+
on(element, event, handler)
|
|
443
|
+
} else if (delegationFunction) {
|
|
444
|
+
// Direct event with delegationFunction as handler
|
|
445
|
+
on(element, event, delegationFunction)
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
off(element, event, handler, delegationFunction) {
|
|
450
|
+
// Simplified - in full implementation would track handlers
|
|
451
|
+
if (handler) {
|
|
452
|
+
off(element, event, handler)
|
|
453
|
+
} else if (delegationFunction) {
|
|
454
|
+
off(element, event, delegationFunction)
|
|
455
|
+
}
|
|
456
|
+
// Note: Full implementation would need to track all handlers for complete removal
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
one(element, event, handler, delegationFunction) {
|
|
460
|
+
if (typeof handler === 'string' && delegationFunction) {
|
|
461
|
+
// Delegated event - use one-time handler
|
|
462
|
+
const onceHandler = (e) => {
|
|
463
|
+
delegationFunction.call(e.target, e)
|
|
464
|
+
EventHandler.off(element, event, handler, onceHandler)
|
|
465
|
+
}
|
|
466
|
+
on(element, event, handler, onceHandler)
|
|
467
|
+
} else {
|
|
468
|
+
one(element, event, handler || delegationFunction)
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
trigger(element, event, args = {}) {
|
|
473
|
+
if (!element) {
|
|
474
|
+
return null
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const evt = new CustomEvent(event, {
|
|
478
|
+
bubbles: true,
|
|
479
|
+
cancelable: true,
|
|
480
|
+
detail: args
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
// Add properties from args to event object
|
|
484
|
+
if (args && typeof args === 'object') {
|
|
485
|
+
Object.keys(args).forEach(key => {
|
|
486
|
+
try {
|
|
487
|
+
evt[key] = args[key]
|
|
488
|
+
} catch {
|
|
489
|
+
// Ignore read-only properties
|
|
490
|
+
}
|
|
491
|
+
})
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
element.dispatchEvent(evt)
|
|
495
|
+
return evt
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Normalize data attribute value
|
|
501
|
+
* Converts string values to appropriate types (boolean, number, null, JSON)
|
|
502
|
+
*/
|
|
503
|
+
function normalizeData(value) {
|
|
504
|
+
if (value === 'true') {
|
|
505
|
+
return true
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (value === 'false') {
|
|
509
|
+
return false
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (value === Number(value).toString()) {
|
|
513
|
+
return Number(value)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (value === '' || value === 'null') {
|
|
517
|
+
return null
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (typeof value !== 'string') {
|
|
521
|
+
return value
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
try {
|
|
525
|
+
return JSON.parse(decodeURIComponent(value))
|
|
526
|
+
} catch {
|
|
527
|
+
return value
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Get data attributes from element
|
|
533
|
+
* Reads both data-c-* and data-bl-* attributes and converts them to camelCase
|
|
534
|
+
* @param {Element} element - The element to read attributes from
|
|
535
|
+
* @returns {Object} Object with camelCase keys and normalized values
|
|
536
|
+
*/
|
|
537
|
+
export function getDataAttributes(element) {
|
|
538
|
+
if (!element || !isElement(element)) {
|
|
539
|
+
return {}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const attributes = {}
|
|
543
|
+
|
|
544
|
+
// Get all dataset keys that start with 'c' or 'bs' (but not 'bsConfig')
|
|
545
|
+
const dataKeys = Object.keys(element.dataset).filter(key =>
|
|
546
|
+
(key.startsWith('c') || key.startsWith('bs')) && !key.startsWith('bsConfig')
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
for (const key of dataKeys) {
|
|
550
|
+
// Remove prefix (c or bs) and convert to camelCase
|
|
551
|
+
let pureKey = key.replace(/^(c|bs)/, '')
|
|
552
|
+
// Convert first character to lowercase if it exists
|
|
553
|
+
if (pureKey.length > 0) {
|
|
554
|
+
pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Normalize the value (convert strings to booleans, numbers, null, etc.)
|
|
558
|
+
attributes[pureKey] = normalizeData(element.dataset[key])
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return attributes
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rhavenside/baseline",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Ein striktes, schichtbasiertes Design-System mit semantischen Tokens",
|
|
5
|
+
"main": "dist/base-line.css",
|
|
6
|
+
"module": "dist/js/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/js/index.js",
|
|
10
|
+
"require": "./dist/js/index.js",
|
|
11
|
+
"default": "./dist/base-line.css"
|
|
12
|
+
},
|
|
13
|
+
"./css": "./dist/base-line.css",
|
|
14
|
+
"./js": "./dist/js/index.js",
|
|
15
|
+
"./fonts/*": "./dist/fonts/*"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "npm run build:css && npm run build:js && npm run copy:fonts",
|
|
22
|
+
"build:css": "sass src/base.scss dist/base-line.css --style=expanded && npm run fix:font-paths",
|
|
23
|
+
"build:css:compressed": "sass src/base.scss dist/base-line.css --style=compressed && npm run fix:font-paths",
|
|
24
|
+
"build:js": "mkdir -p dist/js && cp -r src/js/* dist/js/",
|
|
25
|
+
"copy:fonts": "mkdir -p dist/fonts && cp -f src/fonts/*.ttf dist/fonts/ 2>/dev/null || true && (test -f dist/fonts/baseline-icons.woff || (test -f demo/dist/fonts/baseline-icons.woff && cp demo/dist/fonts/baseline-icons.woff* dist/fonts/)) || true",
|
|
26
|
+
"fix:font-paths": "node scripts/fix-font-paths.js",
|
|
27
|
+
"rename:fonts": "node scripts/rename-fonts.js",
|
|
28
|
+
"rename:icons": "node scripts/rename-baseline-icons.js",
|
|
29
|
+
"watch": "sass --watch src/base.scss dist/base-line.css --style=expanded",
|
|
30
|
+
"prepublishOnly": "npm run build:css:compressed && npm run build:js && npm run copy:fonts"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"design-system",
|
|
34
|
+
"css",
|
|
35
|
+
"scss",
|
|
36
|
+
"tokens",
|
|
37
|
+
"components",
|
|
38
|
+
"baseline",
|
|
39
|
+
"baseline"
|
|
40
|
+
],
|
|
41
|
+
"author": "",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@popperjs/core": "^2.11.8",
|
|
45
|
+
"sass": "^1.77.0"
|
|
46
|
+
}
|
|
47
|
+
}
|