@markout-lang/bootstrap-kit 0.2.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.
@@ -0,0 +1,73 @@
1
+ <lib>
2
+ <:import src="base.htm" />
3
+
4
+ <!---
5
+ Pagination driven by a page number rather than by markup: give it the
6
+ `:current` page and the total `:pages` and it works out which entry is
7
+ active and when the arrows are dead.
8
+
9
+ The current page is `:current` rather than `:page`, because `page` is
10
+ already the name of `<html>`'s own scope — a parameter of that name would
11
+ resolve to the scope instead of to the number.
12
+
13
+ `:link` and `:select` are the two ways to answer "what does a click do".
14
+ `:link` builds an href per page for a server-rendered list; `:select` is
15
+ called with the page number for one that stays on the page. Both are
16
+ ordinary function parameters — a callback the component calls, not a DOM
17
+ event, so it is a value and not an `:on-` handler.
18
+ -->
19
+ <:define tag="bs-pagination:nav"
20
+
21
+ // parameters
22
+ :extra=${null} // extra classes for this instance
23
+ :current=${1}
24
+ :pages=${5}
25
+ :size=${null} // 'sm' | 'lg'
26
+ :align=${'start'} // 'start' | 'center' | 'end'
27
+ :prev=${'«'}
28
+ :next=${'»'}
29
+ :link=${(n) => null}
30
+ :select=${(n) => {}}
31
+ :label=${'Page navigation'}
32
+
33
+ // private
34
+ :_class=${[
35
+ 'pagination mb-0',
36
+ size ? `pagination-${size}` : '',
37
+ align === 'center' ? 'justify-content-center' : '',
38
+ align === 'end' ? 'justify-content-end' : '',
39
+ ].filter(s => s).join(' ')}
40
+ :_numbers=${Array.from({ length: pages }, (_, i) => i + 1)}
41
+
42
+ class=${extra}
43
+ aria-label=${label}
44
+ >
45
+ <ul class=${_class}>
46
+ <li class="page-item" :class-disabled=${current <= 1}>
47
+ <a class="page-link"
48
+ href=${link(current - 1)}
49
+ aria-label="Previous"
50
+ :on-click=${() => current > 1 && select(current - 1)}>${prev}</a>
51
+ </li>
52
+
53
+ <li class="page-item"
54
+ :for-each=${_numbers}
55
+ :for-as="n"
56
+ :for-key=${n}
57
+ :class-active=${n === current}
58
+ aria-current=${n === current ? 'page' : null}>
59
+ <a class="page-link"
60
+ href=${link(n)}
61
+ :on-click=${() => select(n)}>${n}</a>
62
+ </li>
63
+
64
+ <li class="page-item" :class-disabled=${current >= pages}>
65
+ <a class="page-link"
66
+ href=${link(current + 1)}
67
+ aria-label="Next"
68
+ :on-click=${() => current < pages && select(current + 1)}>${next}</a>
69
+ </li>
70
+ </ul>
71
+ </:define>
72
+
73
+ </lib>
@@ -0,0 +1,34 @@
1
+ <lib>
2
+ <:import src="base.htm" />
3
+
4
+ <!---
5
+ A loading placeholder. Bootstrap's animation classes are descendant
6
+ selectors (`.placeholder-glow .placeholder`), so the component wraps its
7
+ own bar and is self-contained — no animation wrapper to remember.
8
+ -->
9
+ <:define tag="bs-placeholder:span"
10
+
11
+ // parameters
12
+ :extra=${null} // extra classes for this instance
13
+ :cols=${6}
14
+ :size=${null} // 'xs' | 'sm' | 'lg'
15
+ :variant=${null}
16
+ :animation=${'glow'} // 'glow' | 'wave' | null
17
+
18
+ // private
19
+ :_class=${[animation ? `placeholder-${animation}` : '', extra]
20
+ .filter(s => s).join(' ') || null}
21
+ :_barClass=${[
22
+ 'placeholder',
23
+ `col-${cols}`,
24
+ size ? `placeholder-${size}` : '',
25
+ variant ? `bg-${variant}` : '',
26
+ ].filter(s => s).join(' ')}
27
+
28
+ class=${_class}
29
+ aria-hidden="true"
30
+ >
31
+ <span class=${_barClass}></span>
32
+ </:define>
33
+
34
+ </lib>
@@ -0,0 +1,43 @@
1
+ <lib>
2
+ <:import src="base.htm" />
3
+
4
+ <!---
5
+ A popover: a tooltip with a heading and a body, and click to open rather
6
+ than hover. Built, rebuilt and disposed on the same three moments, and
7
+ for the same reasons — see `tooltip.htm`.
8
+
9
+ `:handle-content` rather than `:handle-title` because the body is the
10
+ part that usually changes; both are read when it runs.
11
+ -->
12
+ <:define tag="bs-popover:span"
13
+ class="d-inline-block ${extra ?? ''}"
14
+
15
+ // parameters
16
+ :extra=${null} // extra classes for this instance
17
+ :title=${''}
18
+ :content=${''}
19
+ :placement=${'right'}
20
+ :trigger=${'click'}
21
+ :html=${false}
22
+
23
+ // private
24
+ :_build=${() => {
25
+ const bs = globalThis.bootstrap;
26
+ bs.Popover.getInstance($dom)?.dispose();
27
+ new bs.Popover($dom, {
28
+ title: title,
29
+ content: content,
30
+ placement: placement,
31
+ trigger: trigger,
32
+ html: html,
33
+ });
34
+ }}
35
+
36
+ :did-attach=${() => _build()}
37
+ :handle-content=${() => _build()}
38
+ :will-detach=${() => globalThis.bootstrap.Popover.getInstance($dom)?.dispose()}
39
+ >
40
+ <:slot />
41
+ </:define>
42
+
43
+ </lib>
@@ -0,0 +1,54 @@
1
+ <lib>
2
+ <:import src="base.htm" />
3
+
4
+ <!---
5
+ Bootstrap 5.3 moved the ARIA onto the outer `.progress` and left
6
+ `.progress-bar` as the visual fill, so a progress bar is two elements
7
+ whose values have to agree. Here they are derived from one `:value`.
8
+
9
+ `:label` is the text drawn inside the bar; leave it off for a bare bar.
10
+ -->
11
+ <:define tag="bs-progress:div"
12
+ class="progress ${extra ?? ''}"
13
+
14
+ // parameters
15
+ :extra=${null} // extra classes for this instance
16
+ :value=${0}
17
+ :min=${0}
18
+ :max=${100}
19
+ :label=${null}
20
+ :variant=${null}
21
+ :striped=${false}
22
+ :animated=${false}
23
+ :height=${null}
24
+ :name=${'Progress'}
25
+
26
+ // private
27
+ :_percent=${Math.max(0, Math.min(100, ((value - min) / (max - min || 1)) * 100))}
28
+ :_class=${[
29
+ 'progress-bar',
30
+ variant ? `text-bg-${variant}` : '',
31
+ striped || animated ? 'progress-bar-striped' : '',
32
+ animated ? 'progress-bar-animated' : '',
33
+ ].filter(s => s).join(' ')}
34
+
35
+ role="progressbar"
36
+ aria-label=${name}
37
+ aria-valuenow=${value}
38
+ aria-valuemin=${min}
39
+ aria-valuemax=${max}
40
+ :style-height=${height}
41
+ >
42
+ <div class=${_class}
43
+ :style-width="${_percent}%">${label}</div>
44
+ </:define>
45
+
46
+ <!---
47
+ Several bars sharing one track. Each child is a `bs-progress` whose
48
+ percentages add up to 100.
49
+ -->
50
+ <:define tag="bs-progress-stacked:div" class="progress-stacked">
51
+ <:slot />
52
+ </:define>
53
+
54
+ </lib>
@@ -0,0 +1,33 @@
1
+ <lib>
2
+ <:import src="base.htm" />
3
+
4
+ <!---
5
+ Scrollspy is behaviour rather than appearance: it marks the entry in a
6
+ nav that matches what is currently on screen. `:target` is the selector
7
+ of that nav, and the element this sits on is the thing being scrolled.
8
+
9
+ <bs-nav :vertical id="toc" :items=${sections} />
10
+ <bs-scrollspy :target="#toc" :height="300">…</bs-scrollspy>
11
+ -->
12
+ <:define tag="bs-scrollspy:div"
13
+
14
+ // parameters
15
+ :extra=${null} // extra classes for this instance
16
+ :target=${null}
17
+ :offset=${0}
18
+ :smooth=${false}
19
+ :height=${null}
20
+
21
+ class=${extra}
22
+ data-bs-spy="scroll"
23
+ data-bs-target=${target}
24
+ data-bs-root-margin="${offset}px 0px -40%"
25
+ data-bs-smooth-scroll=${smooth ? 'true' : null}
26
+ tabindex="0"
27
+ :style-height=${height ? `${height}px` : null}
28
+ :style-overflow-y=${height ? 'auto' : null}
29
+ >
30
+ <:slot />
31
+ </:define>
32
+
33
+ </lib>
@@ -0,0 +1,72 @@
1
+ <lib>
2
+ <:import src="base.htm" />
3
+
4
+ <!---
5
+ A select whose options come from an array. An entry is either a string or
6
+ `{ name, value, disabled }`, so the quick case stays quick:
7
+
8
+ <bs-select :label="Size" :options=${['S', 'M', 'L']} />
9
+ -->
10
+ <:define tag="bs-select:div"
11
+ class="mb-3 ${extra ?? ''}"
12
+
13
+ // parameters
14
+ :extra=${null} // extra classes for this instance
15
+ :label=${null}
16
+ :name=${null}
17
+ :options=${[]}
18
+ :value=${null}
19
+ :placeholder=${null}
20
+ :help=${null}
21
+ :size=${null} // 'sm' | 'lg'
22
+ :multiple=${false}
23
+ :disabled=${false}
24
+ :required=${false}
25
+ :floating=${false}
26
+
27
+ // private
28
+ :_id=${`bs-select-${$id}`}
29
+ :_helpId=${`bs-select-help-${$id}`}
30
+ :_class=${['form-select', size ? `form-select-${size}` : '']
31
+ .filter(s => s).join(' ')}
32
+ :_options=${options.map(o =>
33
+ typeof o === 'object' && o !== null
34
+ ? { name: o.name, value: o.value ?? o.name, disabled: !!o.disabled }
35
+ : { name: o, value: o, disabled: false })}
36
+ >
37
+ <label class="form-label"
38
+ for=${_id}
39
+ :if=${!(floating || !label)}>${label}</label>
40
+
41
+ <div :class-form-floating=${floating}>
42
+ <select class=${_class}
43
+ id=${_id}
44
+ name=${name}
45
+ aria-label=${label || null}
46
+ aria-describedby=${help ? _helpId : null}
47
+ :attr-multiple=${multiple}
48
+ :attr-disabled=${disabled}
49
+ :attr-required=${required}
50
+ :on-change=${ev => value = ev.target.value}>
51
+
52
+ <option value=""
53
+ :if=${placeholder}
54
+ :attr-selected=${value == null}>${placeholder}</option>
55
+
56
+ <option :for-each=${_options}
57
+ :for-as="option"
58
+ value=${option.value}
59
+ :attr-disabled=${option.disabled}
60
+ :attr-selected=${option.value === value}>${option.name}</option>
61
+ </select>
62
+
63
+ <label for=${_id}
64
+ :if=${floating}>${label}</label>
65
+ </div>
66
+
67
+ <div class="form-text"
68
+ id=${_helpId}
69
+ :if=${help}>${help}</div>
70
+ </:define>
71
+
72
+ </lib>
@@ -0,0 +1,32 @@
1
+ <lib>
2
+ <:import src="base.htm" />
3
+
4
+ <!---
5
+ A spinner is a loading state, so it needs to say so to a screen reader as
6
+ well as to the eye: `role="status"` plus visually hidden text. That text
7
+ is the reason this is a component rather than a class.
8
+ -->
9
+ <:define tag="bs-spinner:div"
10
+ role="status"
11
+
12
+ // parameters
13
+ :extra=${null} // extra classes for this instance
14
+ :variant=${null}
15
+ :type=${'border'} // 'border' | 'grow'
16
+ :small=${false}
17
+ :label=${'Loading…'}
18
+
19
+ // private
20
+ :_class=${[
21
+ `spinner-${type}`,
22
+ small ? `spinner-${type}-sm` : '',
23
+ variant ? `text-${variant}` : '',
24
+ extra,
25
+ ].filter(s => s).join(' ')}
26
+
27
+ class=${_class}
28
+ >
29
+ <span class="visually-hidden">${label}</span>
30
+ </:define>
31
+
32
+ </lib>
@@ -0,0 +1,70 @@
1
+ <lib>
2
+ <:import src="base.htm" />
3
+
4
+ <!---
5
+ A table from `:columns` and `:rows`.
6
+
7
+ A column is `{ key, name, class }`; a row is any object the columns can
8
+ be read out of. That is the one place in this kit where markup is
9
+ generated from data twice over — once per column, once per row — and it
10
+ is the case where writing it by hand hurts most.
11
+
12
+ `:responsive` is a class on the same element rather than an extra
13
+ wrapper: `.table-responsive` only sets `overflow-x`, so it costs nothing
14
+ when off.
15
+ -->
16
+ <:define tag="bs-table:div"
17
+
18
+ // parameters
19
+ :extra=${null} // extra classes for this instance
20
+ :columns=${[]}
21
+ :rows=${[]}
22
+ :caption=${null}
23
+ :striped=${false}
24
+ :hover=${false}
25
+ :bordered=${false}
26
+ :borderless=${false}
27
+ :small=${false}
28
+ :variant=${null}
29
+ :headVariant=${null}
30
+ :align=${null} // 'middle' | 'bottom'
31
+ :responsive=${false}
32
+
33
+ // private
34
+ :_class=${[
35
+ 'table',
36
+ striped ? 'table-striped' : '',
37
+ hover ? 'table-hover' : '',
38
+ bordered ? 'table-bordered' : '',
39
+ borderless ? 'table-borderless' : '',
40
+ small ? 'table-sm' : '',
41
+ variant ? `table-${variant}` : '',
42
+ align ? `align-${align}` : '',
43
+ ].filter(s => s).join(' ')}
44
+
45
+ class=${extra}
46
+ :class-table-responsive=${responsive}
47
+ >
48
+ <table class=${_class}>
49
+ <caption :if=${caption}>${caption}</caption>
50
+
51
+ <thead class=${headVariant ? `table-${headVariant}` : null}>
52
+ <tr>
53
+ <th scope="col"
54
+ :for-each=${columns}
55
+ :for-as="column"
56
+ class=${column.class ?? null}>${column.name ?? column.key}</th>
57
+ </tr>
58
+ </thead>
59
+
60
+ <tbody>
61
+ <tr :for-each=${rows} :for-as="row" class=${row.class ?? null}>
62
+ <td :for-each=${columns}
63
+ :for-as="column"
64
+ class=${column.class ?? null}>${row[column.key]}</td>
65
+ </tr>
66
+ </tbody>
67
+ </table>
68
+ </:define>
69
+
70
+ </lib>
@@ -0,0 +1,70 @@
1
+ <lib>
2
+ <:import src="base.htm" />
3
+
4
+ <!---
5
+ Bootstrap 5.3's colour modes, as a value.
6
+
7
+ The inline script runs before first paint, which is the whole point of
8
+ putting it here rather than in a handler: the mode has to be on `<html>`
9
+ before the first frame or the page flashes the wrong one. It is the one
10
+ piece of this kit that cannot be reactive, because it has to happen
11
+ before Markout exists.
12
+
13
+ From there `_theme` is an ordinary value, and it starts as whatever that
14
+ script decided rather than as a literal: a served page knows nothing
15
+ about this visitor, so a default written here would be a guess that the
16
+ handler then wrote over the truth. In the browser the expression finds
17
+ the attribute already set; on the server there is no document and the
18
+ fallback stands, which is only ever what the markup is stamped with.
19
+
20
+ `:handle-_theme` writes every change out, and a handler runs once at
21
+ start as well as on every change — so it asks first. On that first run
22
+ the page already shows `v`, and there is nothing to do; leaving storage
23
+ alone there is what keeps a visitor who has never chosen following their
24
+ system rather than being pinned to whatever it said the first time.
25
+
26
+ Everything goes through `globalThis`, because `document` and
27
+ `localStorage` are browser globals rather than standard library, and this
28
+ kit would rather say so at the point of use than pretend a server has
29
+ them.
30
+ -->
31
+ <script>
32
+ (() => {
33
+ const stored = localStorage.getItem('bs-theme');
34
+ const theme = stored
35
+ || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
36
+ document.documentElement.setAttribute('data-bs-theme', theme);
37
+ })();
38
+ </script>
39
+
40
+ <:define tag="bs-theme-toggle:button"
41
+ type="button"
42
+
43
+ // parameters
44
+ :extra=${null} // extra classes for this instance
45
+ :variant=${'secondary'}
46
+ :outline=${true}
47
+ :size=${null}
48
+
49
+ // private
50
+ :_theme=${globalThis.document?.documentElement.getAttribute('data-bs-theme') ?? 'light'}
51
+ :_class=${[
52
+ 'btn',
53
+ `btn-${outline ? 'outline-' : ''}${variant}`,
54
+ size ? `btn-${size}` : '',
55
+ extra,
56
+ ].filter(s => s).join(' ')}
57
+
58
+ class=${_class}
59
+ aria-label="Toggle colour mode"
60
+
61
+ :handle-_theme=${(v) => {
62
+ const html = globalThis.document.documentElement;
63
+ if (html.getAttribute('data-bs-theme') === v) return;
64
+ html.setAttribute('data-bs-theme', v);
65
+ globalThis.localStorage.setItem('bs-theme', v);
66
+ }}
67
+ :on-click=${() => _theme = _theme === 'dark' ? 'light' : 'dark'}
68
+ >${_theme === 'dark' ? '☀' : '☾'}</:define>
69
+
70
+ </lib>
@@ -0,0 +1,115 @@
1
+ <lib>
2
+ <:import src="base.htm" />
3
+ <:import src="button.htm" />
4
+
5
+ <!---
6
+ A toast, shown by setting a value rather than by calling anything:
7
+
8
+ <bs-toast :aka="saved" :title="Saved">Your changes are safe.</bs-toast>
9
+ <bs-button :on-click=${() => saved.open = true}>Save</bs-button>
10
+
11
+ `:handle-open` turns that value into the `show()` / `hide()` the plugin
12
+ wants, and Bootstrap's own `hidden.bs.toast` puts the value back when the
13
+ toast times out — so the value stays true to what is on screen however it
14
+ got closed.
15
+ -->
16
+ <:define tag="bs-toast:div"
17
+ class="toast"
18
+ role="alert"
19
+ aria-live="assertive"
20
+ aria-atomic="true"
21
+
22
+ // parameters
23
+ :extra=${null} // extra classes for this instance
24
+ :title=${null}
25
+ :time=${null}
26
+ :variant=${null}
27
+ :open=${false}
28
+ :autohide=${true}
29
+ :delay=${5000}
30
+
31
+ // private
32
+ :_class=${['toast', variant ? `text-bg-${variant} border-0` : '', extra]
33
+ .filter(s => s).join(' ')}
34
+
35
+ class=${_class}
36
+ data-bs-autohide=${autohide ? null : 'false'}
37
+ data-bs-delay=${delay}
38
+
39
+ :handle-open=${(v) => {
40
+ // only while this markup is in the page: an `:if`/`:for-data` region
41
+ // can take it out with `open` still true, and Bootstrap asked
42
+ // to show a detached element walks into its own internals
43
+ if (!$dom.isConnected) return;
44
+ const toast = globalThis.bootstrap.Toast.getOrCreateInstance($dom);
45
+ v ? toast.show() : toast.hide();
46
+ }}
47
+ /*
48
+ Bootstrap's plugins own DOM outside this element -- a backdrop,
49
+ a popper, a body class -- so an element removed without a
50
+ dispose() leaves it behind. `:will-detach` rather than
51
+ `:will-dispose`: an `:if`/`:for-data` region takes its markup out of
52
+ the page without its scope going anywhere, and the leak is the
53
+ same either way
54
+ */
55
+ :will-detach=${() => globalThis.bootstrap.Toast.getInstance($dom)?.dispose()}
56
+ :on-hidden.bs.toast=${() => open = false}
57
+ >
58
+ <!--- with a title, Bootstrap's header form: the close button lives in it -->
59
+ <div class="toast-header" :if=${title}>
60
+ <strong class="me-auto">${title}</strong>
61
+ <small class="text-body-secondary"
62
+ :if=${time}>${time}</small>
63
+ <bs-close :dismiss="toast" :label="Close notification" />
64
+ </div>
65
+
66
+ <!---
67
+ Without one, Bootstrap's flex form, because a toast that cannot be
68
+ dismissed is a real outcome here: `:autohide=${false}` and no title
69
+ used to leave no close button anywhere on it.
70
+
71
+ The wrapper is what carries `d-flex`, not the toast: `.d-flex` is
72
+ `display: flex !important`, which would beat Bootstrap's own
73
+ `.toast:not(.show) { display: none }` and leave the toast on screen
74
+ for good. The class goes on rather than off so a titled toast keeps
75
+ the plain block body it has always had.
76
+ -->
77
+ <div class=${title ? null : 'd-flex'}>
78
+ <div class="toast-body">
79
+ <:slot />
80
+ </div>
81
+
82
+ <bs-close :dismiss="toast"
83
+ :label="Close notification"
84
+ :extra="me-2 m-auto"
85
+ :if=${!title} />
86
+ </div>
87
+ </:define>
88
+
89
+ <!---
90
+ Where toasts stack. Bootstrap positions the container, not the toasts, so
91
+ this is the piece that decides the corner they appear in.
92
+ -->
93
+ <:define tag="bs-toast-container:div"
94
+
95
+ // parameters
96
+ :extra=${null} // extra classes for this instance
97
+ :placement=${'bottom-end'} // 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | 'top-center'
98
+
99
+ // private
100
+ :_class=${[
101
+ 'toast-container position-fixed p-3',
102
+ placement.startsWith('top') ? 'top-0' : 'bottom-0',
103
+ placement.endsWith('start') ? 'start-0' : '',
104
+ placement.endsWith('end') ? 'end-0' : '',
105
+ placement.endsWith('center') ? 'start-50 translate-middle-x' : '',
106
+ extra,
107
+ ].filter(s => s).join(' ')}
108
+
109
+ class=${_class}
110
+ style="z-index: 1090"
111
+ >
112
+ <:slot />
113
+ </:define>
114
+
115
+ </lib>
@@ -0,0 +1,57 @@
1
+ <lib>
2
+ <:import src="base.htm" />
3
+
4
+ <!---
5
+ Tooltips and popovers are the two components Bootstrap does not start on
6
+ its own: every other one wakes up from a `data-bs-*` attribute, these two
7
+ have to be constructed. The usual answer is a page-level loop over
8
+ `[data-bs-toggle="tooltip"]`, which then misses anything added later.
9
+
10
+ Three moments instead, which between them cover everything a tooltip can
11
+ live through:
12
+
13
+ - `:did-attach` builds it, every time the element enters the page — so
14
+ one inside an `:if`/`:for-data` region that comes back has a working tooltip
15
+ rather than the memory of one.
16
+ - `:will-detach` disposes it before the element leaves. Bootstrap parks
17
+ the popper on `<body>`, so without this, hiding the region leaves a
18
+ tooltip floating over the page for good.
19
+ - `:handle-title` rebuilds it when the text changes, since Bootstrap
20
+ reads its options at construction and never again.
21
+
22
+ All three go through the same `_build`, which disposes first and is
23
+ therefore safe to call more than once.
24
+
25
+ This wraps its content in a `<span>`, since the tooltip needs an element
26
+ of its own to hang off; wrap the trigger, not the page around it.
27
+ -->
28
+ <:define tag="bs-tooltip:span"
29
+ class="d-inline-block ${extra ?? ''}"
30
+
31
+ // parameters
32
+ :extra=${null} // extra classes for this instance
33
+ :title=${''}
34
+ :placement=${'top'}
35
+ :html=${false}
36
+ :trigger=${'hover focus'}
37
+
38
+ // private
39
+ :_build=${() => {
40
+ const bs = globalThis.bootstrap;
41
+ bs.Tooltip.getInstance($dom)?.dispose();
42
+ new bs.Tooltip($dom, {
43
+ title: title,
44
+ placement: placement,
45
+ html: html,
46
+ trigger: trigger,
47
+ });
48
+ }}
49
+
50
+ :did-attach=${() => _build()}
51
+ :handle-title=${() => _build()}
52
+ :will-detach=${() => globalThis.bootstrap.Tooltip.getInstance($dom)?.dispose()}
53
+ >
54
+ <:slot />
55
+ </:define>
56
+
57
+ </lib>