@markout-lang/bootstrap-kit 0.3.0 → 0.4.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 CHANGED
@@ -24,8 +24,15 @@ logical root it declares. See [npm kits](../../docs/design/npm-kits.md) —
24
24
  including the other case, a kit vendored into a docroot, which is imported by
25
25
  its path instead.
26
26
 
27
- `all.htm` pulls in everything. Each part imports `base.htm` itself and a file
28
- is only imported once per page, so importing parts by hand never leaves
27
+ `all.htm` pulls in everything, and that is the ordinary thing to import: a
28
+ component no tag on the page uses is dropped before the page is served, so
29
+ what the whole kit costs is what you actually reach for. A page importing all
30
+ of it and using one `bs-alert` serves 4.7KB where the same page with that
31
+ pass turned off is 19.8KB.
32
+
33
+ Importing parts by hand is for when you want to be explicit rather than to
34
+ save weight — it is worth 134 bytes on that page. Each part imports
35
+ `base.htm` itself and a file is only imported once, so it never leaves
29
36
  Bootstrap out:
30
37
 
31
38
  ```html
@@ -33,6 +40,20 @@ Bootstrap out:
33
40
  <:import src="/npm/@markout-lang/bootstrap-kit/parts/card.htm" />
34
41
  ```
35
42
 
