@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
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base-Line Offcanvas Component
|
|
3
|
+
* Baseline 2.0 compatible offcanvas with Base-Line naming
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BaseComponent } from './base.js'
|
|
7
|
+
import { getElement, getElements, on, off, one, executeAfterTransition, reflow, getElementFromSelector, isDisabled, isVisible } from './util.js'
|
|
8
|
+
import { addClass, removeClass, hasClass } from './component.js'
|
|
9
|
+
import { enableDismissTrigger } from './util/component-functions.js'
|
|
10
|
+
|
|
11
|
+
const NAME = 'offcanvas'
|
|
12
|
+
const DATA_KEY = `c.${NAME}`
|
|
13
|
+
const EVENT_KEY = `.${DATA_KEY}`
|
|
14
|
+
const DATA_API_KEY = '.data-api'
|
|
15
|
+
|
|
16
|
+
const EVENT_SHOW = `show${EVENT_KEY}`
|
|
17
|
+
const EVENT_SHOWN = `shown${EVENT_KEY}`
|
|
18
|
+
const EVENT_HIDE = `hide${EVENT_KEY}`
|
|
19
|
+
const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`
|
|
20
|
+
const EVENT_HIDDEN = `hidden${EVENT_KEY}`
|
|
21
|
+
const EVENT_RESIZE = `resize${EVENT_KEY}`
|
|
22
|
+
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
|
|
23
|
+
const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`
|
|
24
|
+
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`
|
|
25
|
+
|
|
26
|
+
const ESCAPE_KEY = 'Escape'
|
|
27
|
+
|
|
28
|
+
const CLASS_NAME_SHOW = 'show'
|
|
29
|
+
const CLASS_NAME_SHOWING = 'showing'
|
|
30
|
+
const CLASS_NAME_HIDING = 'hiding'
|
|
31
|
+
const CLASS_NAME_BACKDROP = 'c-offcanvas-backdrop'
|
|
32
|
+
const OPEN_SELECTOR = '.c-offcanvas.show'
|
|
33
|
+
|
|
34
|
+
const SELECTOR_DATA_TOGGLE = '[data-c-toggle="offcanvas"]'
|
|
35
|
+
const SELECTOR_DATA_DISMISS = '[data-c-dismiss="offcanvas"]'
|
|
36
|
+
|
|
37
|
+
const Default = {
|
|
38
|
+
backdrop: true,
|
|
39
|
+
keyboard: true,
|
|
40
|
+
scroll: false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class Offcanvas extends BaseComponent {
|
|
44
|
+
constructor(element, config) {
|
|
45
|
+
super(element, config)
|
|
46
|
+
// Sync initial state with DOM - check if element already has 'show' class
|
|
47
|
+
const hasShowClass = element.classList.contains(CLASS_NAME_SHOW)
|
|
48
|
+
this._isShown = hasShowClass
|
|
49
|
+
|
|
50
|
+
// If element already has 'show' class, check if there's an existing backdrop in the DOM
|
|
51
|
+
if (hasShowClass) {
|
|
52
|
+
const existingBackdrop = element.parentElement?.querySelector(`.${CLASS_NAME_BACKDROP}`)
|
|
53
|
+
if (existingBackdrop) {
|
|
54
|
+
// Create backdrop instance but reuse the existing DOM element
|
|
55
|
+
this._backdrop = this._initializeBackDrop()
|
|
56
|
+
// Manually set the backdrop element to the existing one
|
|
57
|
+
this._backdrop._element = existingBackdrop
|
|
58
|
+
} else {
|
|
59
|
+
this._backdrop = this._initializeBackDrop()
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
this._backdrop = this._initializeBackDrop()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this._addEventListeners()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_configAfterMerge(config) {
|
|
69
|
+
// Read data attributes
|
|
70
|
+
const scroll = this._element.getAttribute('data-c-scroll')
|
|
71
|
+
const backdrop = this._element.getAttribute('data-c-backdrop')
|
|
72
|
+
|
|
73
|
+
if (scroll !== null) {
|
|
74
|
+
config.scroll = scroll === 'true'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (backdrop !== null) {
|
|
78
|
+
config.backdrop = backdrop === 'true' ? true : backdrop === 'false' ? false : backdrop
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return config
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static get NAME() {
|
|
85
|
+
return NAME
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static get Default() {
|
|
89
|
+
return Default
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Public
|
|
93
|
+
toggle(relatedTarget) {
|
|
94
|
+
return this._isShown ? this.hide() : this.show(relatedTarget)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
show(relatedTarget) {
|
|
98
|
+
if (this._isShown) {
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const showEvent = new CustomEvent(EVENT_SHOW, {
|
|
103
|
+
bubbles: true,
|
|
104
|
+
cancelable: true,
|
|
105
|
+
detail: { relatedTarget }
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
this._element.dispatchEvent(showEvent)
|
|
109
|
+
|
|
110
|
+
if (showEvent.defaultPrevented) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this._isShown = true
|
|
115
|
+
this._backdrop.show()
|
|
116
|
+
|
|
117
|
+
if (!this._config.scroll) {
|
|
118
|
+
document.body.style.overflow = 'hidden'
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this._element.setAttribute('aria-modal', 'true')
|
|
122
|
+
this._element.setAttribute('role', 'dialog')
|
|
123
|
+
addClass(this._element, CLASS_NAME_SHOWING)
|
|
124
|
+
|
|
125
|
+
const completeCallback = () => {
|
|
126
|
+
addClass(this._element, CLASS_NAME_SHOW)
|
|
127
|
+
removeClass(this._element, CLASS_NAME_SHOWING)
|
|
128
|
+
const shownEvent = new CustomEvent(EVENT_SHOWN, {
|
|
129
|
+
bubbles: true,
|
|
130
|
+
detail: { relatedTarget }
|
|
131
|
+
})
|
|
132
|
+
this._element.dispatchEvent(shownEvent)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this._queueCallback(completeCallback, this._element, true)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
hide() {
|
|
139
|
+
// Check if element has 'show' class even if _isShown is false (state might be out of sync)
|
|
140
|
+
const hasShowClass = this._element.classList.contains(CLASS_NAME_SHOW)
|
|
141
|
+
|
|
142
|
+
if (!this._isShown && !hasShowClass) {
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// If element has show class but _isShown is false, sync the state
|
|
147
|
+
if (!this._isShown && hasShowClass) {
|
|
148
|
+
this._isShown = true
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const hideEvent = new CustomEvent(EVENT_HIDE, {
|
|
152
|
+
bubbles: true,
|
|
153
|
+
cancelable: true
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
this._element.dispatchEvent(hideEvent)
|
|
157
|
+
|
|
158
|
+
if (hideEvent.defaultPrevented) {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this._element.blur()
|
|
163
|
+
this._isShown = false
|
|
164
|
+
addClass(this._element, CLASS_NAME_HIDING)
|
|
165
|
+
if (this._backdrop && typeof this._backdrop.hide === 'function') {
|
|
166
|
+
this._backdrop.hide()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const completeCallback = () => {
|
|
170
|
+
removeClass(this._element, CLASS_NAME_SHOW)
|
|
171
|
+
removeClass(this._element, CLASS_NAME_HIDING)
|
|
172
|
+
this._element.removeAttribute('aria-modal')
|
|
173
|
+
this._element.removeAttribute('role')
|
|
174
|
+
|
|
175
|
+
if (!this._config.scroll) {
|
|
176
|
+
document.body.style.overflow = ''
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const hiddenEvent = new CustomEvent(EVENT_HIDDEN, {
|
|
180
|
+
bubbles: true
|
|
181
|
+
})
|
|
182
|
+
this._element.dispatchEvent(hiddenEvent)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this._queueCallback(completeCallback, this._element, true)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
dispose() {
|
|
189
|
+
this._backdrop.dispose()
|
|
190
|
+
super.dispose()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Private
|
|
194
|
+
_initializeBackDrop() {
|
|
195
|
+
const clickCallback = () => {
|
|
196
|
+
if (this._config.backdrop === 'static') {
|
|
197
|
+
const hidePreventedEvent = new CustomEvent(EVENT_HIDE_PREVENTED, {
|
|
198
|
+
bubbles: true
|
|
199
|
+
})
|
|
200
|
+
this._element.dispatchEvent(hidePreventedEvent)
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.hide()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const isVisible = Boolean(this._config.backdrop)
|
|
208
|
+
|
|
209
|
+
return new Backdrop({
|
|
210
|
+
className: CLASS_NAME_BACKDROP,
|
|
211
|
+
isVisible,
|
|
212
|
+
isAnimated: true,
|
|
213
|
+
rootElement: this._element.parentNode,
|
|
214
|
+
clickCallback: isVisible ? clickCallback : null
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
_addEventListeners() {
|
|
219
|
+
on(this._element, EVENT_KEYDOWN_DISMISS, (event) => {
|
|
220
|
+
if (event.key !== ESCAPE_KEY) {
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (this._config.keyboard) {
|
|
225
|
+
this.hide()
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const hidePreventedEvent = new CustomEvent(EVENT_HIDE_PREVENTED, {
|
|
230
|
+
bubbles: true
|
|
231
|
+
})
|
|
232
|
+
this._element.dispatchEvent(hidePreventedEvent)
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
static getOrCreateInstance(element, config = {}) {
|
|
237
|
+
if (element._baseLineComponent) {
|
|
238
|
+
// Sync state with DOM if instance already exists
|
|
239
|
+
const hasShowClass = element.classList.contains(CLASS_NAME_SHOW)
|
|
240
|
+
if (hasShowClass && !element._baseLineComponent._isShown) {
|
|
241
|
+
element._baseLineComponent._isShown = true
|
|
242
|
+
} else if (!hasShowClass && element._baseLineComponent._isShown) {
|
|
243
|
+
element._baseLineComponent._isShown = false
|
|
244
|
+
}
|
|
245
|
+
return element._baseLineComponent
|
|
246
|
+
}
|
|
247
|
+
return new Offcanvas(element, config)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Backdrop helper class
|
|
252
|
+
class Backdrop {
|
|
253
|
+
constructor(config) {
|
|
254
|
+
this._config = config
|
|
255
|
+
this._element = null
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
show() {
|
|
259
|
+
if (!this._config.isVisible) {
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!this._element) {
|
|
264
|
+
this._element = document.createElement('div')
|
|
265
|
+
this._element.className = `${this._config.className} c-fade`
|
|
266
|
+
if (this._config.rootElement) {
|
|
267
|
+
this._config.rootElement.appendChild(this._element)
|
|
268
|
+
} else {
|
|
269
|
+
document.body.appendChild(this._element)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
reflow(this._element)
|
|
274
|
+
|
|
275
|
+
if (this._config.clickCallback) {
|
|
276
|
+
on(this._element, 'click', this._config.clickCallback)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
addClass(this._element, 'show')
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
hide() {
|
|
283
|
+
if (!this._element) {
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
removeClass(this._element, 'show')
|
|
288
|
+
|
|
289
|
+
if (this._config.isAnimated) {
|
|
290
|
+
executeAfterTransition(() => {
|
|
291
|
+
this.dispose()
|
|
292
|
+
}, this._element)
|
|
293
|
+
} else {
|
|
294
|
+
this.dispose()
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
dispose() {
|
|
299
|
+
if (this._element) {
|
|
300
|
+
this._element.remove()
|
|
301
|
+
this._element = null
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Data API implementation
|
|
307
|
+
on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
|
|
308
|
+
const target = getElementFromSelector(this)
|
|
309
|
+
|
|
310
|
+
if (['A', 'AREA'].includes(this.tagName)) {
|
|
311
|
+
event.preventDefault()
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (isDisabled(this)) {
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
one(target, EVENT_HIDDEN, () => {
|
|
319
|
+
// focus on trigger when it is closed
|
|
320
|
+
if (isVisible(this)) {
|
|
321
|
+
this.focus()
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
// avoid conflict when clicking a toggler of an offcanvas, while another is open
|
|
326
|
+
const alreadyOpen = getElement(OPEN_SELECTOR)
|
|
327
|
+
if (alreadyOpen && alreadyOpen !== target) {
|
|
328
|
+
Offcanvas.getOrCreateInstance(alreadyOpen).hide()
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const data = Offcanvas.getOrCreateInstance(target)
|
|
332
|
+
data.toggle(this)
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
// Initialize on load
|
|
336
|
+
on(window, EVENT_LOAD_DATA_API, () => {
|
|
337
|
+
const elements = getElements(OPEN_SELECTOR)
|
|
338
|
+
for (const element of elements) {
|
|
339
|
+
Offcanvas.getOrCreateInstance(element).show()
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
// Handle resize
|
|
344
|
+
on(window, EVENT_RESIZE, () => {
|
|
345
|
+
const elements = getElements('[aria-modal][class*=show][class*=c-offcanvas-]')
|
|
346
|
+
for (const element of elements) {
|
|
347
|
+
if (window.getComputedStyle(element).position !== 'fixed') {
|
|
348
|
+
Offcanvas.getOrCreateInstance(element).hide()
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
// Enable dismiss trigger
|
|
354
|
+
enableDismissTrigger(Offcanvas)
|
|
355
|
+
|
|
356
|
+
export default Offcanvas
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base-Line Popover Component
|
|
3
|
+
* Baseline 2.0 compatible popover with Base-Line naming
|
|
4
|
+
* Based on Baseline-main architecture
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Tooltip from './tooltip.js'
|
|
8
|
+
|
|
9
|
+
const NAME = 'popover'
|
|
10
|
+
const DATA_KEY = `c.${NAME}`
|
|
11
|
+
const EVENT_KEY = `.${DATA_KEY}`
|
|
12
|
+
|
|
13
|
+
const SELECTOR_TITLE = '.c-popover-header'
|
|
14
|
+
const SELECTOR_CONTENT = '.c-popover-body'
|
|
15
|
+
|
|
16
|
+
class Popover extends Tooltip {
|
|
17
|
+
constructor(element, config) {
|
|
18
|
+
super(element, {
|
|
19
|
+
...config,
|
|
20
|
+
template: '<div class="c-popover" role="tooltip"><div class="c-popover-arrow"></div><h3 class="c-popover-header"></h3><div class="c-popover-body"></div></div>'
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static get NAME() {
|
|
25
|
+
return NAME
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Overrides
|
|
29
|
+
_isWithContent() {
|
|
30
|
+
return Boolean(this._getTitle() || this._getContent())
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_getContentForTemplate() {
|
|
34
|
+
return {
|
|
35
|
+
[SELECTOR_TITLE]: this._getTitle(),
|
|
36
|
+
[SELECTOR_CONTENT]: this._getContent()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_copyCustomStyles(tip) {
|
|
41
|
+
// Copy CSS variables from trigger element to popover
|
|
42
|
+
// This allows custom styling via CSS variables on the trigger element
|
|
43
|
+
const elementStyle = this._element.style
|
|
44
|
+
const computedStyle = window.getComputedStyle(this._element)
|
|
45
|
+
const popoverStyles = tip.style
|
|
46
|
+
const header = tip.querySelector(SELECTOR_TITLE)
|
|
47
|
+
const body = tip.querySelector(SELECTOR_CONTENT)
|
|
48
|
+
|
|
49
|
+
// Get all CSS variables that start with --bl-popover-
|
|
50
|
+
// First check inline styles (for React style props)
|
|
51
|
+
const cssVariables = new Set()
|
|
52
|
+
|
|
53
|
+
// Check inline styles
|
|
54
|
+
for (let i = 0; i < elementStyle.length; i++) {
|
|
55
|
+
const property = elementStyle[i]
|
|
56
|
+
if (property.startsWith('--bl-popover-')) {
|
|
57
|
+
cssVariables.add(property)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Also check computed styles (for CSS classes)
|
|
62
|
+
for (let i = 0; i < computedStyle.length; i++) {
|
|
63
|
+
const property = computedStyle[i]
|
|
64
|
+
if (property.startsWith('--bl-popover-')) {
|
|
65
|
+
cssVariables.add(property)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Copy each CSS variable to the popover element
|
|
70
|
+
// Inline styles have higher specificity than CSS classes, so they will override dark mode
|
|
71
|
+
cssVariables.forEach(variable => {
|
|
72
|
+
// Prefer inline style value, fallback to computed style
|
|
73
|
+
const value = elementStyle.getPropertyValue(variable) || computedStyle.getPropertyValue(variable)
|
|
74
|
+
if (value) {
|
|
75
|
+
// Set on popover element - CSS variables will be inherited by header and body
|
|
76
|
+
popoverStyles.setProperty(variable, value)
|
|
77
|
+
|
|
78
|
+
// Also set directly on header/body if they are header/body specific variables
|
|
79
|
+
// This ensures they override dark mode styles even if inheritance doesn't work
|
|
80
|
+
if (variable.includes('header') && header) {
|
|
81
|
+
header.style.setProperty(variable, value)
|
|
82
|
+
} else if (variable.includes('body') && body) {
|
|
83
|
+
body.style.setProperty(variable, value)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
_createTipElement(content) {
|
|
90
|
+
const tip = super._createTipElement(content)
|
|
91
|
+
|
|
92
|
+
// Copy custom CSS variables from trigger element
|
|
93
|
+
this._copyCustomStyles(tip)
|
|
94
|
+
|
|
95
|
+
// Set content for popover header and body
|
|
96
|
+
if (content && typeof content === 'object') {
|
|
97
|
+
const header = tip.querySelector(SELECTOR_TITLE)
|
|
98
|
+
const body = tip.querySelector(SELECTOR_CONTENT)
|
|
99
|
+
|
|
100
|
+
if (header) {
|
|
101
|
+
const titleContent = content[SELECTOR_TITLE] || ''
|
|
102
|
+
if (titleContent) {
|
|
103
|
+
if (this._config.html) {
|
|
104
|
+
header.innerHTML = titleContent
|
|
105
|
+
} else {
|
|
106
|
+
header.textContent = titleContent
|
|
107
|
+
}
|
|
108
|
+
header.style.display = ''
|
|
109
|
+
} else {
|
|
110
|
+
header.style.display = 'none'
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (body) {
|
|
115
|
+
const bodyContent = content[SELECTOR_CONTENT] || ''
|
|
116
|
+
if (this._config.html) {
|
|
117
|
+
body.innerHTML = bodyContent
|
|
118
|
+
} else {
|
|
119
|
+
body.textContent = bodyContent
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return tip
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
_getTitle() {
|
|
128
|
+
return this._resolvePossibleFunction(this._config.title) ||
|
|
129
|
+
this._element.getAttribute('data-c-title') ||
|
|
130
|
+
this._element.getAttribute('data-bl-title') ||
|
|
131
|
+
super._getTitle() ||
|
|
132
|
+
''
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
_getContent() {
|
|
136
|
+
return this._resolvePossibleFunction(this._config.content) ||
|
|
137
|
+
this._element.getAttribute('data-c-content') ||
|
|
138
|
+
this._element.getAttribute('data-bl-content') ||
|
|
139
|
+
this._element.getAttribute('data-c-body') ||
|
|
140
|
+
''
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
show() {
|
|
144
|
+
super.show()
|
|
145
|
+
|
|
146
|
+
// Ensure custom CSS variables are applied after showing
|
|
147
|
+
// This ensures they override dark mode styles
|
|
148
|
+
// Use requestAnimationFrame to ensure DOM is ready
|
|
149
|
+
if (this.tip) {
|
|
150
|
+
requestAnimationFrame(() => {
|
|
151
|
+
this._copyCustomStyles(this.tip)
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
setContent(content) {
|
|
157
|
+
this._newContent = typeof content === 'object' ? content : {
|
|
158
|
+
[SELECTOR_TITLE]: this._getTitle(),
|
|
159
|
+
[SELECTOR_CONTENT]: content
|
|
160
|
+
}
|
|
161
|
+
if (this._isShown()) {
|
|
162
|
+
this._disposePopper()
|
|
163
|
+
this.show()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static get Default() {
|
|
168
|
+
return {
|
|
169
|
+
...Tooltip.Default,
|
|
170
|
+
content: '',
|
|
171
|
+
offset: [0, 8],
|
|
172
|
+
placement: 'right',
|
|
173
|
+
template: '<div class="c-popover" role="tooltip"><div class="c-popover-arrow"></div><h3 class="c-popover-header"></h3><div class="c-popover-body"></div></div>',
|
|
174
|
+
trigger: 'click'
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
static get DefaultType() {
|
|
179
|
+
return {
|
|
180
|
+
...Tooltip.DefaultType,
|
|
181
|
+
content: '(null|string|element|function)'
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static getOrCreateInstance(element, config = {}) {
|
|
186
|
+
if (!element._baseLineComponent) {
|
|
187
|
+
element._baseLineComponent = new Popover(element, config)
|
|
188
|
+
}
|
|
189
|
+
return element._baseLineComponent
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Data API implementation
|
|
195
|
+
*/
|
|
196
|
+
|
|
197
|
+
// Support both data-c-toggle="popover" and data-bl-toggle="popover"
|
|
198
|
+
const SELECTOR_DATA_TOGGLE = '[data-c-toggle="popover"]'
|
|
199
|
+
const SELECTOR_DATA_TOGGLE_BS = '[data-bl-toggle="popover"]'
|
|
200
|
+
|
|
201
|
+
// Initialize popovers on DOM ready
|
|
202
|
+
import { onDOMContentLoaded } from './util.js'
|
|
203
|
+
|
|
204
|
+
onDOMContentLoaded(() => {
|
|
205
|
+
const popoverElements = document.querySelectorAll(`${SELECTOR_DATA_TOGGLE}, ${SELECTOR_DATA_TOGGLE_BS}`)
|
|
206
|
+
popoverElements.forEach(element => {
|
|
207
|
+
if (!element._baseLineComponent) {
|
|
208
|
+
const title = element.getAttribute('data-c-title') ||
|
|
209
|
+
element.getAttribute('data-bl-title') ||
|
|
210
|
+
element.getAttribute('title')
|
|
211
|
+
const content = element.getAttribute('data-c-content') ||
|
|
212
|
+
element.getAttribute('data-bl-content')
|
|
213
|
+
const placement = element.getAttribute('data-c-placement') ||
|
|
214
|
+
element.getAttribute('data-bl-placement') ||
|
|
215
|
+
'right'
|
|
216
|
+
const container = element.getAttribute('data-c-container') ||
|
|
217
|
+
element.getAttribute('data-bl-container') ||
|
|
218
|
+
false
|
|
219
|
+
const html = element.getAttribute('data-c-html') === 'true' ||
|
|
220
|
+
element.getAttribute('data-bl-html') === 'true' ||
|
|
221
|
+
false
|
|
222
|
+
const trigger = element.getAttribute('data-c-trigger') ||
|
|
223
|
+
element.getAttribute('data-bl-trigger') ||
|
|
224
|
+
'click'
|
|
225
|
+
|
|
226
|
+
if (title || content) {
|
|
227
|
+
Popover.getOrCreateInstance(element, {
|
|
228
|
+
title: title,
|
|
229
|
+
content: content,
|
|
230
|
+
placement: placement,
|
|
231
|
+
container: container,
|
|
232
|
+
html: html,
|
|
233
|
+
trigger: trigger
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
export default Popover
|
|
241
|
+
|