@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.
- package/README.md +331 -0
- package/all.htm +44 -0
- package/package.json +33 -0
- package/parts/accordion.htm +81 -0
- package/parts/alert.htm +48 -0
- package/parts/badge.htm +33 -0
- package/parts/base.htm +71 -0
- package/parts/breadcrumb.htm +44 -0
- package/parts/button.htm +162 -0
- package/parts/card.htm +103 -0
- package/parts/carousel.htm +87 -0
- package/parts/check.htm +164 -0
- package/parts/collapse.htm +31 -0
- package/parts/dropdown.htm +86 -0
- package/parts/image.htm +46 -0
- package/parts/input.htm +178 -0
- package/parts/list-group.htm +58 -0
- package/parts/modal.htm +119 -0
- package/parts/nav.htm +89 -0
- package/parts/navbar.htm +82 -0
- package/parts/offcanvas.htm +90 -0
- package/parts/pagination.htm +73 -0
- package/parts/placeholder.htm +34 -0
- package/parts/popover.htm +43 -0
- package/parts/progress.htm +54 -0
- package/parts/scrollspy.htm +33 -0
- package/parts/select.htm +72 -0
- package/parts/spinner.htm +32 -0
- package/parts/table.htm +70 -0
- package/parts/theme.htm +70 -0
- package/parts/toast.htm +115 -0
- package/parts/tooltip.htm +57 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<lib>
|
|
2
|
+
<:import src="base.htm" />
|
|
3
|
+
|
|
4
|
+
<!---
|
|
5
|
+
The first of the kit's list-driven components. Where a component repeats
|
|
6
|
+
one shape over a list, its API is an array rather than a slot: the caller
|
|
7
|
+
describes the entries, the component owns the markup.
|
|
8
|
+
|
|
9
|
+
An entry is `{ name, link }`. The one without a `link` is the current
|
|
10
|
+
page, and gets `active` plus `aria-current` — the `<a>` stays in place
|
|
11
|
+
either way, since an anchor without an `href` renders as plain text.
|
|
12
|
+
-->
|
|
13
|
+
<:define tag="bs-breadcrumb:nav"
|
|
14
|
+
aria-label="breadcrumb"
|
|
15
|
+
class=${extra}
|
|
16
|
+
|
|
17
|
+
// parameters
|
|
18
|
+
:extra=${null} // extra classes for this instance
|
|
19
|
+
:items=${[
|
|
20
|
+
{ name: 'Home', link: '#' },
|
|
21
|
+
{ name: 'Library', link: '#' },
|
|
22
|
+
{ name: 'Data' },
|
|
23
|
+
]}
|
|
24
|
+
:divider=${null}
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
Bootstrap reads the separator from a CSS custom property, and
|
|
28
|
+
`:style-` writes ordinary CSS properties — so this one goes
|
|
29
|
+
through the plain `style` attribute.
|
|
30
|
+
*/
|
|
31
|
+
style=${divider ? `--bs-breadcrumb-divider: '${divider}'` : null}
|
|
32
|
+
>
|
|
33
|
+
<ol class="breadcrumb mb-0">
|
|
34
|
+
<li class="breadcrumb-item"
|
|
35
|
+
:for-each=${items}
|
|
36
|
+
:for-as="item"
|
|
37
|
+
:class-active=${!item.link}
|
|
38
|
+
aria-current=${item.link ? null : 'page'}>
|
|
39
|
+
<a href=${item.link ?? null}>${item.name}</a>
|
|
40
|
+
</li>
|
|
41
|
+
</ol>
|
|
42
|
+
</:define>
|
|
43
|
+
|
|
44
|
+
</lib>
|
package/parts/button.htm
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<lib>
|
|
2
|
+
<:import src="base.htm" />
|
|
3
|
+
|
|
4
|
+
<!---
|
|
5
|
+
Bootstrap's button, as a real `<button>`.
|
|
6
|
+
|
|
7
|
+
`:toggle`/`:target`/`:dismiss` write the `data-bs-*` triplet every
|
|
8
|
+
JS-driven component in this kit is opened and closed by, so a trigger for
|
|
9
|
+
a modal, an offcanvas or a collapse is this same tag rather than a
|
|
10
|
+
component of its own:
|
|
11
|
+
|
|
12
|
+
<bs-button :toggle="modal" :target="#signup">Sign up</bs-button>
|
|
13
|
+
<bs-button :dismiss="modal" :variant="secondary">Close</bs-button>
|
|
14
|
+
|
|
15
|
+
Note the two spellings of "conditional attribute" used throughout the kit:
|
|
16
|
+
a plain `name=${expr}` sets a VALUE and removes the attribute when the
|
|
17
|
+
expression is `null`, while `:attr-name=${expr}` toggles PRESENCE, which
|
|
18
|
+
is what `disabled` and friends need.
|
|
19
|
+
-->
|
|
20
|
+
<:define tag="bs-button:button"
|
|
21
|
+
|
|
22
|
+
// parameters
|
|
23
|
+
:extra=${null} // extra classes for this instance
|
|
24
|
+
:variant=${'primary'}
|
|
25
|
+
:outline=${false}
|
|
26
|
+
:size=${null}
|
|
27
|
+
:active=${false}
|
|
28
|
+
:disabled=${false}
|
|
29
|
+
:type=${'button'}
|
|
30
|
+
:toggle=${null}
|
|
31
|
+
:target=${null}
|
|
32
|
+
:dismiss=${null}
|
|
33
|
+
|
|
34
|
+
// private
|
|
35
|
+
:_class=${[
|
|
36
|
+
'btn',
|
|
37
|
+
variant ? `btn-${outline ? 'outline-' : ''}${variant}` : '',
|
|
38
|
+
size ? `btn-${size}` : '',
|
|
39
|
+
extra,
|
|
40
|
+
].filter(s => s).join(' ')}
|
|
41
|
+
|
|
42
|
+
class=${_class}
|
|
43
|
+
type=${type}
|
|
44
|
+
:class-active=${active}
|
|
45
|
+
:attr-disabled=${disabled}
|
|
46
|
+
aria-pressed=${active ? 'true' : null}
|
|
47
|
+
data-bs-toggle=${toggle}
|
|
48
|
+
data-bs-target=${target}
|
|
49
|
+
data-bs-dismiss=${dismiss}
|
|
50
|
+
>
|
|
51
|
+
<:slot />
|
|
52
|
+
</:define>
|
|
53
|
+
|
|
54
|
+
<!---
|
|
55
|
+
The same styling on an `<a>`, for a link that is meant to look like a
|
|
56
|
+
button. Left as it is, it is an ordinary link; `:button` turns it into
|
|
57
|
+
one, and `:variant` then means what it means on `bs-button`.
|
|
58
|
+
-->
|
|
59
|
+
<:define tag="bs-link:a"
|
|
60
|
+
|
|
61
|
+
// parameters
|
|
62
|
+
:extra=${null} // extra classes for this instance
|
|
63
|
+
:href=${'#'}
|
|
64
|
+
:variant=${null}
|
|
65
|
+
:outline=${false}
|
|
66
|
+
:size=${null}
|
|
67
|
+
:active=${false}
|
|
68
|
+
:disabled=${false}
|
|
69
|
+
:button=${false}
|
|
70
|
+
:toggle=${null}
|
|
71
|
+
:target=${null}
|
|
72
|
+
|
|
73
|
+
// private
|
|
74
|
+
:_class=${[
|
|
75
|
+
button ? 'btn' : '',
|
|
76
|
+
button && variant ? `btn-${outline ? 'outline-' : ''}${variant}` : '',
|
|
77
|
+
button && size ? `btn-${size}` : '',
|
|
78
|
+
!button && variant ? `link-${variant}` : '',
|
|
79
|
+
extra,
|
|
80
|
+
].filter(s => s).join(' ') || null}
|
|
81
|
+
|
|
82
|
+
class=${_class}
|
|
83
|
+
href=${href}
|
|
84
|
+
role=${button ? 'button' : null}
|
|
85
|
+
:class-active=${active}
|
|
86
|
+
:class-disabled=${disabled}
|
|
87
|
+
aria-disabled=${disabled ? 'true' : null}
|
|
88
|
+
data-bs-toggle=${toggle}
|
|
89
|
+
data-bs-target=${target}
|
|
90
|
+
>
|
|
91
|
+
<:slot />
|
|
92
|
+
</:define>
|
|
93
|
+
|
|
94
|
+
<!---
|
|
95
|
+
A button group. `:label` is Bootstrap's requirement, not this kit's: the
|
|
96
|
+
group needs an accessible name of its own, since the buttons inside only
|
|
97
|
+
name themselves.
|
|
98
|
+
-->
|
|
99
|
+
<:define tag="bs-button-group:div"
|
|
100
|
+
|
|
101
|
+
// parameters
|
|
102
|
+
:extra=${null} // extra classes for this instance
|
|
103
|
+
:label=${'Button group'}
|
|
104
|
+
:size=${null}
|
|
105
|
+
:vertical=${false}
|
|
106
|
+
|
|
107
|
+
// private
|
|
108
|
+
:_class=${[
|
|
109
|
+
vertical ? 'btn-group-vertical' : 'btn-group',
|
|
110
|
+
size ? `btn-group-${size}` : '',
|
|
111
|
+
extra,
|
|
112
|
+
].filter(s => s).join(' ')}
|
|
113
|
+
|
|
114
|
+
class=${_class}
|
|
115
|
+
role="group"
|
|
116
|
+
aria-label=${label}
|
|
117
|
+
>
|
|
118
|
+
<:slot />
|
|
119
|
+
</:define>
|
|
120
|
+
|
|
121
|
+
<!---
|
|
122
|
+
A toolbar of groups: Bootstrap spaces groups apart differently to the
|
|
123
|
+
buttons inside one, and this is the wrapper that says so.
|
|
124
|
+
-->
|
|
125
|
+
<:define tag="bs-button-toolbar:div"
|
|
126
|
+
class="btn-toolbar ${extra ?? ''}"
|
|
127
|
+
role="toolbar"
|
|
128
|
+
|
|
129
|
+
// parameters
|
|
130
|
+
:extra=${null} // extra classes for this instance
|
|
131
|
+
:label=${'Toolbar'}
|
|
132
|
+
:gap=${2}
|
|
133
|
+
|
|
134
|
+
aria-label=${label}
|
|
135
|
+
:class-gap-1=${gap === 1}
|
|
136
|
+
:class-gap-2=${gap === 2}
|
|
137
|
+
:class-gap-3=${gap === 3}
|
|
138
|
+
>
|
|
139
|
+
<:slot />
|
|
140
|
+
</:define>
|
|
141
|
+
|
|
142
|
+
<!---
|
|
143
|
+
The dismiss "×". Nothing but `class="btn-close"`, but it carries an
|
|
144
|
+
accessible name and the dismissal target, both of which are easy to leave
|
|
145
|
+
off when writing it by hand.
|
|
146
|
+
-->
|
|
147
|
+
<:define tag="bs-close:button"
|
|
148
|
+
type="button"
|
|
149
|
+
class="btn-close ${extra ?? ''}"
|
|
150
|
+
|
|
151
|
+
// parameters
|
|
152
|
+
:extra=${null} // extra classes for this instance
|
|
153
|
+
:label=${'Close'}
|
|
154
|
+
:dismiss=${null}
|
|
155
|
+
:disabled=${false}
|
|
156
|
+
|
|
157
|
+
aria-label=${label}
|
|
158
|
+
:attr-disabled=${disabled}
|
|
159
|
+
data-bs-dismiss=${dismiss}
|
|
160
|
+
/>
|
|
161
|
+
|
|
162
|
+
</lib>
|
package/parts/card.htm
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<lib>
|
|
2
|
+
<:import src="base.htm" />
|
|
3
|
+
|
|
4
|
+
<!---
|
|
5
|
+
A card: parameters for the chrome, the slot for the content. Every region
|
|
6
|
+
other than the body is optional and only appears when its parameter is
|
|
7
|
+
given, which is the kit's general shape — a caller writes what the card
|
|
8
|
+
HAS, not the markup that produces it.
|
|
9
|
+
|
|
10
|
+
<bs-card :title="Card title" :footer="2 days ago">
|
|
11
|
+
<p class="card-text">Some quick example text.</p>
|
|
12
|
+
</bs-card>
|
|
13
|
+
|
|
14
|
+
The body is the one region a caller cannot reach with `:extra`, which
|
|
15
|
+
goes on the card itself -- hence `:bodyExtra`, for the padding Bootstrap
|
|
16
|
+
leaves to a utility class: `p-0` for a table or a code surface that runs
|
|
17
|
+
to the edges, `p-4` for a card that is mostly one paragraph.
|
|
18
|
+
-->
|
|
19
|
+
<:define tag="bs-card:div"
|
|
20
|
+
|
|
21
|
+
// parameters
|
|
22
|
+
:extra=${null} // extra classes for this instance
|
|
23
|
+
:bodyExtra=${null} // extra classes for the body: `p-0`, `p-4`, `d-flex`
|
|
24
|
+
:title=${null}
|
|
25
|
+
:subtitle=${null}
|
|
26
|
+
:header=${null}
|
|
27
|
+
:footer=${null}
|
|
28
|
+
:image=${null}
|
|
29
|
+
:imageAlt=${''}
|
|
30
|
+
:imageBottom=${false}
|
|
31
|
+
:variant=${null} // colours the whole card, e.g. 'primary'
|
|
32
|
+
:border=${null} // colours only the border
|
|
33
|
+
:align=${null} // 'center' | 'end'
|
|
34
|
+
|
|
35
|
+
// private
|
|
36
|
+
:_class=${[
|
|
37
|
+
'card',
|
|
38
|
+
variant ? `text-bg-${variant}` : '',
|
|
39
|
+
border ? `border-${border}` : '',
|
|
40
|
+
align ? `text-${align}` : '',
|
|
41
|
+
extra,
|
|
42
|
+
].filter(s => s).join(' ')}
|
|
43
|
+
|
|
44
|
+
class=${_class}
|
|
45
|
+
>
|
|
46
|
+
<img class="card-img-top"
|
|
47
|
+
:if=${image && !imageBottom}
|
|
48
|
+
src=${image}
|
|
49
|
+
alt=${imageAlt}>
|
|
50
|
+
|
|
51
|
+
<!---
|
|
52
|
+
A named slot inside an optional region: the header appears only when
|
|
53
|
+
there is one, and a caller can put markup in it rather than a string.
|
|
54
|
+
Both halves had to be built for this: a `<:slot>` cannot sit inside a
|
|
55
|
+
`:for-each`, and filling one nested inside a component's own scope used
|
|
56
|
+
to leave the fallback's binding pointing at markup that was gone.
|
|
57
|
+
-->
|
|
58
|
+
<div class="card-header"
|
|
59
|
+
:if=${header}><:slot name="header">${header}</:slot></div>
|
|
60
|
+
|
|
61
|
+
<div class=${bodyExtra ? `card-body ${bodyExtra}` : 'card-body'}>
|
|
62
|
+
<h5 class="card-title"
|
|
63
|
+
:if=${title}>${title}</h5>
|
|
64
|
+
|
|
65
|
+
<h6 class="card-subtitle mb-2 text-body-secondary"
|
|
66
|
+
:if=${subtitle}>${subtitle}</h6>
|
|
67
|
+
|
|
68
|
+
<:slot />
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="card-footer text-body-secondary"
|
|
72
|
+
:if=${footer}>${footer}</div>
|
|
73
|
+
|
|
74
|
+
<img class="card-img-bottom"
|
|
75
|
+
:if=${image && imageBottom}
|
|
76
|
+
src=${image}
|
|
77
|
+
alt=${imageAlt}>
|
|
78
|
+
</:define>
|
|
79
|
+
|
|
80
|
+
<!---
|
|
81
|
+
Equal-height, equal-width cards in a grid. Bootstrap wants the row
|
|
82
|
+
classes on a wrapper; this is that wrapper, named.
|
|
83
|
+
-->
|
|
84
|
+
<:define tag="bs-card-group:div"
|
|
85
|
+
|
|
86
|
+
// parameters
|
|
87
|
+
:extra=${null} // extra classes for this instance
|
|
88
|
+
:cols=${3}
|
|
89
|
+
:gap=${4}
|
|
90
|
+
:attached=${false} // a seamless strip instead of separate cards
|
|
91
|
+
|
|
92
|
+
// private
|
|
93
|
+
:_class=${attached
|
|
94
|
+
? 'card-group'
|
|
95
|
+
: `row row-cols-1 row-cols-md-${cols} g-${gap}`}
|
|
96
|
+
:_groupClass=${extra ? `${_class} ${extra}` : _class}
|
|
97
|
+
|
|
98
|
+
class=${_groupClass}
|
|
99
|
+
>
|
|
100
|
+
<:slot />
|
|
101
|
+
</:define>
|
|
102
|
+
|
|
103
|
+
</lib>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<lib>
|
|
2
|
+
<:import src="base.htm" />
|
|
3
|
+
|
|
4
|
+
<!---
|
|
5
|
+
A carousel from an array of slides. A slide is
|
|
6
|
+
`{ image, alt, title, text }`.
|
|
7
|
+
|
|
8
|
+
Controls and indicators both have to point at the carousel's id, which is
|
|
9
|
+
derived from `$id` — three places that have to agree, and the reason this
|
|
10
|
+
is worth a component at all.
|
|
11
|
+
-->
|
|
12
|
+
<:define tag="bs-carousel:div"
|
|
13
|
+
class="carousel slide ${extra ?? ''}"
|
|
14
|
+
|
|
15
|
+
// parameters
|
|
16
|
+
:extra=${null} // extra classes for this instance
|
|
17
|
+
:slides=${[]}
|
|
18
|
+
:controls=${true}
|
|
19
|
+
:indicators=${true}
|
|
20
|
+
:captions=${true}
|
|
21
|
+
:fade=${false}
|
|
22
|
+
:ride=${true}
|
|
23
|
+
:interval=${5000}
|
|
24
|
+
:dark=${false}
|
|
25
|
+
|
|
26
|
+
// private
|
|
27
|
+
:_id=${`bs-carousel-${$id}`}
|
|
28
|
+
:_selector=${`#bs-carousel-${$id}`}
|
|
29
|
+
/*
|
|
30
|
+
the position is needed three times per slide and `:for-each`
|
|
31
|
+
binds the item, not its index, so it is carried on the item
|
|
32
|
+
*/
|
|
33
|
+
:_slides=${slides.map((slide, i) => ({ ...slide, index: i }))}
|
|
34
|
+
|
|
35
|
+
id=${_id}
|
|
36
|
+
:class-carousel-fade=${fade}
|
|
37
|
+
data-bs-ride=${ride ? 'carousel' : null}
|
|
38
|
+
data-bs-interval=${interval}
|
|
39
|
+
data-bs-theme=${dark ? 'dark' : null}
|
|
40
|
+
>
|
|
41
|
+
<div class="carousel-indicators" :if=${indicators}>
|
|
42
|
+
<button type="button"
|
|
43
|
+
:for-each=${_slides}
|
|
44
|
+
:for-as="slide"
|
|
45
|
+
data-bs-target=${_selector}
|
|
46
|
+
data-bs-slide-to=${slide.index}
|
|
47
|
+
:class-active=${slide.index === 0}
|
|
48
|
+
aria-current=${slide.index === 0 ? 'true' : null}
|
|
49
|
+
aria-label="Slide ${slide.index + 1}"></button>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="carousel-inner">
|
|
53
|
+
<div class="carousel-item"
|
|
54
|
+
:for-each=${_slides}
|
|
55
|
+
:for-as="slide"
|
|
56
|
+
:class-active=${slide.index === 0}>
|
|
57
|
+
<img class="d-block w-100"
|
|
58
|
+
src=${slide.image}
|
|
59
|
+
alt=${slide.alt ?? ''}>
|
|
60
|
+
<div class="carousel-caption d-none d-md-block"
|
|
61
|
+
:if=${captions && (slide.title || slide.text)}>
|
|
62
|
+
<h5 :if=${slide.title}>${slide.title}</h5>
|
|
63
|
+
<p :if=${slide.text}>${slide.text}</p>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<button class="carousel-control-prev"
|
|
69
|
+
type="button"
|
|
70
|
+
:if=${controls}
|
|
71
|
+
data-bs-target=${_selector}
|
|
72
|
+
data-bs-slide="prev">
|
|
73
|
+
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
|
74
|
+
<span class="visually-hidden">Previous</span>
|
|
75
|
+
</button>
|
|
76
|
+
|
|
77
|
+
<button class="carousel-control-next"
|
|
78
|
+
type="button"
|
|
79
|
+
:if=${controls}
|
|
80
|
+
data-bs-target=${_selector}
|
|
81
|
+
data-bs-slide="next">
|
|
82
|
+
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
|
83
|
+
<span class="visually-hidden">Next</span>
|
|
84
|
+
</button>
|
|
85
|
+
</:define>
|
|
86
|
+
|
|
87
|
+
</lib>
|
package/parts/check.htm
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<lib>
|
|
2
|
+
<:import src="base.htm" />
|
|
3
|
+
|
|
4
|
+
<!---
|
|
5
|
+
Checkbox, radio and switch are one component: in Bootstrap a switch IS a
|
|
6
|
+
checkbox with `.form-switch` on the wrapper, and a radio differs only by
|
|
7
|
+
`type`. `:type` picks between them.
|
|
8
|
+
|
|
9
|
+
A group of radios shares one `:name`, as it does in plain HTML — that is
|
|
10
|
+
what makes them exclusive, and the kit doesn't hide it.
|
|
11
|
+
-->
|
|
12
|
+
<:define tag="bs-check:div"
|
|
13
|
+
|
|
14
|
+
// parameters
|
|
15
|
+
:extra=${null} // extra classes for this instance
|
|
16
|
+
:label=${null}
|
|
17
|
+
:type=${'checkbox'} // 'checkbox' | 'radio' | 'switch'
|
|
18
|
+
:name=${null}
|
|
19
|
+
:value=${null}
|
|
20
|
+
:checked=${false}
|
|
21
|
+
:inline=${false}
|
|
22
|
+
:reverse=${false}
|
|
23
|
+
:disabled=${false}
|
|
24
|
+
:help=${null}
|
|
25
|
+
|
|
26
|
+
// private
|
|
27
|
+
:_id=${`bs-check-${$id}`}
|
|
28
|
+
:_helpId=${`bs-check-help-${$id}`}
|
|
29
|
+
:_type=${type === 'switch' ? 'checkbox' : type}
|
|
30
|
+
|
|
31
|
+
class="form-check ${extra ?? ''}"
|
|
32
|
+
:class-form-switch=${type === 'switch'}
|
|
33
|
+
:class-form-check-inline=${inline}
|
|
34
|
+
:class-form-check-reverse=${reverse}
|
|
35
|
+
>
|
|
36
|
+
<input class="form-check-input"
|
|
37
|
+
id=${_id}
|
|
38
|
+
type=${_type}
|
|
39
|
+
name=${name}
|
|
40
|
+
value=${value}
|
|
41
|
+
role=${type === 'switch' ? 'switch' : null}
|
|
42
|
+
aria-describedby=${help ? _helpId : null}
|
|
43
|
+
:attr-checked=${checked}
|
|
44
|
+
:attr-disabled=${disabled}
|
|
45
|
+
:on-change=${ev => checked = ev.target.checked}>
|
|
46
|
+
|
|
47
|
+
<label class="form-check-label"
|
|
48
|
+
for=${_id}>${label}</label>
|
|
49
|
+
|
|
50
|
+
<div class="form-text"
|
|
51
|
+
id=${_helpId}
|
|
52
|
+
:if=${help}>${help}</div>
|
|
53
|
+
</:define>
|
|
54
|
+
|
|
55
|
+
<!---
|
|
56
|
+
A whole group of radios or checkboxes from one array, sharing a name and
|
|
57
|
+
reporting the selection back through `:value`. Entries are strings or
|
|
58
|
+
`{ name, value, disabled }`, as in `bs-select`.
|
|
59
|
+
-->
|
|
60
|
+
<:define tag="bs-check-group:div"
|
|
61
|
+
class="mb-3 ${extra ?? ''}"
|
|
62
|
+
role="group"
|
|
63
|
+
|
|
64
|
+
// parameters
|
|
65
|
+
:extra=${null} // extra classes for this instance
|
|
66
|
+
:legend=${null}
|
|
67
|
+
:type=${'radio'}
|
|
68
|
+
:options=${[]}
|
|
69
|
+
:value=${null}
|
|
70
|
+
:inline=${false}
|
|
71
|
+
:disabled=${false}
|
|
72
|
+
|
|
73
|
+
// private
|
|
74
|
+
:_name=${`bs-check-group-${$id}`}
|
|
75
|
+
:_legendId=${`bs-check-group-legend-${$id}`}
|
|
76
|
+
|
|
77
|
+
aria-labelledby=${legend ? _legendId : null}
|
|
78
|
+
:_options=${options.map(o =>
|
|
79
|
+
typeof o === 'object' && o !== null
|
|
80
|
+
? { name: o.name, value: o.value ?? o.name, disabled: !!o.disabled }
|
|
81
|
+
: { name: o, value: o, disabled: false })}
|
|
82
|
+
>
|
|
83
|
+
<!---
|
|
84
|
+
A `<div>` rather than `<fieldset>`/`<legend>`: Bootstrap floats a
|
|
85
|
+
legend, and a floated check input beside it wraps up onto the same
|
|
86
|
+
line. `role="group"` plus `aria-labelledby` says the same thing to a
|
|
87
|
+
screen reader without the float.
|
|
88
|
+
-->
|
|
89
|
+
<div class="form-label fs-6"
|
|
90
|
+
id=${_legendId}
|
|
91
|
+
:if=${legend}>${legend}</div>
|
|
92
|
+
|
|
93
|
+
<div class="form-check"
|
|
94
|
+
:for-each=${_options}
|
|
95
|
+
:for-as="option"
|
|
96
|
+
:class-form-switch=${type === 'switch'}
|
|
97
|
+
:class-form-check-inline=${inline}>
|
|
98
|
+
<input class="form-check-input"
|
|
99
|
+
id="${_name}-${option.value}"
|
|
100
|
+
type=${type === 'switch' ? 'checkbox' : type}
|
|
101
|
+
name=${_name}
|
|
102
|
+
value=${option.value}
|
|
103
|
+
:attr-checked=${option.value === value}
|
|
104
|
+
:attr-disabled=${disabled || option.disabled}
|
|
105
|
+
:on-change=${ev => value = ev.target.value}>
|
|
106
|
+
<label class="form-check-label"
|
|
107
|
+
for="${_name}-${option.value}">${option.name}</label>
|
|
108
|
+
</div>
|
|
109
|
+
</:define>
|
|
110
|
+
|
|
111
|
+
<!--- A range slider, with its label and its current value. -->
|
|
112
|
+
<:define tag="bs-range:div"
|
|
113
|
+
class="mb-3 ${extra ?? ''}"
|
|
114
|
+
|
|
115
|
+
// parameters
|
|
116
|
+
:extra=${null} // extra classes for this instance
|
|
117
|
+
:label=${null}
|
|
118
|
+
:name=${null}
|
|
119
|
+
:min=${0}
|
|
120
|
+
:max=${100}
|
|
121
|
+
:step=${1}
|
|
122
|
+
:value=${50}
|
|
123
|
+
:disabled=${false}
|
|
124
|
+
:showValue=${false}
|
|
125
|
+
|
|
126
|
+
// private
|
|
127
|
+
:_id=${`bs-range-${$id}`}
|
|
128
|
+
>
|
|
129
|
+
<!---
|
|
130
|
+
The label row, which either half can be alone in: `:showValue` with no
|
|
131
|
+
`:label` used to render nothing at all, since the row was gated on the
|
|
132
|
+
label.
|
|
133
|
+
|
|
134
|
+
A row rather than a `<label>` wrapping both, so the readout stays out
|
|
135
|
+
of the accessible name -- a label whose text is "50" is worse than no
|
|
136
|
+
label, and the range input announces its own value anyway. The
|
|
137
|
+
readout keeps its place at the right when it is the only thing here --
|
|
138
|
+
by swapping the justification rather than adding to it, since
|
|
139
|
+
Bootstrap emits `justify-content-end` before `justify-content-between`
|
|
140
|
+
and the two together leave `between` winning.
|
|
141
|
+
-->
|
|
142
|
+
<div class="form-label d-flex"
|
|
143
|
+
:class-justify-content-between=${label}
|
|
144
|
+
:class-justify-content-end=${!label}
|
|
145
|
+
:if=${label || showValue}>
|
|
146
|
+
<label for=${_id}
|
|
147
|
+
:if=${label}>${label}</label>
|
|
148
|
+
<span class="text-body-secondary"
|
|
149
|
+
:if=${showValue}>${value}</span>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<input class="form-range"
|
|
153
|
+
id=${_id}
|
|
154
|
+
type="range"
|
|
155
|
+
name=${name}
|
|
156
|
+
min=${min}
|
|
157
|
+
max=${max}
|
|
158
|
+
step=${step}
|
|
159
|
+
value=${value}
|
|
160
|
+
:attr-disabled=${disabled}
|
|
161
|
+
:on-input=${ev => value = Number(ev.target.value)}>
|
|
162
|
+
</:define>
|
|
163
|
+
|
|
164
|
+
</lib>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<lib>
|
|
2
|
+
<:import src="base.htm" />
|
|
3
|
+
|
|
4
|
+
<!---
|
|
5
|
+
A collapsible region. The trigger is deliberately not part of it —
|
|
6
|
+
anything can open a collapse, and often several things do — so the region
|
|
7
|
+
takes a `:name` and a `bs-button` points at it:
|
|
8
|
+
|
|
9
|
+
<bs-button :toggle="collapse" :target="#details">Details</bs-button>
|
|
10
|
+
<bs-collapse :name="details">…</bs-collapse>
|
|
11
|
+
|
|
12
|
+
`:horizontal` collapses width instead of height, in which case Bootstrap
|
|
13
|
+
needs the content to have a width of its own.
|
|
14
|
+
-->
|
|
15
|
+
<:define tag="bs-collapse:div"
|
|
16
|
+
class="collapse ${extra ?? ''}"
|
|
17
|
+
|
|
18
|
+
// parameters
|
|
19
|
+
:extra=${null} // extra classes for this instance
|
|
20
|
+
:name=${null}
|
|
21
|
+
:open=${false}
|
|
22
|
+
:horizontal=${false}
|
|
23
|
+
|
|
24
|
+
id=${name}
|
|
25
|
+
:class-show=${open}
|
|
26
|
+
:class-collapse-horizontal=${horizontal}
|
|
27
|
+
>
|
|
28
|
+
<:slot />
|
|
29
|
+
</:define>
|
|
30
|
+
|
|
31
|
+
</lib>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<lib>
|
|
2
|
+
<:import src="base.htm" />
|
|
3
|
+
|
|
4
|
+
<!---
|
|
5
|
+
A dropdown. Entries are `{ name, link, active, disabled, header, divider,
|
|
6
|
+
select }`; the three shapes Bootstrap draws differently — a divider, a
|
|
7
|
+
header, an item — are three optional regions over the same list.
|
|
8
|
+
|
|
9
|
+
The menu is tied to its toggle by `aria-labelledby`, and the id for that
|
|
10
|
+
comes from `$id`, which is unique per instance. That is the general
|
|
11
|
+
answer in this kit to "this widget needs an id": derive it, never ask the
|
|
12
|
+
caller for one, unless another component has to name it (see
|
|
13
|
+
`bs-accordion`).
|
|
14
|
+
-->
|
|
15
|
+
<:define tag="bs-dropdown:div"
|
|
16
|
+
|
|
17
|
+
// parameters
|
|
18
|
+
:extra=${null} // extra classes for this instance
|
|
19
|
+
:label=${'Dropdown'}
|
|
20
|
+
:items=${[
|
|
21
|
+
{ name: 'Action', link: '#' },
|
|
22
|
+
{ name: 'Another action', link: '#' },
|
|
23
|
+
{ divider: true },
|
|
24
|
+
{ name: 'Something else here', link: '#' },
|
|
25
|
+
]}
|
|
26
|
+
:variant=${'primary'}
|
|
27
|
+
:outline=${false}
|
|
28
|
+
:size=${null}
|
|
29
|
+
:split=${false}
|
|
30
|
+
:direction=${'down'} // 'down' | 'up' | 'start' | 'end'
|
|
31
|
+
:align=${'start'} // 'start' | 'end'
|
|
32
|
+
:dark=${false}
|
|
33
|
+
|
|
34
|
+
// private
|
|
35
|
+
:_id=${`bs-dropdown-${$id}`}
|
|
36
|
+
:_class=${[direction === 'down' ? 'dropdown' : `drop${direction}`, extra]
|
|
37
|
+
.filter(s => s).join(' ')}
|
|
38
|
+
:_btnClass=${[
|
|
39
|
+
'btn',
|
|
40
|
+
`btn-${outline ? 'outline-' : ''}${variant}`,
|
|
41
|
+
size ? `btn-${size}` : '',
|
|
42
|
+
].filter(s => s).join(' ')}
|
|
43
|
+
|
|
44
|
+
class=${_class}
|
|
45
|
+
:class-btn-group=${split}
|
|
46
|
+
>
|
|
47
|
+
<!--- a split button keeps the label clickable on its own -->
|
|
48
|
+
<button type="button"
|
|
49
|
+
class=${_btnClass}
|
|
50
|
+
:if=${split}>${label}</button>
|
|
51
|
+
|
|
52
|
+
<button type="button"
|
|
53
|
+
id=${_id}
|
|
54
|
+
class=${_btnClass}
|
|
55
|
+
:class-dropdown-toggle
|
|
56
|
+
:class-dropdown-toggle-split=${split}
|
|
57
|
+
data-bs-toggle="dropdown"
|
|
58
|
+
aria-expanded="false">
|
|
59
|
+
<span :if=${!split}>${label}</span>
|
|
60
|
+
<span class="visually-hidden"
|
|
61
|
+
:else>Toggle dropdown</span>
|
|
62
|
+
</button>
|
|
63
|
+
|
|
64
|
+
<ul class="dropdown-menu"
|
|
65
|
+
:class-dropdown-menu-end=${align === 'end'}
|
|
66
|
+
data-bs-theme=${dark ? 'dark' : null}
|
|
67
|
+
aria-labelledby=${_id}>
|
|
68
|
+
<li :for-each=${items} :for-as="item">
|
|
69
|
+
<hr class="dropdown-divider"
|
|
70
|
+
:if=${item.divider}>
|
|
71
|
+
|
|
72
|
+
<h6 class="dropdown-header"
|
|
73
|
+
:else-if=${item.header}>${item.header}</h6>
|
|
74
|
+
|
|
75
|
+
<a class="dropdown-item"
|
|
76
|
+
:else
|
|
77
|
+
:class-active=${item.active}
|
|
78
|
+
:class-disabled=${item.disabled}
|
|
79
|
+
href=${item.link ?? null}
|
|
80
|
+
aria-current=${item.active ? 'true' : null}
|
|
81
|
+
:on-click=${() => item.select?.(item)}>${item.name}</a>
|
|
82
|
+
</li>
|
|
83
|
+
</ul>
|
|
84
|
+
</:define>
|
|
85
|
+
|
|
86
|
+
</lib>
|