@markout-lang/bootstrap-kit 0.4.1 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markout-lang/bootstrap-kit",
3
- "version": "0.4.1",
3
+ "version": "0.5.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",
@@ -7,8 +7,11 @@
7
7
  describes the entries, the component owns the markup.
8
8
 
9
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.
10
+ page, and gets `active` plus `aria-current`. It is also the one entry
11
+ with no `<a>` around it: an anchor with no `href` renders as plain text,
12
+ which is why it used to be left in place, but it is still an anchor to
13
+ everything that reads the document rather than looks at it. Bootstrap's
14
+ own markup for the current crumb is the text on its own, and so is this.
12
15
  -->
13
16
  <:define tag="bs-breadcrumb:nav"
14
17
  aria-label="breadcrumb"
@@ -34,7 +37,8 @@
34
37
  :for-as="item"
35
38
  :class-active=${!item.link}
36
39
  aria-current=${item.link ? null : 'page'}>
37
- <a href=${item.link ?? null}>${item.name}</a>
40
+ <a :if=${item.link} href=${item.link}>${item.name}</a>
41
+ <:group :else>${item.name}</:group>
38
42
  </li>
39
43
  </ol>
40
44
  </:define>
@@ -3,8 +3,17 @@
3
3
 
4
4
  <!---