43
+ Those 134 bytes are the pass being careful rather than a component sneaking
44
+ through, and they are two stencils: `bs-alert` names `<bs-close>` in its
45
+ body, `bs-close` lives in `button.htm`, and so `bs-button` and `bs-badge`
46
+ count as used. A tag named inside another definition's body is kept even
47
+ when that definition is itself dropped — narrowing that needs the usage
48
+ graph rather than a flat set, and getting it wrong deletes markup a page
49
+ needs.
50
+
51
+ Both pages there do the same things, which is what makes the comparison mean
52
+ anything. `theme.htm` used to be the exception: importing `all.htm` brought a
53
+ pre-paint script the hand-picked page never got, so the whole-kit page was
54
+ 278 bytes larger for doing something more. Colour modes are opt-in now, so
55
+ that difference belongs to whichever page asks for them.
56
+
36
57
  Every component below is shown one after another in the [kitchen
37
58
  sink](https://markout.dev/demos/kitchen-sink), and
38
59
  [Orbit](https://markout.dev/demos/orbit) is an operations dashboard built out
@@ -65,15 +86,22 @@ all of them.
65
86
  **Parameters for the chrome, the slot for the content.** What a card *has* —
66
87
  a title, a footer, an image — is a parameter. What it *contains* is slotted.
67
88
 
68
- **`::extra` adds classes.** A `class` written at a usage site *replaces* the
89
+ **`class+=` adds classes.** A `class` written at a usage site *replaces* the
69
90
  one a definition sets, which is the language's rule and not something this
70
- kit overrides. So every component takes `::extra` for the utility classes a
71
- caller wants on top:
91
+ kit overrides and since every component here computes its own, writing one
92
+ gets you a warning naming `class+=`. That is the spelling to reach for:
72
93
 
73
94
  ```html
74
- <bs-alert ::variant="warning" ::extra="mb-0">Careful</bs-alert>
95
+ <bs-alert ::variant="warning" class+="mb-0">Careful</bs-alert>
96
+ <bs-alert ::dismissible class-="fade">No animation, please</bs-alert>
75
97
  ```
76
98
 
99
+ Every component used to declare an `::extra` parameter for this, hand-rolled
100
+ into its own class list — 28 files agreeing on a convention the language now
101
+ has a spelling for. `::bodyExtra` on `bs-card` stays, and says why the rest
102
+ went: `class+=` reaches a component's own element, and the card's *body* is a
103
+ different element.
104
+
77
105
  **Comments in a tag: `//` for one line, `/* … */` for more.** Both are
78
106
  stripped at parse time. A run of `//` lines reads as a stack of fragments;
79
107
  one block says it once.
@@ -215,13 +243,21 @@ variables, so restyling everything is setting one value at the import site:
215
243
  | `bsFontSans` | Bootstrap's system stack |
216
244
  | `bsLinkDecoration` | `underline` |
217
245
 
218
- Colour modes are `theme.htm`: an inline pre-paint script so the page never
219
- flashes the wrong mode, and `<bs-theme-toggle />` to switch it.
246
+ Colour modes are `theme.htm`, and they are opt-in: write `<bs-theme-auto />`
247
+ for the behaviour on its own the page follows a stored choice, or the
248
+ system — or a `<bs-theme-toggle />` for the behaviour and a button to drive
249
+ it. Either keeps the inline pre-paint script that stops the page flashing the
250
+ wrong mode; a page that writes neither ships neither, even having imported
251
+ the whole kit.
252
+
253
+ That is `:when-used` doing what it is for. Before it, importing `all.htm`
254
+ gave every page colour modes whether it had asked or not, which is the one
255
+ thing in this kit that DID something merely because a file was imported.
220
256
 
221
257
  ## Components
222
258
 
223
- Every tag also takes `::extra`. Defaults are in the definitions, which are
224
- commented.
259
+ Every tag takes `class+=` and `class-=`, which are the language's and not
260
+ listed here. Defaults are in the definitions, which are commented.
225
261
 
226
262
  ### Content
227
263
 
@@ -239,13 +275,29 @@ commented.
239
275
  | `bs-textarea` | `label` `name` `value` `placeholder` `rows` `help` `disabled` `readonly` `required` `check` `message` |
240
276
  | `bs-select` | `label` `name` `options` `value` `placeholder` `help` `size` `multiple` `disabled` `required` `floating` |
241
277
  | `bs-check` | `label` `type` (`checkbox`/`radio`/`switch`) `name` `value` `checked` `inline` `reverse` `disabled` `help` |
242
- | `bs-check-group` | `legend` `type` `options` `value` `inline` `disabled` |
278
+ | `bs-check-group` | `legend` `type` `options` `value` (see below) `inline` `disabled` |
243
279
  | `bs-range` | `label` `name` `min` `max` `step` `value` `disabled` `showValue` |
244
280
  | `bs-input-group` | `prefix` `suffix` `size` |
245
281
 
246
282
  `bs-input` and `bs-textarea` also answer `valid`, which is read rather than
247
283
  passed -- see *Values are read and written* above.
248
284
 
285
+ **`bs-check-group`'s `::value` is decided by its `::type`**, because that is
286
+ what the two controls mean: a radio group submits one value and a checkbox
287
+ group submits every box that is ticked, under the same name. So a radio
288
+ group's is the selected value or `null`, and a checkbox or switch group's is
289
+ an **array**, in the order the options were given rather than the order they
290
+ were clicked:
291
+
292
+ ```html
293
+ <bs-check-group :aka="channels"
294
+ ::type="checkbox"
295
+ ::value=${['Slack']}
296
+ ::options=${['Email', 'Slack', 'SMS']} />
297
+
298
+ Deliver to ${channels.value.join(', ')}
299
+ ```
300
+
249
301
  ### Components
250
302
 
251
303
  | Tag | Parameters |
@@ -278,6 +330,7 @@ passed -- see *Values are read and written* above.
278
330
  | `bs-progress-stacked` | — |
279
331
  | `bs-scrollspy` | `target` `offset` `smooth` `height` |
280
332
  | `bs-spinner` | `variant` `type` `small` `label` |
333
+ | `bs-theme-auto` | — (declares that the page uses colour modes) |
281
334
  | `bs-theme-toggle` | `variant` `outline` `size` |
282
335
  | `bs-toast` | `title` `time` `variant` `open` `autohide` `delay` |
283
336
  | `bs-toast-container` | `placement` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markout-lang/bootstrap-kit",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Bootstrap 5.3 as Markout components: one part per component, with the id wiring, the ARIA attributes and the repetition written once",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "homepage": "https://markout.dev",
14
14
  "peerDependencies": {
15
- "@markout-lang/core": "^0.5.0"
15
+ "@markout-lang/core": ">=0.5.0 <1.0.0"
16
16
  },
17
17
  "markout": {
18
18
  "root": "/bootstrap-kit"
@@ -25,10 +25,9 @@
25
25
  collide and nothing has to be named.
26
26
  -->
27
27
  <:define tag="bs-accordion:div"
28
- class="accordion ${extra ?? ''}"
28
+ class="accordion"
29
29
 
30
30
  // parameters
31
- ::extra=${null} // extra classes for this instance
32
31
  ::exclusive=${true} // one open panel at a time
33
32
  ::flush=${false}
34
33
 
@@ -45,10 +44,9 @@
45
44
  </:define>
46
45
 
47
46
  <:define tag="bs-accordion-item:div"
48
- class="accordion-item ${extra ?? ''}"
47
+ class="accordion-item"
49
48
 
50
49
  // parameters
51
- ::extra=${null} // extra classes for this instance
52
50
  ::title=${''}
53
51
  ::open=${false}
54
52
 
package/parts/alert.htm CHANGED
@@ -22,13 +22,12 @@
22
22
  role="alert"
23
23
 
24
24
  // parameters
25
- ::extra=${null} // extra classes for this instance
26
25
  ::variant=${'primary'}
27
26
  ::heading=${null}
28
27
  ::dismissible=${false}
29
28
 
30
29
  // private
31
- :_class=${['alert', `alert-${variant}`, extra].filter(s => s).join(' ')}
30
+ :_class=${`alert alert-${variant}`}
32
31
 
33
32
  class=${_class}
34
33
  :class-alert-dismissible=${dismissible}
package/parts/badge.htm CHANGED
@@ -9,7 +9,6 @@
9
9
  <:define tag="bs-badge:span"
10
10
 
11
11
  // parameters
12
- ::extra=${null} // extra classes for this instance
13
12
  ::variant=${'primary'}
14
13
  ::pill=${false}
15
14
  ::position=${null} // 'top-end' | 'bottom-end' — for a badge on a button
@@ -22,7 +21,6 @@
22
21
  position ? 'position-absolute translate-middle' : '',
23
22
  position === 'top-end' ? 'top-0 start-100' : '',
24
23
  position === 'bottom-end' ? 'top-100 start-100' : '',
25
- extra,
26
24
  ].filter(s => s).join(' ')}
27
25
 
28
26
  class=${_class}
@@ -12,10 +12,8 @@
12
12
  -->
13
13
  <:define tag="bs-breadcrumb:nav"
14
14
  aria-label="breadcrumb"
15
- class=${extra}
16
15
 
17
16
  // parameters
18
- ::extra=${null} // extra classes for this instance
19
17
  ::items=${[
20
18
  { name: 'Home', link: '#' },
21
19
  { name: 'Library', link: '#' },
package/parts/button.htm CHANGED
@@ -20,7 +20,6 @@
20
20
  <:define tag="bs-button:button"
21
21
 
22
22
  // parameters
23
- ::extra=${null} // extra classes for this instance
24
23
  ::variant=${'primary'}
25
24
  ::outline=${false}
26
25
  ::size=${null}
@@ -36,7 +35,6 @@
36
35
  'btn',
37
36
  variant ? `btn-${outline ? 'outline-' : ''}${variant}` : '',
38
37
  size ? `btn-${size}` : '',
39
- extra,
40
38
  ].filter(s => s).join(' ')}
41
39
 
42
40
  class=${_class}
@@ -59,7 +57,6 @@
59
57
  <:define tag="bs-link:a"
60
58
 
61
59
  // parameters
62
- ::extra=${null} // extra classes for this instance
63
60
  ::href=${'#'}
64
61
  ::variant=${null}
65
62
  ::outline=${false}
@@ -76,7 +73,6 @@
76
73
  button && variant ? `btn-${outline ? 'outline-' : ''}${variant}` : '',
77
74
  button && size ? `btn-${size}` : '',
78
75
  !button && variant ? `link-${variant}` : '',
79
- extra,
80
76
  ].filter(s => s).join(' ') || null}
