Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/formats/html/bslib/components/dist/components.js
12924 views
1
/*! bslib 0.5.1.9000 | (c) 2012-2023 RStudio, PBC. | License: MIT + file LICENSE */
2
"use strict";
3
(() => {
4
// srcts/src/components/_utils.ts
5
var InputBinding = window.Shiny ? Shiny.InputBinding : class {
6
};
7
function registerBinding(inputBindingClass, name) {
8
if (window.Shiny) {
9
Shiny.inputBindings.register(new inputBindingClass(), "bslib." + name);
10
}
11
}
12
function hasDefinedProperty(obj, prop) {
13
return Object.prototype.hasOwnProperty.call(obj, prop) && obj[prop] !== void 0;
14
}
15
function getAllFocusableChildren(el) {
16
const base = [
17
"a[href]",
18
"area[href]",
19
"button",
20
"details summary",
21
"input",
22
"iframe",
23
"select",
24
"textarea",
25
'[contentEditable=""]',
26
'[contentEditable="true"]',
27
'[contentEditable="TRUE"]',
28
"[tabindex]"
29
];
30
const modifiers = [':not([tabindex="-1"])', ":not([disabled])"];
31
const selectors = base.map((b) => b + modifiers.join(""));
32
const focusable = el.querySelectorAll(selectors.join(", "));
33
return Array.from(focusable);
34
}
35
36
// srcts/src/components/accordion.ts
37
var AccordionInputBinding = class extends InputBinding {
38
find(scope) {
39
return $(scope).find(".accordion.bslib-accordion-input");
40
}
41
getValue(el) {
42
const items = this._getItemInfo(el);
43
const selected = items.filter((x) => x.isOpen()).map((x) => x.value);
44
return selected.length === 0 ? null : selected;
45
}
46
subscribe(el, callback) {
47
$(el).on(
48
"shown.bs.collapse.accordionInputBinding hidden.bs.collapse.accordionInputBinding",
49
// eslint-disable-next-line @typescript-eslint/no-unused-vars
50
function(event) {
51
callback(true);
52
}
53
);
54
}
55
unsubscribe(el) {
56
$(el).off(".accordionInputBinding");
57
}
58
receiveMessage(el, data) {
59
const method = data.method;
60
if (method === "set") {
61
this._setItems(el, data);
62
} else if (method === "open") {
63
this._openItems(el, data);
64
} else if (method === "close") {
65
this._closeItems(el, data);
66
} else if (method === "remove") {
67
this._removeItem(el, data);
68
} else if (method === "insert") {
69
this._insertItem(el, data);
70
} else if (method === "update") {
71
this._updateItem(el, data);
72
} else {
73
throw new Error(`Method not yet implemented: ${method}`);
74
}
75
}
76
_setItems(el, data) {
77
const items = this._getItemInfo(el);
78
const vals = this._getValues(el, items, data.values);
79
items.forEach((x) => {
80
vals.indexOf(x.value) > -1 ? x.show() : x.hide();
81
});
82
}
83
_openItems(el, data) {
84
const items = this._getItemInfo(el);
85
const vals = this._getValues(el, items, data.values);
86
items.forEach((x) => {
87
if (vals.indexOf(x.value) > -1)
88
x.show();
89
});
90
}
91
_closeItems(el, data) {
92
const items = this._getItemInfo(el);
93
const vals = this._getValues(el, items, data.values);
94
items.forEach((x) => {
95
if (vals.indexOf(x.value) > -1)
96
x.hide();
97
});
98
}
99
_insertItem(el, data) {
100
let targetItem = this._findItem(el, data.target);
101
if (!targetItem) {
102
targetItem = data.position === "before" ? el.firstElementChild : el.lastElementChild;
103
}
104
const panel = data.panel;
105
if (targetItem) {
106
Shiny.renderContent(
107
targetItem,
108
panel,
109
data.position === "before" ? "beforeBegin" : "afterEnd"
110
);
111
} else {
112
Shiny.renderContent(el, panel);
113
}
114
if (this._isAutoClosing(el)) {
115
const val = $(panel.html).attr("data-value");
116
$(el).find(`[data-value="${val}"] .accordion-collapse`).attr("data-bs-parent", "#" + el.id);
117
}
118
}
119
_removeItem(el, data) {
120
const targetItems = this._getItemInfo(el).filter(
121
(x) => data.target.indexOf(x.value) > -1
122
);
123
const unbindAll = Shiny == null ? void 0 : Shiny.unbindAll;
124
targetItems.forEach((x) => {
125
if (unbindAll)
126
unbindAll(x.item);
127
x.item.remove();
128
});
129
}
130
_updateItem(el, data) {
131
const target = this._findItem(el, data.target);
132
if (!target) {
133
throw new Error(
134
`Unable to find an accordion_panel() with a value of ${data.target}`
135
);
136
}
137
if (hasDefinedProperty(data, "value")) {
138
target.dataset.value = data.value;
139
}
140
if (hasDefinedProperty(data, "body")) {
141
const body = target.querySelector(".accordion-body");
142
Shiny.renderContent(body, data.body);
143
}
144
const header = target.querySelector(".accordion-header");
145
if (hasDefinedProperty(data, "title")) {
146
const title = header.querySelector(".accordion-title");
147
Shiny.renderContent(title, data.title);
148
}
149
if (hasDefinedProperty(data, "icon")) {
150
const icon = header.querySelector(
151
".accordion-button > .accordion-icon"
152
);
153
Shiny.renderContent(icon, data.icon);
154
}
155
}
156
_getItemInfo(el) {
157
const items = Array.from(
158
el.querySelectorAll(":scope > .accordion-item")
159
);
160
return items.map((x) => this._getSingleItemInfo(x));
161
}
162
_getSingleItemInfo(x) {
163
const collapse = x.querySelector(".accordion-collapse");
164
const isOpen = () => $(collapse).hasClass("show");
165
return {
166
item: x,
167
value: x.dataset.value,
168
isOpen,
169
show: () => {
170
if (!isOpen())
171
$(collapse).collapse("show");
172
},
173
hide: () => {
174
if (isOpen())
175
$(collapse).collapse("hide");
176
}
177
};
178
}
179
_getValues(el, items, values) {
180
let vals = values !== true ? values : items.map((x) => x.value);
181
const autoclose = this._isAutoClosing(el);
182
if (autoclose) {
183
vals = vals.slice(vals.length - 1, vals.length);
184
}
185
return vals;
186
}
187
_findItem(el, value) {
188
return el.querySelector(`[data-value="${value}"]`);
189
}
190
_isAutoClosing(el) {
191
return el.classList.contains("autoclose");
192
}
193
};
194
registerBinding(AccordionInputBinding, "accordion");
195
196
// srcts/src/components/_shinyResizeObserver.ts
197
var ShinyResizeObserver = class {
198
/**
199
* Watch containers for size changes and ensure that Shiny outputs and
200
* htmlwidgets within resize appropriately.
201
*
202
* @details
203
* The ShinyResizeObserver is used to watch the containers, such as Sidebars
204
* and Cards for size changes, in particular when the sidebar state is toggled
205
* or the card body is expanded full screen. It performs two primary tasks:
206
*
207
* 1. Dispatches a `resize` event on the window object. This is necessary to
208
* ensure that Shiny outputs resize appropriately. In general, the window
209
* resizing is throttled and the output update occurs when the transition
210
* is complete.
211
* 2. If an output with a resize method on the output binding is detected, we
212
* directly call the `.onResize()` method of the binding. This ensures that
213
* htmlwidgets transition smoothly. In static mode, htmlwidgets does this
214
* already.
215
*
216
* @note
217
* This resize observer also handles race conditions in some complex
218
* fill-based layouts with multiple outputs (e.g., plotly), where shiny
219
* initializes with the correct sizing, but in-between the 1st and last
220
* renderValue(), the size of the output containers can change, meaning every
221
* output but the 1st gets initialized with the wrong size during their
222
* renderValue(). Then, after the render phase, shiny won't know to trigger a
223
* resize since all the widgets will return to their original size (and thus,
224
* Shiny thinks there isn't any resizing to do). The resize observer works
225
* around this by ensuring that the output is resized whenever its container
226
* size changes.
227
* @constructor
228
*/
229
constructor() {
230
this.resizeObserverEntries = [];
231
this.resizeObserver = new ResizeObserver((entries) => {
232
const resizeEvent = new Event("resize");
233
window.dispatchEvent(resizeEvent);
234
if (!window.Shiny)
235
return;
236
const resized = [];
237
for (const entry of entries) {
238
if (!(entry.target instanceof HTMLElement))
239
continue;
240
if (!entry.target.querySelector(".shiny-bound-output"))
241
continue;
242
entry.target.querySelectorAll(".shiny-bound-output").forEach((el) => {
243
if (resized.includes(el))
244
return;
245
const { binding, onResize } = $(el).data("shinyOutputBinding");
246
if (!binding || !binding.resize)
247
return;
248
const owner = el.shinyResizeObserver;
249
if (owner && owner !== this)
250
return;
251
if (!owner)
252
el.shinyResizeObserver = this;
253
onResize(el);
254
resized.push(el);
255
if (!el.classList.contains("shiny-plot-output"))
256
return;
257
const img = el.querySelector(
258
'img:not([width="100%"])'
259
);
260
if (img)
261
img.setAttribute("width", "100%");
262
});
263
}
264
});
265
}
266
/**
267
* Observe an element for size changes.
268
* @param {HTMLElement} el - The element to observe.
269
*/
270
observe(el) {
271
this.resizeObserver.observe(el);
272
this.resizeObserverEntries.push(el);
273
}
274
/**
275
* Stop observing an element for size changes.
276
* @param {HTMLElement} el - The element to stop observing.
277
*/
278
unobserve(el) {
279
const idxEl = this.resizeObserverEntries.indexOf(el);
280
if (idxEl < 0)
281
return;
282
this.resizeObserver.unobserve(el);
283
this.resizeObserverEntries.splice(idxEl, 1);
284
}
285
/**
286
* This method checks that we're not continuing to watch elements that no
287
* longer exist in the DOM. If any are found, we stop observing them and
288
* remove them from our array of observed elements.
289
*
290
* @private
291
* @static
292
*/
293
flush() {
294
this.resizeObserverEntries.forEach((el) => {
295
if (!document.body.contains(el))
296
this.unobserve(el);
297
});
298
}
299
};
300
301
// srcts/src/components/card.ts
302
var _Card = class {
303
/**
304
* Creates an instance of a bslib Card component.
305
*
306
* @constructor
307
* @param {HTMLElement} card
308
*/
309
constructor(card) {
310
var _a;
311
card.removeAttribute(_Card.attr.ATTR_INIT);
312
(_a = card.querySelector(`script[${_Card.attr.ATTR_INIT}]`)) == null ? void 0 : _a.remove();
313
this.card = card;
314
_Card.instanceMap.set(card, this);
315
_Card.shinyResizeObserver.observe(this.card);
316
this._addEventListeners();
317
this.overlay = this._createOverlay();
318
this._exitFullScreenOnEscape = this._exitFullScreenOnEscape.bind(this);
319
this._trapFocusExit = this._trapFocusExit.bind(this);
320
}
321
/**
322
* Enter the card's full screen mode, either programmatically or via an event
323
* handler. Full screen mode is activated by adding a class to the card that
324
* positions it absolutely and expands it to fill the viewport. In addition,
325
* we add a full screen overlay element behind the card and we trap focus in
326
* the expanded card while in full screen mode.
327
*
328
* @param {?Event} [event]
329
*/
330
enterFullScreen(event) {
331
var _a;
332
if (event)
333
event.preventDefault();
334
document.addEventListener("keydown", this._exitFullScreenOnEscape, false);
335
document.addEventListener("keydown", this._trapFocusExit, true);
336
this.card.setAttribute(_Card.attr.ATTR_FULL_SCREEN, "true");
337
document.body.classList.add(_Card.attr.CLASS_HAS_FULL_SCREEN);
338
this.card.insertAdjacentElement("beforebegin", this.overlay.container);
339
if (!this.card.contains(document.activeElement) || ((_a = document.activeElement) == null ? void 0 : _a.classList.contains(
340
_Card.attr.CLASS_FULL_SCREEN_ENTER
341
))) {
342
this.card.setAttribute("tabindex", "-1");
343
this.card.focus();
344
}
345
}
346
/**
347
* Exit full screen mode. This removes the full screen overlay element,
348
* removes the full screen class from the card, and removes the keyboard event
349
* listeners that were added when entering full screen mode.
350
*/
351
exitFullScreen() {
352
document.removeEventListener(
353
"keydown",
354
this._exitFullScreenOnEscape,
355
false
356
);
357
document.removeEventListener("keydown", this._trapFocusExit, true);
358
this.overlay.container.remove();
359
this.card.setAttribute(_Card.attr.ATTR_FULL_SCREEN, "false");
360
this.card.removeAttribute("tabindex");
361
document.body.classList.remove(_Card.attr.CLASS_HAS_FULL_SCREEN);
362
}
363
/**
364
* Adds general card-specific event listeners.
365
* @private
366
*/
367
_addEventListeners() {
368
const btnFullScreen = this.card.querySelector(
369
`:scope > * > .${_Card.attr.CLASS_FULL_SCREEN_ENTER}`
370
);
371
if (!btnFullScreen)
372
return;
373
btnFullScreen.addEventListener("click", (ev) => this.enterFullScreen(ev));
374
}
375
/**
376
* An event handler to exit full screen mode when the Escape key is pressed.
377
* @private
378
* @param {KeyboardEvent} event
379
*/
380
_exitFullScreenOnEscape(event) {
381
if (!(event.target instanceof HTMLElement))
382
return;
383
const selOpenSelectInput = ["select[open]", "input[aria-expanded='true']"];
384
if (event.target.matches(selOpenSelectInput.join(", ")))
385
return;
386
if (event.key === "Escape") {
387
this.exitFullScreen();
388
}
389
}
390
/**
391
* An event handler to trap focus within the card when in full screen mode.
392
*
393
* @description
394
* This keyboard event handler ensures that tab focus stays within the card
395
* when in full screen mode. When the card is first expanded,
396
* we move focus to the card element itself. If focus somehow leaves the card,
397
* we returns focus to the card container.
398
*
399
* Within the card, we handle only tabbing from the close anchor or the last
400
* focusable element and only when tab focus would have otherwise left the
401
* card. In those cases, we cycle focus to the last focusable element or back
402
* to the anchor. If the card doesn't have any focusable elements, we move
403
* focus to the close anchor.
404
*
405
* @note
406
* Because the card contents may change, we check for focusable elements
407
* every time the handler is called.
408
*
409
* @private
410
* @param {KeyboardEvent} event
411
*/
412
_trapFocusExit(event) {
413
if (!(event instanceof KeyboardEvent))
414
return;
415
if (event.key !== "Tab")
416
return;
417
const isFocusedContainer = event.target === this.card;
418
const isFocusedAnchor = event.target === this.overlay.anchor;
419
const isFocusedWithin = this.card.contains(event.target);
420
const stopEvent = () => {
421
event.preventDefault();
422
event.stopImmediatePropagation();
423
};
424
if (!(isFocusedWithin || isFocusedContainer || isFocusedAnchor)) {
425
stopEvent();
426
this.card.focus();
427
return;
428
}
429
const focusableElements = getAllFocusableChildren(this.card).filter(
430
(el) => !el.classList.contains(_Card.attr.CLASS_FULL_SCREEN_ENTER)
431
);
432
const hasFocusableElements = focusableElements.length > 0;
433
if (!hasFocusableElements) {
434
stopEvent();
435
this.overlay.anchor.focus();
436
return;
437
}
438
if (isFocusedContainer)
439
return;
440
const lastFocusable = focusableElements[focusableElements.length - 1];
441
const isFocusedLast = event.target === lastFocusable;
442
if (isFocusedAnchor && event.shiftKey) {
443
stopEvent();
444
lastFocusable.focus();
445
return;
446
}
447
if (isFocusedLast && !event.shiftKey) {
448
stopEvent();
449
this.overlay.anchor.focus();
450
return;
451
}
452
}
453
/**
454
* Creates the full screen overlay.
455
* @private
456
* @returns {CardFullScreenOverlay}
457
*/
458
_createOverlay() {
459
const container = document.createElement("div");
460
container.id = _Card.attr.ID_FULL_SCREEN_OVERLAY;
461
container.onclick = this.exitFullScreen.bind(this);
462
const anchor = this._createOverlayCloseAnchor();
463
container.appendChild(anchor);
464
return { container, anchor };
465
}
466
/**
467
* Creates the anchor element used to exit the full screen mode.
468
* @private
469
* @returns {HTMLAnchorElement}
470
*/
471
_createOverlayCloseAnchor() {
472
const anchor = document.createElement("a");
473
anchor.classList.add(_Card.attr.CLASS_FULL_SCREEN_EXIT);
474
anchor.tabIndex = 0;
475
anchor.onclick = () => this.exitFullScreen();
476
anchor.onkeydown = (ev) => {
477
if (ev.key === "Enter" || ev.key === " ") {
478
this.exitFullScreen();
479
}
480
};
481
anchor.innerHTML = this._overlayCloseHtml();
482
return anchor;
483
}
484
/**
485
* Returns the HTML for the close icon.
486
* @private
487
* @returns {string}
488
*/
489
_overlayCloseHtml() {
490
return "Close <svg width='20' height='20' fill='currentColor' class='bi bi-x-lg' viewBox='0 0 16 16'><path d='M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854Z'/></svg>";
491
}
492
/**
493
* Returns the card instance associated with the given element, if any.
494
* @public
495
* @static
496
* @param {HTMLElement} el
497
* @returns {(Card | undefined)}
498
*/
499
static getInstance(el) {
500
return _Card.instanceMap.get(el);
501
}
502
/**
503
* Initializes all cards that require initialization on the page, or schedules
504
* initialization if the DOM is not yet ready.
505
* @public
506
* @static
507
* @param {boolean} [flushResizeObserver=true]
508
*/
509
static initializeAllCards(flushResizeObserver = true) {
510
if (document.readyState === "loading") {
511
if (!_Card.onReadyScheduled) {
512
_Card.onReadyScheduled = true;
513
document.addEventListener("DOMContentLoaded", () => {
514
_Card.initializeAllCards(false);
515
});
516
}
517
return;
518
}
519
if (flushResizeObserver) {
520
_Card.shinyResizeObserver.flush();
521
}
522
const initSelector = `.${_Card.attr.CLASS_CARD}[${_Card.attr.ATTR_INIT}]`;
523
if (!document.querySelector(initSelector)) {
524
return;
525
}
526
const cards = document.querySelectorAll(initSelector);
527
cards.forEach((card) => new _Card(card));
528
}
529
};
530
var Card = _Card;
531
/**
532
* Key bslib-specific classes and attributes used by the card component.
533
* @private
534
* @static
535
* @type {{ ATTR_INIT: string; CLASS_CARD: string; CLASS_FULL_SCREEN: string; CLASS_HAS_FULL_SCREEN: string; CLASS_FULL_SCREEN_ENTER: string; CLASS_FULL_SCREEN_EXIT: string; ID_FULL_SCREEN_OVERLAY: string; }}
536
*/
537
Card.attr = {
538
// eslint-disable-next-line @typescript-eslint/naming-convention
539
ATTR_INIT: "data-bslib-card-init",
540
// eslint-disable-next-line @typescript-eslint/naming-convention
541
CLASS_CARD: "bslib-card",
542
// eslint-disable-next-line @typescript-eslint/naming-convention
543
ATTR_FULL_SCREEN: "data-full-screen",
544
// eslint-disable-next-line @typescript-eslint/naming-convention
545
CLASS_HAS_FULL_SCREEN: "bslib-has-full-screen",
546
// eslint-disable-next-line @typescript-eslint/naming-convention
547
CLASS_FULL_SCREEN_ENTER: "bslib-full-screen-enter",
548
// eslint-disable-next-line @typescript-eslint/naming-convention
549
CLASS_FULL_SCREEN_EXIT: "bslib-full-screen-exit",
550
// eslint-disable-next-line @typescript-eslint/naming-convention
551
ID_FULL_SCREEN_OVERLAY: "bslib-full-screen-overlay"
552
};
553
/**
554
* A Shiny-specific resize observer that ensures Shiny outputs in within the
555
* card resize appropriately.
556
* @private
557
* @type {ShinyResizeObserver}
558
* @static
559
*/
560
Card.shinyResizeObserver = new ShinyResizeObserver();
561
/**
562
* The registry of card instances and their associated DOM elements.
563
* @private
564
* @static
565
* @type {WeakMap<HTMLElement, Card>}
566
*/
567
Card.instanceMap = /* @__PURE__ */ new WeakMap();
568
/**
569
* If cards are initialized before the DOM is ready, we re-schedule the
570
* initialization to occur on DOMContentLoaded.
571
* @private
572
* @static
573
* @type {boolean}
574
*/
575
Card.onReadyScheduled = false;
576
window.bslib = window.bslib || {};
577
window.bslib.Card = Card;
578
579
// srcts/src/components/sidebar.ts
580
var _Sidebar = class {
581
/**
582
* Creates an instance of a collapsible bslib Sidebar.
583
* @constructor
584
* @param {HTMLElement} container
585
*/
586
constructor(container) {
587
var _a;
588
_Sidebar.instanceMap.set(container, this);
589
this.layout = {
590
container,
591
main: container.querySelector(":scope > .main"),
592
sidebar: container.querySelector(":scope > .sidebar"),
593
toggle: container.querySelector(
594
":scope > .collapse-toggle"
595
)
596
};
597
const sideAccordion = this.layout.sidebar.querySelector(
598
":scope > .sidebar-content > .accordion"
599
);
600
if (sideAccordion) {
601
(_a = sideAccordion == null ? void 0 : sideAccordion.parentElement) == null ? void 0 : _a.classList.add("has-accordion");
602
sideAccordion.classList.add("accordion-flush");
603
}
604
if (this.layout.toggle) {
605
this._initEventListeners();
606
this._initSidebarCounters();
607
this._initDesktop();
608
}
609
_Sidebar.shinyResizeObserver.observe(this.layout.main);
610
container.removeAttribute("data-bslib-sidebar-init");
611
const initScript = container.querySelector(
612
":scope > script[data-bslib-sidebar-init]"
613
);
614
if (initScript) {
615
container.removeChild(initScript);
616
}
617
}
618
/**
619
* Read the current state of the sidebar. Note that, when calling this method,
620
* the sidebar may be transitioning into the state returned by this method.
621
*
622
* @description
623
* The sidebar state works as follows, starting from the open state. When the
624
* sidebar is closed:
625
* 1. We add both the `COLLAPSE` and `TRANSITIONING` classes to the sidebar.
626
* 2. The sidebar collapse begins to animate. On desktop devices, and where it
627
* is supported, we transition the `grid-template-columns` property of the
628
* sidebar layout. On mobile, the sidebar is hidden immediately. In both
629
* cases, the collapse icon rotates and we use this rotation to determine
630
* when the transition is complete.
631
* 3. If another sidebar state toggle is requested while closing the sidebar,
632
* we remove the `COLLAPSE` class and the animation immediately starts to
633
* reverse.
634
* 4. When the `transition` is complete, we remove the `TRANSITIONING` class.
635
* @readonly
636
* @type {boolean}
637
*/
638
get isClosed() {
639
return this.layout.container.classList.contains(_Sidebar.classes.COLLAPSE);
640
}
641
/**
642
* Given a sidebar container, return the Sidebar instance associated with it.
643
* @public
644
* @static
645
* @param {HTMLElement} el
646
* @returns {(Sidebar | undefined)}
647
*/
648
static getInstance(el) {
649
return _Sidebar.instanceMap.get(el);
650
}
651
/**
652
* Initialize all collapsible sidebars on the page.
653
* @public
654
* @static
655
* @param {boolean} [flushResizeObserver=true] When `true`, we remove
656
* non-existent elements from the ResizeObserver. This is required
657
* periodically to prevent memory leaks. To avoid over-checking, we only flush
658
* the ResizeObserver when initializing sidebars after page load.
659
*/
660
static initCollapsibleAll(flushResizeObserver = true) {
661
if (document.readyState === "loading") {
662
if (!_Sidebar.onReadyScheduled) {
663
_Sidebar.onReadyScheduled = true;
664
document.addEventListener("DOMContentLoaded", () => {
665
_Sidebar.initCollapsibleAll(false);
666
});
667
}
668
return;
669
}
670
const initSelector = `.${_Sidebar.classes.LAYOUT}[data-bslib-sidebar-init]`;
671
if (!document.querySelector(initSelector)) {
672
return;
673
}
674
if (flushResizeObserver)
675
_Sidebar.shinyResizeObserver.flush();
676
const containers = document.querySelectorAll(initSelector);
677
containers.forEach((container) => new _Sidebar(container));
678
}
679
/**
680
* Initialize event listeners for the sidebar toggle button.
681
* @private
682
*/
683
_initEventListeners() {
684
var _a;
685
const { toggle } = this.layout;
686
toggle.addEventListener("click", (ev) => {
687
ev.preventDefault();
688
this.toggle("toggle");
689
});
690
(_a = toggle.querySelector(".collapse-icon")) == null ? void 0 : _a.addEventListener("transitionend", () => this._finalizeState());
691
}
692
/**
693
* Initialize nested sidebar counters.
694
*
695
* @description
696
* This function walks up the DOM tree, adding CSS variables to each direct
697
* parent sidebar layout that count the layout's position in the stack of
698
* nested layouts. We use these counters to keep the collapse toggles from
699
* overlapping. Note that always-open sidebars that don't have collapse
700
* toggles break the chain of nesting.
701
* @private
702
*/
703
_initSidebarCounters() {
704
const { container } = this.layout;
705
const selectorChildLayouts = `.${_Sidebar.classes.LAYOUT}> .main > .${_Sidebar.classes.LAYOUT}:not([data-bslib-sidebar-open="always"])`;
706
const isInnermostLayout = container.querySelector(selectorChildLayouts) === null;
707
if (!isInnermostLayout) {
708
return;
709
}
710
function nextSidebarParent(el) {
711
el = el ? el.parentElement : null;
712
if (el && el.classList.contains("main")) {
713
el = el.parentElement;
714
}
715
if (el && el.classList.contains(_Sidebar.classes.LAYOUT)) {
716
return el;
717
}
718
return null;
719
}
720
const layouts = [container];
721
let parent = nextSidebarParent(container);
722
while (parent) {
723
layouts.unshift(parent);
724
parent = nextSidebarParent(parent);
725
}
726
const count = { left: 0, right: 0 };
727
layouts.forEach(function(x, i) {
728
x.style.setProperty("--bslib-sidebar-counter", i.toString());
729
const isRight = x.classList.contains("sidebar-right");
730
const thisCount = isRight ? count.right++ : count.left++;
731
x.style.setProperty(
732
"--bslib-sidebar-overlap-counter",
733
thisCount.toString()
734
);
735
});
736
}
737
/**
738
* Initialize the sidebar's initial state when `open = "desktop"`.
739
* @private
740
*/
741
_initDesktop() {
742
var _a;
743
const { container } = this.layout;
744
if (((_a = container.dataset.bslibSidebarOpen) == null ? void 0 : _a.trim()) !== "desktop") {
745
return;
746
}
747
const initCollapsed = window.getComputedStyle(container).getPropertyValue("--bslib-sidebar-js-init-collapsed");
748
if (initCollapsed.trim() === "true") {
749
this.toggle("close");
750
}
751
}
752
/**
753
* Toggle the sidebar's open/closed state.
754
* @public
755
* @param {SidebarToggleMethod | undefined} method Whether to `"open"`,
756
* `"close"` or `"toggle"` the sidebar. If `.toggle()` is called without an
757
* argument, it will toggle the sidebar's state.
758
*/
759
toggle(method) {
760
if (typeof method === "undefined") {
761
method = "toggle";
762
}
763
const { container, sidebar } = this.layout;
764
const isClosed = this.isClosed;
765
if (["open", "close", "toggle"].indexOf(method) === -1) {
766
throw new Error(`Unknown method ${method}`);
767
}
768
if (method === "toggle") {
769
method = isClosed ? "open" : "close";
770
}
771
if (isClosed && method === "close" || !isClosed && method === "open") {
772
return;
773
}
774
if (method === "open") {
775
sidebar.hidden = false;
776
}
777
container.classList.add(_Sidebar.classes.TRANSITIONING);
778
container.classList.toggle(_Sidebar.classes.COLLAPSE);
779
}
780
/**
781
* When the sidebar open/close transition ends, finalize the sidebar's state.
782
* @private
783
*/
784
_finalizeState() {
785
const { container, sidebar, toggle } = this.layout;
786
container.classList.remove(_Sidebar.classes.TRANSITIONING);
787
sidebar.hidden = this.isClosed;
788
toggle.setAttribute("aria-expanded", this.isClosed ? "false" : "true");
789
const event = new CustomEvent("bslib.sidebar", {
790
bubbles: true,
791
detail: { open: !this.isClosed }
792
});
793
sidebar.dispatchEvent(event);
794
$(sidebar).trigger("toggleCollapse.sidebarInputBinding");
795
$(sidebar).trigger(this.isClosed ? "hidden" : "shown");
796
}
797
};
798
var Sidebar = _Sidebar;
799
/**
800
* A Shiny-specific resize observer that ensures Shiny outputs in the main
801
* content areas of the sidebar resize appropriately.
802
* @private
803
* @type {ShinyResizeObserver}
804
* @static
805
*/
806
Sidebar.shinyResizeObserver = new ShinyResizeObserver();
807
/**
808
* Static classes related to the sidebar layout or state.
809
* @public
810
* @static
811
* @readonly
812
* @type {{ LAYOUT: string; COLLAPSE: string; TRANSITIONING: string; }}
813
*/
814
Sidebar.classes = {
815
// eslint-disable-next-line @typescript-eslint/naming-convention
816
LAYOUT: "bslib-sidebar-layout",
817
// eslint-disable-next-line @typescript-eslint/naming-convention
818
COLLAPSE: "sidebar-collapsed",
819
// eslint-disable-next-line @typescript-eslint/naming-convention
820
TRANSITIONING: "transitioning"
821
};
822
/**
823
* If sidebars are initialized before the DOM is ready, we re-schedule the
824
* initialization to occur on DOMContentLoaded.
825
* @private
826
* @static
827
* @type {boolean}
828
*/
829
Sidebar.onReadyScheduled = false;
830
/**
831
* A map of initialized sidebars to their respective Sidebar instances.
832
* @private
833
* @static
834
* @type {WeakMap<HTMLElement, Sidebar>}
835
*/
836
Sidebar.instanceMap = /* @__PURE__ */ new WeakMap();
837
var SidebarInputBinding = class extends InputBinding {
838
find(scope) {
839
return $(scope).find(`.${Sidebar.classes.LAYOUT} > .bslib-sidebar-input`);
840
}
841
getValue(el) {
842
const sb = Sidebar.getInstance(el.parentElement);
843
if (!sb)
844
return false;
845
return !sb.isClosed;
846
}
847
setValue(el, value) {
848
const method = value ? "open" : "close";
849
this.receiveMessage(el, { method });
850
}
851
subscribe(el, callback) {
852
$(el).on(
853
"toggleCollapse.sidebarInputBinding",
854
// eslint-disable-next-line @typescript-eslint/no-unused-vars
855
function(event) {
856
callback(true);
857
}
858
);
859
}
860
unsubscribe(el) {
861
$(el).off(".sidebarInputBinding");
862
}
863
receiveMessage(el, data) {
864
const sb = Sidebar.getInstance(el.parentElement);
865
if (sb)
866
sb.toggle(data.method);
867
}
868
};
869
registerBinding(SidebarInputBinding, "sidebar");
870
window.bslib = window.bslib || {};
871
window.bslib.Sidebar = Sidebar;
872
873
// srcts/src/components/_shinyAddCustomMessageHandlers.ts
874
function shinyAddCustomMessageHandlers(handlers) {
875
if (!window.Shiny) {
876
return;
877
}
878
for (const [name, handler] of Object.entries(handlers)) {
879
Shiny.addCustomMessageHandler(name, handler);
880
}
881
}
882
883
// srcts/src/components/index.ts
884
var bslibMessageHandlers = {
885
// eslint-disable-next-line @typescript-eslint/naming-convention
886
"bslib.toggle-input-binary": (msg) => {
887
const el = document.getElementById(msg.id);
888
if (!el) {
889
console.warn("[bslib.toggle-input-binary] No element found", msg);
890
}
891
const binding = $(el).data("shiny-input-binding");
892
if (!(binding instanceof InputBinding)) {
893
console.warn("[bslib.toggle-input-binary] No input binding found", msg);
894
return;
895
}
896
let value = msg.value;
897
if (typeof value === "undefined") {
898
value = !binding.getValue(el);
899
}
900
binding.receiveMessage(el, { value });
901
}
902
};
903
if (window.Shiny) {
904
shinyAddCustomMessageHandlers(bslibMessageHandlers);
905
}
906
function insertSvgGradient() {
907
const temp = document.createElement("div");
908
temp.innerHTML = `
909
<svg aria-hidden="true" focusable="false" style="width:0;height:0;position:absolute;">
910
<!-- ref: https://fvsch.com/svg-gradient-fill -->
911
<linearGradient id='bslib---icon-gradient' x1='0' y1='0' x2='1.6' y2='2.4'>
912
<stop offset='0%' stop-color='var(--bslib-icon-gradient-0, #007bc2)' />
913
<stop offset='14.29%' stop-color='var(--bslib-icon-gradient-1, #0770c9)' />
914
<stop offset='28.57%' stop-color='var(--bslib-icon-gradient-2, #0d63da)' />
915
<stop offset='42.86%' stop-color='var(--bslib-icon-gradient-3, #2b4af9)' />
916
<stop offset='57.14%' stop-color='var(--bslib-icon-gradient-4, #5e29f7)' />
917
<stop offset='71.43%' stop-color='var(--bslib-icon-gradient-5, #7217d7)' />
918
<stop offset='100%' stop-color='var(--bslib-icon-gradient-6, #74149c)' />
919
</linearGradient>
920
</svg>`;
921
document.body.appendChild(temp.children[0]);
922
}
923
if (document.readyState === "complete") {
924
insertSvgGradient();
925
} else {
926
document.addEventListener("DOMContentLoaded", insertSvgGradient);
927
}
928
})();
929
930
931