5
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.
6
+ select }`; the shapes Bootstrap draws differently — a divider, a header,
7
+ an item — are optional regions over the same list.
8
+
9
+ An item comes out as an `<a>` when it has a `link` and a `<button>` when
10
+ it does not, which is the rule the whole kit follows: what navigates is a
11
+ link, what acts is a button, and what does neither is not an interactive
12
+ element at all. The alternative -- one `<a>` with the `href` dropped when
13
+ there is nowhere to go -- looks identical and is reachable only by mouse:
14
+ an anchor without an `href` takes no focus and reaches the accessibility
15
+ tree with no role, so `select` could be triggered by a click and by
16
+ nothing else. Bootstrap styles `.dropdown-item` on both elements.
8
17
 
9
18
  The menu is tied to its toggle by `aria-labelledby`, and the id for that
10
19
  comes from `$id`, which is unique per instance. That is the general
@@ -72,12 +81,26 @@
72
81
  :else-if=${item.header}>${item.header}</h6>
73
82
 
74
83
  <a class="dropdown-item"
75
- :else
84
+ :else-if=${item.link}
76
85
  :class-active=${item.active}
77
86
  :class-disabled=${item.disabled}
78
- href=${item.link ?? null}
87
+ href=${item.link}
79
88
  aria-current=${item.active ? 'true' : null}
89
+ aria-disabled=${item.disabled ? 'true' : null}
80
90
  :on-click=${() => item.select?.(item)}>${item.name}</a>
91
+
92
+ <!---
93
+ `disabled` rather than `.disabled`: on a button the attribute is
94
+ the real thing, and it takes the entry out of the tab order as
95
+ well as greying it. Bootstrap's CSS matches both.
96
+ -->
97
+ <button class="dropdown-item"
98
+ type="button"
99
+ :else
100
+ :class-active=${item.active}
101
+ :attr-disabled=${item.disabled}
102
+ aria-current=${item.active ? 'true' : null}
103
+ :on-click=${() => item.select?.(item)}>${item.name}</button>
81
104
  </li>
82
105
  </ul>
83
106
  </:define>
@@ -6,10 +6,17 @@
6
6
  A list group, from `{ name, link, active, disabled, variant, badge, text }`
7
7
  entries.
8
8
 
9
- Every entry is an `<a>`. Bootstrap only wants `.list-group-item-action`
10
- (the hover and focus treatment) on the ones that actually go somewhere, so
11
- that class follows `link` rather than being always on and an anchor with
12
- no `href` is inert, which is exactly what a non-link entry should be.
9
+ An entry with a `link` is an `<a>`; one without is a `<div>`. Both were
10
+ an `<a>` once, the second with its `href` dropped, which is inert and
11
+ looks right and is still a link as far as anything reading the document
12
+ is concerned. `.list-group-item-action` Bootstrap's hover and focus
13
+ treatment — goes on the first alone, as it always did, and now it goes on
14
+ the element that can take focus.
15
+
16
+ The body is written twice because the element is what differs and markout
17
+ has no way to choose a tag name from an expression. Two branches of six
18
+ lines is the smaller cost: the alternative is a wrapper inside every
19
+ entry, on every page, to save repeating them here.
13
20
  -->
14
21
  <:define tag="bs-list-group:div"
15
22
 
@@ -31,26 +38,43 @@
31
38
 
32
39
  class=${_class}
33
40
  >
34
- <a :for-each=${items}
35
- :for-as="item"
36
- class="list-group-item d-flex justify-content-between align-items-center"
37
- :class-list-group-item-action=${!!item.link}
38
- :class-active=${item.active}
39
- :class-disabled=${item.disabled}
40
- href=${item.link ?? null}
41
- aria-current=${item.active ? 'true' : null}
42
- aria-disabled=${item.disabled ? 'true' : null}>
43
-
44
- <span>
45
- <span :class-fw-bold=${!!item.text}>${item.name}</span>
46
- <span class="d-block small opacity-75"
47
- :if=${item.text}>${item.text}</span>
48
- </span>
49
-
50
- <bs-badge ::variant=${item.badge?.variant ?? 'secondary'}
51
- ::pill
52
- :if=${item.badge}>${item.badge?.name ?? item.badge}</bs-badge>
53
- </a>
41
+ <:group :for-each=${items} :for-as="item">
42
+ <a :if=${item.link}
43
+ class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
44
+ :class-active=${item.active}
45
+ :class-disabled=${item.disabled}
46
+ href=${item.link}
47
+ aria-current=${item.active ? 'true' : null}
48
+ aria-disabled=${item.disabled ? 'true' : null}>
49
+
50
+ <span>
51
+ <span :class-fw-bold=${!!item.text}>${item.name}</span>
52
+ <span class="d-block small opacity-75"
53
+ :if=${item.text}>${item.text}</span>
54
+ </span>
55
+
56
+ <bs-badge ::variant=${item.badge?.variant ?? 'secondary'}
57
+ ::pill
58
+ :if=${item.badge}>${item.badge?.name ?? item.badge}</bs-badge>
59
+ </a>
60
+
61
+ <div :else
62
+ class="list-group-item d-flex justify-content-between align-items-center"
63
+ :class-active=${item.active}
64
+ :class-disabled=${item.disabled}
65
+ aria-current=${item.active ? 'true' : null}>
66
+
67
+ <span>
68
+ <span :class-fw-bold=${!!item.text}>${item.name}</span>
69
+ <span class="d-block small opacity-75"
70
+ :if=${item.text}>${item.text}</span>
71
+ </span>
72
+
73
+ <bs-badge ::variant=${item.badge?.variant ?? 'secondary'}
74
+ ::pill
75
+ :if=${item.badge}>${item.badge?.name ?? item.badge}</bs-badge>
76
+ </div>
77
+ </:group>
54
78
  </:define>
55
79
 
56
80
  </lib>
package/parts/nav.htm CHANGED
@@ -8,6 +8,12 @@
8
8
  Entries are `{ name, link, active, disabled, target }`. For a tabbed
9
9
  interface set `:toggle="tab"` (or `"pill"`) and give each entry the
10
10
  `target` of its pane; `bs-tab-pane` below is the other half.
11
+
12
+ Three elements come out of that, one per kind of entry: a `<button>` when
13
+ the nav switches panes, an `<a>` when the entry has a `link`, and a
14
+ `<span>` when it has neither and is a label sitting among the links. The
15
+ third used to be an `<a>` with no `href`, which reads as a link to
16
+ anything that is not looking at the screen.
11
17
  -->
12
18
  <:define tag="bs-nav:ul"
13
19
 
@@ -53,12 +59,18 @@
53
59
  aria-selected=${item.active ? 'true' : 'false'}>${item.name}</button>
54
60
 
55
61
  <a class="nav-link"
56
- :else
62
+ :else-if=${item.link}
57
63
  :class-active=${item.active}
58
64
  :class-disabled=${item.disabled}
59
- href=${item.link ?? null}
65
+ href=${item.link}
60
66
  aria-current=${item.active ? 'page' : null}
61
67
  aria-disabled=${item.disabled ? 'true' : null}>${item.name}</a>
68
+
69
+ <span class="nav-link"
70
+ :else
71
+ :class-active=${item.active}
72
+ :class-disabled=${item.disabled}
73
+ aria-current=${item.active ? 'page' : null}>${item.name}</span>
62
74
  </li>
63
75
  </:define>
64
76
 
package/parts/navbar.htm CHANGED
@@ -70,14 +70,29 @@
70
70
  <div class="collapse navbar-collapse" id=${_id}>
71
71
  <ul class=${_navClass}>
72
72
  <li class="nav-item" :for-each=${items} :for-as="item">
73
+ <!---
74
+ An entry with no `link` is a label among the links rather than
75
+ a link that goes nowhere, and comes out as a `<span>`. See
76
+ `nav.htm`, which makes the same distinction for the same
77
+ reason.
78
+ -->
73
79
  <a class="nav-link"
80
+ :if=${item.link}
74
81
  :class-active=${item.active}
75
82
  :class-disabled=${item.disabled}
76
83
  :class-btn=${item.button}
77
84
  :class-btn-primary=${item.button}
78
- href=${item.link ?? null}
85
+ href=${item.link}
79
86
  aria-current=${item.active ? 'page' : null}
80
87
  aria-disabled=${item.disabled ? 'true' : null}>${item.name}</a>
88
+
89
+ <span class="nav-link"
90
+ :else
91
+ :class-active=${item.active}
92
+ :class-disabled=${item.disabled}
93
+ :class-btn=${item.button}
94
+ :class-btn-primary=${item.button}
95
+ aria-current=${item.active ? 'page' : null}>${item.name}</span>
81
96
  </li>
82
97
  </ul>
83
98
 
@@ -15,6 +15,14 @@
15
15
  called with the page number for one that stays on the page. Both are
16
16
  ordinary function parameters — a callback the component calls, not a DOM
17
17
  event, so it is a value and not an `:on-` handler.
18
+
19
+ They also decide the element. `::link` yields an href, so those entries
20
+ are `<a>`; with `::select` alone — the default, and the mode a page that
21
+ never reloads uses — there is nowhere to go, and each entry is a
22
+ `<button>`. Written as an `<a>` with no `href` it looks right and pages
23
+ for a mouse alone: an anchor without an `href` takes no focus and carries
24
+ no role, so the whole control is unreachable from the keyboard. Bootstrap
25
+ styles `.page-link` on both.
18
26
  -->
19
27
  <:define tag="bs-pagination:nav"
20
28
 
@@ -41,11 +49,27 @@
41
49
  aria-label=${label}
42
50
  >
43
51
  <ul class=${_class}>
44
- <li class="page-item" :class-disabled=${current <= 1}>
52
+ <!---
53
+ `_href` is declared on the `<li>` so the two branches inside read one
54
+ value rather than calling `link()` twice each. The `<li>` is a scope
55
+ like any element, which is what makes that the obvious place for it.
56
+ -->
57
+ <li class="page-item"
58
+ :class-disabled=${current <= 1}
59
+ :_href=${link(current - 1)}>
45
60
  <a class="page-link"
46
- href=${link(current - 1)}
61
+ :if=${_href != null}
62
+ href=${_href}
47
63
  aria-label="Previous"
64
+ aria-disabled=${current <= 1 ? 'true' : null}
48
65
  :on-click=${() => current > 1 && select(current - 1)}>${prev}</a>
66
+
67
+ <button class="page-link"
68
+ type="button"
69
+ :else
70
+ :attr-disabled=${current <= 1}
71
+ aria-label="Previous"
72
+ :on-click=${() => select(current - 1)}>${prev}</button>
49
73
  </li>
50
74
 
51
75
  <li class="page-item"
@@ -53,17 +77,35 @@
53
77
  :for-as="n"
54
78
  :for-key=${n}
55
79
  :class-active=${n === current}
56
- aria-current=${n === current ? 'page' : null}>
80
+ aria-current=${n === current ? 'page' : null}
81
+ :_href=${link(n)}>
57
82
  <a class="page-link"
58
- href=${link(n)}
83
+ :if=${_href != null}
84
+ href=${_href}
59
85
  :on-click=${() => select(n)}>${n}</a>
86
+
87
+ <button class="page-link"
88
+ type="button"
89
+ :else
90
+ :on-click=${() => select(n)}>${n}</button>
60
91
  </li>
61
92
 
62
- <li class="page-item" :class-disabled=${current >= pages}>
93
+ <li class="page-item"
94
+ :class-disabled=${current >= pages}
95
+ :_href=${link(current + 1)}>
63
96
  <a class="page-link"
64
- href=${link(current + 1)}
97
+ :if=${_href != null}
98
+ href=${_href}
65
99
  aria-label="Next"
100
+ aria-disabled=${current >= pages ? 'true' : null}
66
101
  :on-click=${() => current < pages && select(current + 1)}>${next}</a>
102
+
103
+ <button class="page-link"
104
+ type="button"
105
+ :else
106
+ :attr-disabled=${current >= pages}
107
+ aria-label="Next"
108
+ :on-click=${() => select(current + 1)}>${next}</button>
67
109
  </li>
68
110
  </ul>
69
111
  </:define>