81
77
 
82
78
  class=${_class}
@@ -99,7 +95,6 @@
99
95
  <:define tag="bs-button-group:div"
100
96
 
101
97
  // parameters
102
- ::extra=${null} // extra classes for this instance
103
98
  ::label=${'Button group'}
104
99
  ::size=${null}
105
100
  ::vertical=${false}
@@ -108,7 +103,6 @@
108
103
  :_class=${[
109
104
  vertical ? 'btn-group-vertical' : 'btn-group',
110
105
  size ? `btn-group-${size}` : '',
111
- extra,
112
106
  ].filter(s => s).join(' ')}
113
107
 
114
108
  class=${_class}
@@ -123,11 +117,10 @@
123
117
  buttons inside one, and this is the wrapper that says so.
124
118
  -->
125
119
  <:define tag="bs-button-toolbar:div"
126
- class="btn-toolbar ${extra ?? ''}"
120
+ class="btn-toolbar"
127
121
  role="toolbar"
128
122
 
129
123
  // parameters
130
- ::extra=${null} // extra classes for this instance
131
124
  ::label=${'Toolbar'}
132
125
  ::gap=${2}
133
126
 
@@ -146,10 +139,9 @@
146
139
  -->
147
140
  <:define tag="bs-close:button"
148
141
  type="button"
149
- class="btn-close ${extra ?? ''}"
142
+ class="btn-close"
150
143
 
151
144
  // parameters
152
- ::extra=${null} // extra classes for this instance
153
145
  ::label=${'Close'}
154
146
  ::dismiss=${null}
155
147
  ::disabled=${false}
package/parts/card.htm CHANGED
@@ -11,15 +11,14 @@
11
11
  <p class="card-text">Some quick example text.</p>
12
12
  </bs-card>
13
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
14
+ The body is the one region a caller cannot reach with `class+=`, which
15
+ goes on the card itself -- hence `::bodyExtra`, for the padding Bootstrap
16
16
  leaves to a utility class: `p-0` for a table or a code surface that runs
17
17
  to the edges, `p-4` for a card that is mostly one paragraph.
18
18
  -->
19
19
  <:define tag="bs-card:div"
20
20
 
21
21
  // parameters
22
- ::extra=${null} // extra classes for this instance
23
22
  ::bodyExtra=${null} // extra classes for the body: `p-0`, `p-4`, `d-flex`
24
23
  ::title=${null}
25
24
  ::subtitle=${null}
@@ -38,7 +37,6 @@
38
37
  variant ? `text-bg-${variant}` : '',
39
38
  border ? `border-${border}` : '',
40
39
  align ? `text-${align}` : '',
41
- extra,
42
40
  ].filter(s => s).join(' ')}
43
41
 
44
42
  class=${_class}
@@ -84,7 +82,6 @@
84
82
  <:define tag="bs-card-group:div"
85
83
 
86
84
  // parameters
87
- ::extra=${null} // extra classes for this instance
88
85
  ::cols=${3}
89
86
  ::gap=${4}
90
87
  ::attached=${false} // a seamless strip instead of separate cards
@@ -93,9 +90,7 @@
93
90
  :_class=${attached
94
91
  ? 'card-group'
95
92
  : `row row-cols-1 row-cols-md-${cols} g-${gap}`}
96
- :_groupClass=${extra ? `${_class} ${extra}` : _class}
97
-
98
- class=${_groupClass}
93
+ class=${_class}
99
94
  >
100
95
  <:slot />
101
96
  </:define>
@@ -10,10 +10,9 @@
10
10
  is worth a component at all.
11
11
  -->
12
12
  <:define tag="bs-carousel:div"
13
- class="carousel slide ${extra ?? ''}"
13
+ class="carousel slide"
14
14
 
15
15
  // parameters
16
- ::extra=${null} // extra classes for this instance
17
16
  ::slides=${[]}
18
17
  ::controls=${true}
19
18
  ::indicators=${true}
package/parts/check.htm CHANGED
@@ -12,7 +12,6 @@
12
12
  <:define tag="bs-check:div"
13
13
 
14
14
  // parameters
15
- ::extra=${null} // extra classes for this instance
16
15
  ::label=${null}
17
16
  ::type=${'checkbox'} // 'checkbox' | 'radio' | 'switch'
18
17
  ::name=${null}
@@ -28,7 +27,7 @@
28
27
  :_helpId=${`bs-check-help-${$id}`}
29
28
  :_type=${type === 'switch' ? 'checkbox' : type}
30
29
 
31
- class="form-check ${extra ?? ''}"
30
+ class="form-check"
32
31
  :class-form-switch=${type === 'switch'}
33
32
  :class-form-check-inline=${inline}
34
33
  :class-form-check-reverse=${reverse}
@@ -41,6 +40,7 @@
41
40
  role=${type === 'switch' ? 'switch' : null}
42
41
  aria-describedby=${help ? _helpId : null}
43
42
  :attr-checked=${checked}
43
+ :prop-checked=${checked}
44
44
  :attr-disabled=${disabled}
45
45
  :on-change=${ev => checked = ev.target.checked}>
46
46
 
@@ -56,23 +56,41 @@
56
56
  A whole group of radios or checkboxes from one array, sharing a name and
57
57
  reporting the selection back through `:value`. Entries are strings or
58
58
  `{ name, value, disabled }`, as in `bs-select`.
59
+
60
+ What `:value` HOLDS is decided by `:type`, because that is what the two
61
+ controls mean: a radio group submits one value and a checkbox group
62
+ submits every box that is ticked, under the same name. So a radio group's
63
+ `:value` is the selected value or `null`, and a checkbox or switch
64
+ group's is an ARRAY, in the order the options were given rather than the
65
+ order they were clicked — a summary sentence reading it should not
66
+ reorder itself under the reader.
67
+
68
+ It used to be one value either way, which made a checkbox group behave
69
+ like a radio group that could not make up its mind: ticking a second box
70
+ unticked the first, and unticking a box left it ticked. Not a shape
71
+ guessed from a value — `:type` is declared, and it is the same thing that
72
+ already picks the input's type and the switch class.
59
73
  -->
60
74
  <:define tag="bs-check-group:div"
61
- class="mb-3 ${extra ?? ''}"
75
+ class="mb-3"
62
76
  role="group"
63
77
 
64
78
  // parameters
65
- ::extra=${null} // extra classes for this instance
66
79
  ::legend=${null}
67
80
  ::type=${'radio'}
68
81
  ::options=${[]}
69
- ::value=${null}
82
+ ::value=${type === 'radio' ? null : []}
70
83
  ::inline=${false}
71
84
  ::disabled=${false}
72
85
 
73
86
  // private
74
87
  :_name=${`bs-check-group-${$id}`}
75
88
  :_legendId=${`bs-check-group-legend-${$id}`}
89
+ :_multi=${type !== 'radio'}
90
+ // a lone value read as a list of one, the way `:options` reads a lone
91
+ // string as an entry: passing `:value="Slack"` to a checkbox group says
92
+ // something unambiguous, and spreading a string into characters is not it
93
+ :_selected=${!_multi ? [] : Array.isArray(value) ? value : value == null ? [] : [value]}
76
94
 
77
95
  aria-labelledby=${legend ? _legendId : null}
78
96
  :_options=${options.map(o =>
@@ -100,9 +118,17 @@
100
118
  type=${type === 'switch' ? 'checkbox' : type}
101
119
  name=${_name}
102
120
  value=${option.value}
103
- :attr-checked=${option.value === value}
121
+ :attr-checked=${_multi ? _selected.includes(option.value) : option.value === value}
122
+ :prop-checked=${_multi ? _selected.includes(option.value) : option.value === value}
104
123
  :attr-disabled=${disabled || option.disabled}
105
- :on-change=${ev => value = ev.target.value}>
124
+ :on-change=${(ev) => {
125
+ if (!_multi) { value = ev.target.value; return; }
126
+ const on = ev.target.checked;
127
+ const hit = ev.target.value;
128
+ value = _options
129
+ .filter(o => (o.value === hit ? on : _selected.includes(o.value)))
130
+ .map(o => o.value);
131
+ }}>
106
132
  <label class="form-check-label"
107
133
  for="${_name}-${option.value}">${option.name}</label>
108
134
  </div>
@@ -110,10 +136,9 @@
110
136
 
111
137
  <!--- A range slider, with its label and its current value. -->
112
138
  <:define tag="bs-range:div"
113
- class="mb-3 ${extra ?? ''}"
139
+ class="mb-3"
114
140
 
115
141
  // parameters
116
- ::extra=${null} // extra classes for this instance
117
142
  ::label=${null}
118
143
  ::name=${null}
119
144
  ::min=${0}
@@ -157,6 +182,7 @@
157
182
  max=${max}
158
183
  step=${step}
159
184
  value=${value}
185
+ :prop-value=${value}
160
186
  :attr-disabled=${disabled}
161
187
  :on-input=${ev => value = Number(ev.target.value)}>
162
188
  </:define>
@@ -13,10 +13,9 @@
13
13
  needs the content to have a width of its own.
14
14
  -->
15
15
  <:define tag="bs-collapse:div"
16
- class="collapse ${extra ?? ''}"
16
+ class="collapse"
17
17
 
18
18
  // parameters
19
- ::extra=${null} // extra classes for this instance
20
19
  ::name=${null}
21
20
  ::open=${false}
22
21
  ::horizontal=${false}
@@ -15,7 +15,6 @@
15
15
  <:define tag="bs-dropdown:div"
16
16
 
17
17
  // parameters
18
- ::extra=${null} // extra classes for this instance
19
18
  ::label=${'Dropdown'}
20
19
  ::items=${[
21
20
  { name: 'Action', link: '#' },
@@ -33,7 +32,7 @@
33
32
 
34
33
  // private
35
34
  :_id=${`bs-dropdown-${$id}`}
36
- :_class=${[direction === 'down' ? 'dropdown' : `drop${direction}`, extra]
35
+ :_class=${[direction === 'down' ? 'dropdown' : `drop${direction}`]
37
36
  .filter(s => s).join(' ')}
38
37
  :_btnClass=${[
39
38
  'btn',
package/parts/image.htm CHANGED
@@ -8,14 +8,12 @@
8
8
  <:define tag="bs-image:img"
9
9
 
10
10
  // parameters
11
- ::extra=${null} // extra classes for this instance
12
11
  ::src=${null}
13
12
  ::alt=${''}
14
13
  ::fluid=${true}
15
14
  ::thumbnail=${false}
16
15
  ::rounded=${false}
17
16
 
18
- class=${extra}
19
17
  src=${src}
20
18
  alt=${alt}
21
19
  :class-img-fluid=${fluid}
@@ -25,10 +23,9 @@
25
23
 
26
24
  <!--- An image with its caption, as one thing. -->
27
25
  <:define tag="bs-figure:figure"
28
- class="figure ${extra ?? ''}"
26
+ class="figure"
29
27
 
30
28
  // parameters
31
- ::extra=${null} // extra classes for this instance
32
29
  ::src=${null}
33
30
  ::alt=${''}
34
31
  ::caption=${null}
package/parts/input.htm CHANGED
@@ -35,10 +35,9 @@
35
35
  layouts are two regions rather than one with a class toggled on it.
36
36
  -->
37
37
  <:define tag="bs-input:div"
38
- class="mb-3 ${extra ?? ''}"
38
+ class="mb-3"
39
39
 
40
40
  // parameters
41
- ::extra=${null} // extra classes for this instance
42
41
  ::label=${null}
43
42
  ::type=${'text'}
44
43
  ::name=${null}
@@ -75,6 +74,7 @@
75
74
  type=${type}
76
75
  name=${name}
77
76
  value=${value}
77
+ :prop-value=${value}
78
78
  placeholder=${placeholder}
79
79
  aria-describedby=${help ? _helpId : null}
80
80
  :class-is-invalid=${_invalid}
@@ -97,6 +97,7 @@
97
97
  type=${type}
98
98
  name=${name}
99
99
  value=${value}
100
+ :prop-value=${value}
100
101
  placeholder=${placeholder || label || ' '}
101
102
  :class-is-invalid=${_invalid}
102
103
  :attr-disabled=${disabled}
@@ -116,10 +117,9 @@
116
117
  `rows`, and a type that isn't one of HTML's would be a small lie.
117
118
  -->
118
119
  <:define tag="bs-textarea:div"
119
- class="mb-3 ${extra ?? ''}"
120
+ class="mb-3"
120
121
 
121
122
  // parameters
122
- ::extra=${null} // extra classes for this instance
123
123
  ::label=${null}
124
124
  ::name=${null}
125
125
  ::value=${''}
@@ -155,6 +155,7 @@
155
155
  :attr-disabled=${disabled}
156
156
  :attr-readonly=${readonly}
157
157
  :attr-required=${required}
158
+ :prop-value=${value}
158
159
  :on-input=${ev => value = ev.target.value}>${value}</textarea>
159
160
 
160
161
  <div class="form-text"
@@ -172,13 +173,12 @@
172
173
  <:define tag="bs-input-group:div"
173
174
 
174
175
  // parameters
175
- ::extra=${null} // extra classes for this instance
176
176
  ::prefix=${null}
177
177
  ::suffix=${null}
178
178
  ::size=${null}
179
179
 
180
180
  // private
181
- :_class=${['input-group', size ? `input-group-${size}` : '', extra]
181
+ :_class=${['input-group', size ? `input-group-${size}` : '']
182
182
  .filter(s => s).join(' ')}
183
183
 
184
184
  class=${_class}
@@ -14,7 +14,6 @@
14
14
  <:define tag="bs-list-group:div"
15
15
 
16
16
  // parameters
17
- ::extra=${null} // extra classes for this instance
18
17
  ::items=${[
19
18
  { name: 'An item', active: true },
20
19
  { name: 'A second item' },
@@ -28,7 +27,6 @@
28
27
  'list-group',
29
28
  flush ? 'list-group-flush' : '',
30
29
  horizontal ? 'list-group-horizontal' : '',
31
- extra,
32
30
  ].filter(s => s).join(' ')}
33
31
 
34
32
  class=${_class}
package/parts/modal.htm CHANGED
@@ -19,11 +19,10 @@
19
19
  amount of markup expresses them, and a handler is what runs them.
20
20
  -->
21
21
  <:define tag="bs-modal:div"
22
- class="modal fade ${extra ?? ''}"
22
+ class="modal fade"
23
23
  tabindex="-1"
24
24
 
25
25
  // parameters
26
- ::extra=${null} // extra classes for this instance
27
26
  ::name=${null}
28
27
  ::title=${''}
29
28
  ::size=${null} // 'sm' | 'lg' | 'xl'
package/parts/nav.htm CHANGED
@@ -12,7 +12,6 @@
12
12
  <:define tag="bs-nav:ul"
13
13
 
14
14
  // parameters
15
- ::extra=${null} // extra classes for this instance
16
15
  ::items=${[
17
16
  { name: 'Active', link: '#', active: true },
18
17
  { name: 'Link', link: '#' },
@@ -34,7 +33,6 @@
34
33
  vertical ? 'flex-column' : '',
35
34
  align === 'center' ? 'justify-content-center' : '',
36
35
  align === 'end' ? 'justify-content-end' : '',
37
- extra,
38
36
  ].filter(s => s).join(' ')}
39
37
 
40
38
  class=${_class}
@@ -70,11 +68,10 @@
70
68
  </:define>
71
69
 
72
70
  <:define tag="bs-tab-pane:div"
73
- class="tab-pane fade ${extra ?? ''}"
71
+ class="tab-pane fade"
74
72
  role="tabpanel"
75
73
 
76
74
  // parameters
77
- ::extra=${null} // extra classes for this instance
78
75
  ::name=${null}
79
76
  ::active=${false}
80
77
 
package/parts/navbar.htm CHANGED
@@ -17,7 +17,6 @@
17
17
  <:define tag="bs-navbar:nav"
18
18
 
19
19
  // parameters
20
- ::extra=${null} // extra classes for this instance
21
20
  ::items=${[
22
21
  { name: 'Home', link: '#', active: true },
23
22
  { name: 'Features', link: '#' },
@@ -37,7 +36,6 @@
37
36
  'navbar',
38
37
  expand ? `navbar-expand-${expand}` : '',
39
38
  bg ? `bg-${bg}` : '',
40
- extra,
41
39
  ].filter(s => s).join(' ')}
42
40
 
43
41
  // The gap under the links is only wanted while they are stacked, which
@@ -14,7 +14,6 @@
14
14
  tabindex="-1"
15
15
 
16
16
  // parameters
17
- ::extra=${null} // extra classes for this instance
18
17
  ::name=${null}
19
18
  ::title=${''}
20
19
  ::placement=${'start'} // 'start' | 'end' | 'top' | 'bottom'
@@ -28,7 +27,6 @@
28
27
  :_class=${[
29
28
  responsive ? `offcanvas-${responsive}` : 'offcanvas',
30
29
  `offcanvas-${placement}`,
31
- extra,
32
30
  ].join(' ')}
33
31
 
34
32
  class=${_class}
@@ -19,7 +19,6 @@
19
19
  <:define tag="bs-pagination:nav"
20
20
 
21
21
  // parameters
22
- ::extra=${null} // extra classes for this instance
23
22
  ::current=${1}
24
23
  ::pages=${5}
25
24
  ::size=${null} // 'sm' | 'lg'
@@ -39,7 +38,6 @@
39
38
  ].filter(s => s).join(' ')}
40
39
  :_numbers=${Array.from({ length: pages }, (_, i) => i + 1)}
41
40
 
42
- class=${extra}
43
41
  aria-label=${label}
44
42
  >
45
43
  <ul class=${_class}>
@@ -9,14 +9,13 @@
9
9
  <:define tag="bs-placeholder:span"
10
10
 
11
11
  // parameters
12
- ::extra=${null} // extra classes for this instance
13
12
  ::cols=${6}
14
13
  ::size=${null} // 'xs' | 'sm' | 'lg'
15
14
  ::variant=${null}
16
15
  ::animation=${'glow'} // 'glow' | 'wave' | null
17
16
 
18
17
  // private
19
- :_class=${[animation ? `placeholder-${animation}` : '', extra]
18
+ :_class=${[animation ? `placeholder-${animation}` : '']
20
19
  .filter(s => s).join(' ') || null}
21
20
  :_barClass=${[
22
21
  'placeholder',
package/parts/popover.htm CHANGED
@@ -10,10 +10,9 @@
10
10
  part that usually changes; both are read when it runs.
11
11
  -->
12
12
  <:define tag="bs-popover:span"
13
- class="d-inline-block ${extra ?? ''}"
13
+ class="d-inline-block"
14
14
 
15
15
  // parameters
16
- ::extra=${null} // extra classes for this instance
17
16
  ::title=${''}
18
17
  ::content=${''}
19
18
  ::placement=${'right'}
@@ -9,10 +9,9 @@
9
9
  `:label` is the text drawn inside the bar; leave it off for a bare bar.
10
10
  -->
11
11
  <:define tag="bs-progress:div"
12
- class="progress ${extra ?? ''}"
12
+ class="progress"
13
13
 
14
14
  // parameters
15
- ::extra=${null} // extra classes for this instance
16
15
  ::value=${0}
17
16
  ::min=${0}
18
17
  ::max=${100}
@@ -12,13 +12,11 @@
12
12
  <:define tag="bs-scrollspy:div"
13
13
 
14
14
  // parameters
15
- ::extra=${null} // extra classes for this instance
16
15
  ::target=${null}
17
16
  ::offset=${0}
18
17
  ::smooth=${false}
19
18
  ::height=${null}
20
19
 
21
- class=${extra}
22
20
  data-bs-spy="scroll"
23
21
  data-bs-target=${target}
24
22
  data-bs-root-margin="${offset}px 0px -40%"
package/parts/select.htm CHANGED
@@ -8,10 +8,9 @@
8
8
  <bs-select ::label="Size" ::options=${['S', 'M', 'L']} />
9
9
  -->
10
10
  <:define tag="bs-select:div"
11
- class="mb-3 ${extra ?? ''}"
11
+ class="mb-3"
12
12
 
13
13
  // parameters
14
- ::extra=${null} // extra classes for this instance
15
14
  ::label=${null}
16
15
  ::name=${null}
17
16
  ::options=${[]}
@@ -51,13 +50,15 @@
51
50
 
52
51
  <option value=""
53
52
  :if=${placeholder}
54
- :attr-selected=${value == null}>${placeholder}</option>
53
+ :attr-selected=${value == null}
54
+ :prop-selected=${value == null}>${placeholder}</option>
55
55
 
56
56
  <option :for-each=${_options}
57
57
  :for-as="option"
58
58
  value=${option.value}
59
59
  :attr-disabled=${option.disabled}
60
- :attr-selected=${option.value === value}>${option.name}</option>
60
+ :attr-selected=${option.value === value}
61
+ :prop-selected=${option.value === value}>${option.name}</option>
61
62
  </select>
62
63
 
63
64
  <label for=${_id}
package/parts/spinner.htm CHANGED
@@ -10,7 +10,6 @@
10
10
  role="status"
11
11
 
12
12
  // parameters
13
- ::extra=${null} // extra classes for this instance
14
13
  ::variant=${null}
15
14
  ::type=${'border'} // 'border' | 'grow'
16
15
  ::small=${false}
@@ -21,7 +20,6 @@
21
20
  `spinner-${type}`,
22
21
  small ? `spinner-${type}-sm` : '',
23
22
  variant ? `text-${variant}` : '',
24
- extra,
25
23
  ].filter(s => s).join(' ')}
26
24
 
27
25
  class=${_class}
package/parts/table.htm CHANGED
@@ -16,7 +16,6 @@
16
16
  <:define tag="bs-table:div"
17
17
 
18
18
  // parameters
19
- ::extra=${null} // extra classes for this instance
20
19
  ::columns=${[]}
21
20
  ::rows=${[]}
22
21
  ::caption=${null}
@@ -42,7 +41,6 @@
42
41
  align ? `align-${align}` : '',
43
42
  ].filter(s => s).join(' ')}
44
43
 
45
- class=${extra}
46
44
  :class-table-responsive=${responsive}
47
45
  >
48
46
  <table class=${_class}>
package/parts/theme.htm CHANGED
@@ -10,6 +10,16 @@
10
10
  piece of this kit that cannot be reactive, because it has to happen
11
11
  before Markout exists.
12
12
 
13
+ It is also the one piece that would otherwise DO something merely because
14
+ a file was imported. `all.htm` pulls this file in, so every page taking
15
+ the whole kit used to get colour modes whether it asked for them or not —
16
+ which is the kind of action at a distance the rest of the kit has none
17
+ of. So the script waits on a tag: write `<bs-theme-auto />` for the
18
+ behaviour on its own, or a `<bs-theme-toggle />` for the behaviour and a
19
+ button to drive it, and a page that writes neither ships neither. Naming
20
+ a tag no `<:define>` declares is a compile error, which is what stops a
21
+ rename from leaving this waiting on a name nothing will ever use.
22
+
13
23
  From there `_theme` is an ordinary value, and it starts as whatever that
14
24
  script decided rather than as a literal: a served page knows nothing
15
25
  about this visitor, so a default written here would be a guess that the
@@ -28,7 +38,7 @@
28
38
  kit would rather say so at the point of use than pretend a server has
29
39
  them.
30
40
  -->
31
- <script>
41
+ <script :when-used="bs-theme-auto bs-theme-toggle">
32
42
  (() => {
33
43
  const stored = localStorage.getItem('bs-theme');
34
44
  const theme = stored
@@ -37,11 +47,25 @@
37
47
  })();
38
48
  </script>
39
49
 
50
+ <!---
51
+ "This page uses colour modes", and nothing else. The script above does
52
+ the work; this is how a page asks for it without also asking for a
53
+ button, and it has no element, so what it costs the page is one empty
54
+ scope — 58 bytes on the counter example.
55
+
56
+ Deliberately holding no value. It could expose the current mode for a
57
+ page that needs it in an expression rather than in CSS, and that is 109
58
+ bytes of props on every page that opts in and doesn't read it, which is
59
+ all of them so far. A component whose job is to say one thing should
60
+ cost what saying it costs; the value can be added the day something
61
+ wants it, and adding it then breaks nothing.
62
+ -->
63
+ <:define tag="bs-theme-auto:logic" />
64
+
40
65
  <:define tag="bs-theme-toggle:button"
41
66
  type="button"
42
67
 
43
68
  // parameters
44
- ::extra=${null} // extra classes for this instance
45
69
  ::variant=${'secondary'}
46
70
  ::outline=${true}
47
71
  ::size=${null}
@@ -52,7 +76,6 @@
52
76
  'btn',
53
77
  `btn-${outline ? 'outline-' : ''}${variant}`,
54
78
  size ? `btn-${size}` : '',
55
- extra,
56
79
  ].filter(s => s).join(' ')}
57
80
 
58
81
  class=${_class}
package/parts/toast.htm CHANGED
@@ -20,7 +20,6 @@
20
20
  aria-atomic="true"
21
21
 
22
22
  // parameters
23
- ::extra=${null} // extra classes for this instance
24
23
  ::title=${null}
25
24
  ::time=${null}
26
25
  ::variant=${null}
@@ -29,7 +28,7 @@
29
28
  ::delay=${5000}
30
29
 
31
30
  // private
32
- :_class=${['toast', variant ? `text-bg-${variant} border-0` : '', extra]
31
+ :_class=${['toast', variant ? `text-bg-${variant} border-0` : '']
33
32
  .filter(s => s).join(' ')}
34
33
 
35
34
  class=${_class}
@@ -81,7 +80,7 @@
81
80
 
82
81
  <bs-close ::dismiss="toast"
83
82
  ::label="Close notification"
84
- ::extra="me-2 m-auto"
83
+ class+="me-2 m-auto"
85
84
  :if=${!title} />
86
85
  </div>
87
86
  </:define>
@@ -93,7 +92,6 @@
93
92
  <:define tag="bs-toast-container:div"
94
93
 
95
94
  // parameters
96
- ::extra=${null} // extra classes for this instance
97
95
  ::placement=${'bottom-end'} // 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | 'top-center'
98
96
 
99
97
  // private
@@ -103,7 +101,6 @@
103
101
  placement.endsWith('start') ? 'start-0' : '',
104
102
  placement.endsWith('end') ? 'end-0' : '',
105
103
  placement.endsWith('center') ? 'start-50 translate-middle-x' : '',
106
- extra,
107
104
  ].filter(s => s).join(' ')}
108
105
 
109
106
  class=${_class}
package/parts/tooltip.htm CHANGED
@@ -26,10 +26,9 @@
26
26
  of its own to hang off; wrap the trigger, not the page around it.
27
27
  -->
28
28
  <:define tag="bs-tooltip:span"
29
- class="d-inline-block ${extra ?? ''}"
29
+ class="d-inline-block"
30
30
 
31
31
  // parameters
32
- ::extra=${null} // extra classes for this instance
33
32
  ::title=${''}
34
33
  ::placement=${'top'}
35
34
  ::html=${false}