@allxsmith/bestax-bulma 5.64.0 → 5.65.1

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.
Files changed (48) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/esm/components/Carousel.js +12 -66
  4. package/dist/esm/components/Carousel.js.map +1 -1
  5. package/dist/esm/components/Lightbox.js +756 -0
  6. package/dist/esm/components/Lightbox.js.map +1 -0
  7. package/dist/esm/components/Navbar.js.map +1 -1
  8. package/dist/esm/components/Reveal.js +1 -24
  9. package/dist/esm/components/Reveal.js.map +1 -1
  10. package/dist/esm/helpers/chevronIcons.js +24 -0
  11. package/dist/esm/helpers/chevronIcons.js.map +1 -0
  12. package/dist/esm/helpers/usePrefersReducedMotion.js +34 -0
  13. package/dist/esm/helpers/usePrefersReducedMotion.js.map +1 -0
  14. package/dist/esm/helpers/useSwipe.js +82 -0
  15. package/dist/esm/helpers/useSwipe.js.map +1 -0
  16. package/dist/extras.css +1 -1
  17. package/dist/extras.css.map +1 -1
  18. package/dist/index.cjs +924 -149
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.esm.js +1 -0
  21. package/dist/index.esm.js.map +1 -1
  22. package/dist/types/components/Lightbox.d.ts +244 -0
  23. package/dist/types/components/Navbar.d.ts +10 -0
  24. package/dist/types/helpers/chevronIcons.d.ts +14 -0
  25. package/dist/types/helpers/usePrefersReducedMotion.d.ts +10 -0
  26. package/dist/types/helpers/useSwipe.d.ts +66 -0
  27. package/dist/types/index.d.ts +1 -0
  28. package/dist/types-cjs/components/Lightbox.d.ts +244 -0
  29. package/dist/types-cjs/components/Navbar.d.ts +10 -0
  30. package/dist/types-cjs/helpers/chevronIcons.d.ts +14 -0
  31. package/dist/types-cjs/helpers/usePrefersReducedMotion.d.ts +10 -0
  32. package/dist/types-cjs/helpers/useSwipe.d.ts +66 -0
  33. package/dist/types-cjs/index.d.ts +1 -0
  34. package/dist/versions/bestax-no-dark-mode.css +1 -1
  35. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  36. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  37. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  38. package/dist/versions/bestax-no-helpers.css +1 -1
  39. package/dist/versions/bestax-no-helpers.css.map +1 -1
  40. package/dist/versions/bestax-prefixed.css +1 -1
  41. package/dist/versions/bestax-prefixed.css.map +1 -1
  42. package/package.json +6 -6
  43. package/src/scss/CLAUDE.md +1 -1
  44. package/src/scss/components/_index.scss +1 -0
  45. package/src/scss/components/_lightbox.scss +392 -0
  46. package/src/scss/elements/_button.scss +49 -0
  47. package/src/scss/elements/_index.scss +1 -0
  48. package/src/scss/elements/_loader.scss +10 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allxsmith/bestax-bulma",
3
- "version": "5.64.0",
3
+ "version": "5.65.1",
4
4
  "description": "Component library based on React, Bulma, with Agent Skills and MCP",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.esm.js",
@@ -40,7 +40,7 @@
40
40
  "@typescript-eslint/parser": "^8.72.1",
41
41
  "@vitest/browser-playwright": "^5.0.5",
42
42
  "@vitest/coverage-istanbul": "^5.0.5",
43
- "eslint": "^10.13.0",
43
+ "eslint": "^10.14.0",
44
44
  "eslint-config-prettier": "^10.1.5",
45
45
  "eslint-plugin-prettier": "^5.5.7",
46
46
  "eslint-plugin-react-hooks": "^7.1.1",
@@ -52,13 +52,13 @@
52
52
  "playwright": "^1.64.1",
53
53
  "prettier": "^3.9.10",
54
54
  "rimraf": "^6.0.1",
55
- "rollup": "^4.66.0",
55
+ "rollup": "^4.67.4",
56
56
  "rollup-plugin-scss": "^4.0.1",
57
57
  "rollup-plugin-visualizer": "^7.1.1",
58
- "sass": "^1.107.0",
59
- "sass-embedded": "^1.107.0",
58
+ "sass": "^1.107.1",
59
+ "sass-embedded": "^1.107.1",
60
60
  "storybook": "^10.6.5",
61
- "tslib": "^2.8.0",
61
+ "tslib": "^2.9.1",
62
62
  "typescript": "^6.0.3",
63
63
  "vite": "^8.3.5",
64
64
  "vitest": "^5.0.5"
@@ -1,6 +1,6 @@
1
1
  # scss/ — styles for the bestax "extras"
2
2
 
3
- **Only extras get SCSS.** Components that are stock Bulma (Button, Card, Navbar, …) ship no CSS
3
+ **Only extras get SCSS.** Components that are stock Bulma (Box, Card, Navbar, …) ship no CSS
4
4
  of their own — Bulma provides it. A partial exists only for components that extend beyond Bulma
5
5
  (Carousel, Collapse, Dialog, the extended form inputs, …) — or that extend a stock component
6
6
  (e.g. `_tabs.scss` adds vertical Tabs). The `_index.scss` files are the authoritative inventory.
@@ -13,6 +13,7 @@
13
13
  @use 'toast';
14
14
  @use 'dialog';
15
15
  @use 'carousel';
16
+ @use 'lightbox';
16
17
  @use 'tabs';
17
18
  @use 'menu';
18
19
  @use 'tableofcontents';
@@ -0,0 +1,392 @@
1
+ // Bestax Extras - Lightbox Styles
2
+ // Bulma's image modal, finished: `.modal` with its near-black background and
3
+ // `.modal-close.is-large`, the image centred as large as the viewer allows,
4
+ // a bar along the top with the counter and the zoom buttons, round previous
5
+ // and next buttons at the inline edges, and the caption under the image.
6
+ //
7
+ // The root carries both `modal` and `lightbox`, so these rules outrank
8
+ // Bulma's `.modal` ones in either stylesheet order. The space around the
9
+ // image takes no pointer events, so a click there lands on
10
+ // `.modal-background`, which closes the viewer.
11
+
12
+ @use 'bulma/sass/utilities/initial-variables' as iv;
13
+ @use 'bulma/sass/utilities/css-variables' as cv;
14
+ @use '../variables' as *;
15
+ @use '../mixins' as *;
16
+
17
+ // Carousel's overlay arrows, light controls on a dark tint in either scheme.
18
+ // The same values rather than an `@use` of `_carousel.scss`, which would put
19
+ // the carousel's whole stylesheet into a modular build that asks only for
20
+ // this one.
21
+ $lightbox-arrow-background: hsla(
22
+ #{cv.getVar('scheme-h')},
23
+ #{cv.getVar('scheme-s')},
24
+ #{cv.getVar('scheme-invert-l')},
25
+ 0.3
26
+ ) !default;
27
+ $lightbox-arrow-background-hover: hsla(
28
+ #{cv.getVar('scheme-h')},
29
+ #{cv.getVar('scheme-s')},
30
+ #{cv.getVar('scheme-invert-l')},
31
+ 0.5
32
+ ) !default;
33
+ $lightbox-arrow-color: cv.getVar('white') !default;
34
+ $lightbox-arrow-size: 3rem !default;
35
+ $lightbox-arrow-offset: 1rem !default;
36
+ $lightbox-arrow-icon-size: 1.5rem !default;
37
+ // The zoom buttons in the toolbar start from the arrows' look.
38
+ $lightbox-toolbar-button-background: cv.getVar(
39
+ 'lightbox-arrow-background'
40
+ ) !default;
41
+ $lightbox-toolbar-button-background-hover: cv.getVar(
42
+ 'lightbox-arrow-background-hover'
43
+ ) !default;
44
+ $lightbox-toolbar-icon-size: 1.25rem !default;
45
+ // The round buttons' border, transparent until forced colors draws it.
46
+ $lightbox-button-border-width: 1px !default;
47
+ // A button that does nothing at an end, or at a zoom limit.
48
+ $lightbox-disabled-opacity: 0.4 !default;
49
+ // The close button, the toolbar and the arrows sit over the image.
50
+ $lightbox-controls-z-index: $extras-z-index-base !default;
51
+ // The modal's background stays near-black in Bulma's dark theme too, so the
52
+ // text on it is white in both.
53
+ $lightbox-toolbar-color: cv.getVar('white') !default;
54
+ $lightbox-toolbar-gap: 0.75rem !default;
55
+ $lightbox-caption-color: cv.getVar('white') !default;
56
+ $lightbox-caption-max-width: 40em !default;
57
+ $lightbox-caption-padding: 0.75rem 1rem 0 !default;
58
+ // A failed image's message and Retry, where the image would be.
59
+ $lightbox-error-color: cv.getVar('lightbox-caption-color') !default;
60
+ $lightbox-error-gap: 0.75rem !default;
61
+ $lightbox-error-padding: 1rem !default;
62
+ $lightbox-loader-size: 2.5rem !default;
63
+ // The band kept free above the image for the toolbar and the close button,
64
+ // and below it, so the image stays centred.
65
+ $lightbox-spacing: 4.5rem !default;
66
+ // The image and its caption share the viewer's height less the two bands.
67
+ $lightbox-image-max-height: calc(
68
+ 100% - 2 * #{cv.getVar('lightbox-spacing')}
69
+ ) !default;
70
+ // The fades and the zoom, timed and eased as Bulma's own transitions are.
71
+ $lightbox-animation-duration: cv.getVar('duration') !default;
72
+
73
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox {
74
+ @include cv.register-vars(
75
+ (
76
+ 'lightbox-arrow-background': #{$lightbox-arrow-background},
77
+ 'lightbox-arrow-background-hover': #{$lightbox-arrow-background-hover},
78
+ 'lightbox-arrow-color': #{$lightbox-arrow-color},
79
+ 'lightbox-arrow-size': #{$lightbox-arrow-size},
80
+ 'lightbox-arrow-offset': #{$lightbox-arrow-offset},
81
+ 'lightbox-arrow-icon-size': #{$lightbox-arrow-icon-size},
82
+ 'lightbox-toolbar-button-background':
83
+ #{$lightbox-toolbar-button-background},
84
+ 'lightbox-toolbar-button-background-hover':
85
+ #{$lightbox-toolbar-button-background-hover},
86
+ 'lightbox-toolbar-icon-size': #{$lightbox-toolbar-icon-size},
87
+ 'lightbox-button-border-width': #{$lightbox-button-border-width},
88
+ 'lightbox-disabled-opacity': #{$lightbox-disabled-opacity},
89
+ 'lightbox-controls-z-index': #{$lightbox-controls-z-index},
90
+ 'lightbox-toolbar-color': #{$lightbox-toolbar-color},
91
+ 'lightbox-toolbar-gap': #{$lightbox-toolbar-gap},
92
+ 'lightbox-caption-color': #{$lightbox-caption-color},
93
+ 'lightbox-caption-max-width': #{$lightbox-caption-max-width},
94
+ 'lightbox-caption-padding': #{$lightbox-caption-padding},
95
+ 'lightbox-error-color': #{$lightbox-error-color},
96
+ 'lightbox-error-gap': #{$lightbox-error-gap},
97
+ 'lightbox-error-padding': #{$lightbox-error-padding},
98
+ 'lightbox-loader-size': #{$lightbox-loader-size},
99
+ 'lightbox-spacing': #{$lightbox-spacing},
100
+ 'lightbox-image-max-height': #{$lightbox-image-max-height},
101
+ 'lightbox-animation-duration': #{$lightbox-animation-duration},
102
+ // Bulma's own, wide enough for the image with room for the arrows at
103
+ // either side, in place of its 40rem.
104
+ 'modal-content-width': calc(
105
+ 100% - 2 *
106
+ (
107
+ #{cv.getVar('lightbox-arrow-size')} + 2 *
108
+ #{cv.getVar('lightbox-arrow-offset')}
109
+ )
110
+ ),
111
+ )
112
+ );
113
+ }
114
+
115
+ // ---- The viewer ---------------------------------------------------------------
116
+
117
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox.#{iv.$class-prefix}is-active {
118
+ animation: extras-fade-in cv.getVar('lightbox-animation-duration')
119
+ $extras-easing;
120
+ }
121
+
122
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox:focus {
123
+ outline: none;
124
+ }
125
+
126
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox
127
+ > .#{iv.$class-prefix}modal-close {
128
+ z-index: cv.getVar('lightbox-controls-z-index');
129
+ }
130
+
131
+ // The image's area: the viewer's width less the arrows' room from a tablet
132
+ // up, and its whole width below, where the arrows sit over the image.
133
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox
134
+ > .#{iv.$class-prefix}modal-content {
135
+ height: cv.getVar('lightbox-image-max-height');
136
+ max-height: none;
137
+ margin: 0;
138
+ overflow: visible;
139
+ pointer-events: none;
140
+ }
141
+
142
+ // ---- The toolbar ----------------------------------------------------------------
143
+
144
+ .#{iv.$class-prefix}lightbox-toolbar {
145
+ position: absolute;
146
+ inset-block-start: 0;
147
+ inset-inline: 0;
148
+ z-index: cv.getVar('lightbox-controls-z-index');
149
+ display: flex;
150
+ align-items: center;
151
+ gap: cv.getVar('lightbox-toolbar-gap');
152
+ min-height: cv.getVar('lightbox-spacing');
153
+ // Level with the close button, and clear of it.
154
+ padding-block: cv.getVar('modal-close-top');
155
+ padding-inline: cv.getVar('modal-close-right')
156
+ calc(
157
+ #{cv.getVar('modal-close-right')} * 2 +
158
+ #{cv.getVar('modal-close-dimensions')}
159
+ );
160
+ color: cv.getVar('lightbox-toolbar-color');
161
+ pointer-events: none;
162
+
163
+ > .#{iv.$class-prefix}buttons {
164
+ flex-wrap: nowrap;
165
+ margin-block-end: 0;
166
+ margin-inline-start: auto;
167
+ }
168
+ }
169
+
170
+ .#{iv.$class-prefix}lightbox-progress {
171
+ font-variant-numeric: tabular-nums;
172
+ white-space: nowrap;
173
+ }
174
+
175
+ // ---- The buttons over the dark background ------------------------------------------
176
+
177
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox
178
+ .#{iv.$class-prefix}lightbox-toolbar
179
+ .#{iv.$class-prefix}button,
180
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox
181
+ .#{iv.$class-prefix}lightbox-arrow {
182
+ align-items: center;
183
+ justify-content: center;
184
+ width: cv.getVar('modal-close-dimensions');
185
+ height: cv.getVar('modal-close-dimensions');
186
+ padding: 0;
187
+ // Transparent until forced colors draws it.
188
+ border: cv.getVar('lightbox-button-border-width') solid transparent;
189
+ border-radius: cv.getVar('radius-rounded');
190
+ pointer-events: auto;
191
+
192
+ // Bulma's ring is a link-colored shadow, under 3:1 on the near-black
193
+ // background and gone in forced colors, so an outline in the buttons' own
194
+ // color takes its place.
195
+ &:focus-visible {
196
+ @include extras-focus-outline;
197
+ outline-color: currentColor;
198
+ box-shadow: none;
199
+ }
200
+
201
+ &[aria-disabled='true'] {
202
+ opacity: cv.getVar('lightbox-disabled-opacity');
203
+ cursor: not-allowed;
204
+ }
205
+ }
206
+
207
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox
208
+ .#{iv.$class-prefix}lightbox-toolbar
209
+ .#{iv.$class-prefix}button {
210
+ background-color: cv.getVar('lightbox-toolbar-button-background');
211
+ color: cv.getVar('lightbox-toolbar-color');
212
+
213
+ &:hover:not([aria-disabled='true']) {
214
+ background-color: cv.getVar('lightbox-toolbar-button-background-hover');
215
+ }
216
+
217
+ svg {
218
+ width: cv.getVar('lightbox-toolbar-icon-size');
219
+ height: cv.getVar('lightbox-toolbar-icon-size');
220
+ }
221
+ }
222
+
223
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox
224
+ .#{iv.$class-prefix}lightbox-arrow {
225
+ position: absolute;
226
+ inset-block-start: 50%;
227
+ z-index: cv.getVar('lightbox-controls-z-index');
228
+ width: cv.getVar('lightbox-arrow-size');
229
+ height: cv.getVar('lightbox-arrow-size');
230
+ transform: translateY(-50%);
231
+ background-color: cv.getVar('lightbox-arrow-background');
232
+ color: cv.getVar('lightbox-arrow-color');
233
+
234
+ &:hover:not([aria-disabled='true']) {
235
+ background-color: cv.getVar('lightbox-arrow-background-hover');
236
+ }
237
+
238
+ &.#{iv.$class-prefix}is-prev {
239
+ inset-inline-start: cv.getVar('lightbox-arrow-offset');
240
+ }
241
+
242
+ &.#{iv.$class-prefix}is-next {
243
+ inset-inline-end: cv.getVar('lightbox-arrow-offset');
244
+ }
245
+
246
+ svg {
247
+ width: cv.getVar('lightbox-arrow-icon-size');
248
+ height: cv.getVar('lightbox-arrow-icon-size');
249
+ }
250
+
251
+ // Right to left, the previous button is on the right, so its chevron
252
+ // points that way.
253
+ svg:dir(rtl) {
254
+ transform: scaleX(-1);
255
+ }
256
+ }
257
+
258
+ // ---- The image and its caption -----------------------------------------------------
259
+
260
+ // One cell the image fills, with the caption under it. The image, the one
261
+ // fading out over it, the Loader and the error share the cell.
262
+ .#{iv.$class-prefix}lightbox-figure {
263
+ display: grid;
264
+ grid-template-columns: minmax(0, 1fr);
265
+ grid-template-rows: minmax(0, 1fr) auto;
266
+ height: 100%;
267
+ margin: 0;
268
+ position: relative;
269
+ transition: transform cv.getVar('lightbox-animation-duration') $extras-easing;
270
+
271
+ > :not(.#{iv.$class-prefix}lightbox-caption) {
272
+ grid-area: 1 / 1;
273
+ place-self: center;
274
+ }
275
+ }
276
+
277
+ .#{iv.$class-prefix}lightbox-image {
278
+ display: block;
279
+ max-width: 100%;
280
+ max-height: 100%;
281
+ object-fit: contain;
282
+ pointer-events: auto;
283
+ // Pinch zooms the image rather than the page, and a drag pans it.
284
+ touch-action: none;
285
+ user-select: none;
286
+ -webkit-user-drag: none;
287
+ transition: transform cv.getVar('lightbox-animation-duration') $extras-easing;
288
+
289
+ // Hidden until it loads, then faded in.
290
+ &.#{iv.$class-prefix}is-loading {
291
+ opacity: 0;
292
+ }
293
+
294
+ &:not(.#{iv.$class-prefix}is-loading):not(.#{iv.$class-prefix}is-leaving) {
295
+ animation: extras-fade-in cv.getVar('lightbox-animation-duration')
296
+ $extras-easing;
297
+ }
298
+
299
+ // The image before it, on top, shown until the new one has loaded and then
300
+ // faded out as that one fades in. Animations rather than transitions, so
301
+ // the fades run for an image that was already loaded too.
302
+ &.#{iv.$class-prefix}is-leaving {
303
+ pointer-events: none;
304
+ }
305
+
306
+ &:not(.#{iv.$class-prefix}is-loading)
307
+ + .#{iv.$class-prefix}lightbox-image.#{iv.$class-prefix}is-leaving {
308
+ animation: extras-fade-out cv.getVar('lightbox-animation-duration')
309
+ $extras-easing forwards;
310
+ }
311
+ }
312
+
313
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox.#{iv.$class-prefix}is-zoomed
314
+ .#{iv.$class-prefix}lightbox-image {
315
+ cursor: grab;
316
+ }
317
+
318
+ .#{iv.$class-prefix}lightbox-caption {
319
+ justify-self: center;
320
+ max-width: cv.getVar('lightbox-caption-max-width');
321
+ padding: cv.getVar('lightbox-caption-padding');
322
+ color: cv.getVar('lightbox-caption-color');
323
+ text-align: center;
324
+ pointer-events: auto;
325
+ }
326
+
327
+ .#{iv.$class-prefix}lightbox-error {
328
+ display: flex;
329
+ flex-direction: column;
330
+ align-items: center;
331
+ gap: cv.getVar('lightbox-error-gap');
332
+ padding: cv.getVar('lightbox-error-padding');
333
+ color: cv.getVar('lightbox-error-color');
334
+ text-align: center;
335
+ pointer-events: auto;
336
+ }
337
+
338
+ .#{iv.$class-prefix}lightbox-figure > .#{iv.$class-prefix}loader {
339
+ font-size: cv.getVar('lightbox-loader-size');
340
+ }
341
+
342
+ // ---- The trigger ----------------------------------------------------------------------
343
+
344
+ // A button around a thumbnail, drawn as the thumbnail: no button chrome, as
345
+ // wide as the column or cell it's in, since a ratio `Image` has no width of
346
+ // its own, and an outline around what it holds. `:where()` keeps all of it
347
+ // at no specificity, so a trigger given Bulma's `button` class looks and
348
+ // sizes like a Bulma button instead.
349
+ :where(.#{iv.$class-prefix}lightbox-trigger) {
350
+ @include extras-button-reset;
351
+ text-align: inherit;
352
+ }
353
+
354
+ :where(.#{iv.$class-prefix}lightbox-trigger:not(.#{iv.$class-prefix}button)) {
355
+ display: block;
356
+ width: 100%;
357
+
358
+ &:focus-visible {
359
+ @include extras-focus-outline;
360
+ }
361
+ }
362
+
363
+ // ---- Motion and forced colors ------------------------------------------------------------
364
+
365
+ // The fades are on selectors more specific than these, hence `!important`.
366
+ // Script keeps no outgoing image under reduced motion either.
367
+ @media (prefers-reduced-motion: reduce) {
368
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox.#{iv.$class-prefix}is-active,
369
+ .#{iv.$class-prefix}lightbox-image {
370
+ animation: none !important;
371
+ }
372
+
373
+ .#{iv.$class-prefix}lightbox-figure,
374
+ .#{iv.$class-prefix}lightbox-image {
375
+ transition: none;
376
+ }
377
+ }
378
+
379
+ @media (forced-colors: active) {
380
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox
381
+ .#{iv.$class-prefix}lightbox-toolbar
382
+ .#{iv.$class-prefix}button,
383
+ .#{iv.$class-prefix}modal.#{iv.$class-prefix}lightbox
384
+ .#{iv.$class-prefix}lightbox-arrow {
385
+ border-color: ButtonText;
386
+
387
+ &[aria-disabled='true'] {
388
+ border-color: GrayText;
389
+ color: GrayText;
390
+ }
391
+ }
392
+ }
@@ -0,0 +1,49 @@
1
+ // Bestax Extras - Button Styles
2
+ // Bulma's control mixin sets `outline: none` on a focused button and shows
3
+ // focus through the button's border color and a box-shadow instead. Where
4
+ // those show nothing, these give the button Bulma's focus outline, in its
5
+ // focus color, style, width and offset:
6
+ //
7
+ // - A ghost button, which Bulma draws with no border and no shadow, so it
8
+ // shows no focus at all. This one applies outside forced colors too.
9
+ // - Every button in forced colors (Windows' contrast themes), which drops the
10
+ // shadow and forces the border's color, so a focused button can look just
11
+ // like an unfocused one there. A ghost button matches both rules there,
12
+ // and they draw the same outline. A loading button is left to
13
+ // `_loader.scss`, which turns forcing off on it and rings it in the
14
+ // system's colors.
15
+ //
16
+ // `.is-focused` is the class `isFocused` sets, so it gets the outline too.
17
+ // The ghost rule doesn't ask for LinkButton's class, since a ghost Button has
18
+ // none.
19
+ //
20
+ // Bulma's CSS can land on either side of this partial, as `_loader.scss`
21
+ // says, so each selector outranks the mixin's rules by its classes: the
22
+ // ghost rule has three to Bulma's two, and the forced colors rule names
23
+ // `.button` twice, as `_delete.scss` does.
24
+
25
+ @use 'bulma/sass/utilities/initial-variables' as iv;
26
+ @use 'bulma/sass/utilities/css-variables' as cv;
27
+
28
+ @mixin -focus-outline {
29
+ outline-color: hsl(
30
+ cv.getVar('focus-h'),
31
+ cv.getVar('focus-s'),
32
+ cv.getVar('focus-l')
33
+ );
34
+ outline-offset: cv.getVar('focus-offset');
35
+ outline-style: cv.getVar('focus-style');
36
+ outline-width: cv.getVar('focus-width');
37
+ }
38
+
39
+ .#{iv.$class-prefix}button.#{iv.$class-prefix}is-ghost:focus-visible,
40
+ .#{iv.$class-prefix}button.#{iv.$class-prefix}is-ghost.#{iv.$class-prefix}is-focused {
41
+ @include -focus-outline;
42
+ }
43
+
44
+ @media (forced-colors: active) {
45
+ .#{iv.$class-prefix}button.#{iv.$class-prefix}button:not(.#{iv.$class-prefix}is-loading):focus-visible,
46
+ .#{iv.$class-prefix}button.#{iv.$class-prefix}button:not(.#{iv.$class-prefix}is-loading).#{iv.$class-prefix}is-focused {
47
+ @include -focus-outline;
48
+ }
49
+ }
@@ -2,6 +2,7 @@
2
2
  // Import all element partials here
3
3
 
4
4
  // Element Components
5
+ @use 'button';
5
6
  @use 'colorswatch';
6
7
  @use 'delete';
7
8
  @use 'descriptionlist';
@@ -72,14 +72,16 @@
72
72
  // `<button>`, a link's for a link, and no surface for a variant that has
73
73
  // none, as below. Forcing off reaches everything inside too, which the
74
74
  // rule at the top already hides. It also stops the browser recoloring the
75
- // border to show focus, so a focused one gets an outline.
75
+ // border to show focus, so a focused one gets an outline, and so does one
76
+ // with `is-focused`.
76
77
  .#{iv.$class-prefix}button.#{iv.$class-prefix}is-loading {
77
78
  forced-color-adjust: none;
78
79
  background-color: Canvas !important;
79
80
  border-color: CanvasText !important;
80
81
  color: transparent !important;
81
82
 
82
- &:focus-visible {
83
+ &:focus-visible,
84
+ &.#{iv.$class-prefix}is-focused {
83
85
  outline: 2px solid CanvasText;
84
86
  outline-offset: 2px;
85
87
  }
@@ -100,7 +102,8 @@
100
102
  background-color: ButtonFace !important;
101
103
  border-color: ButtonText !important;
102
104
 
103
- &:focus-visible {
105
+ &:focus-visible,
106
+ &.#{iv.$class-prefix}is-focused {
104
107
  outline-color: ButtonText;
105
108
  }
106
109
  }
@@ -108,7 +111,8 @@
108
111
  a[href].#{iv.$class-prefix}button.#{iv.$class-prefix}is-loading {
109
112
  border-color: LinkText !important;
110
113
 
111
- &:focus-visible {
114
+ &:focus-visible,
115
+ &.#{iv.$class-prefix}is-focused {
112
116
  outline-color: LinkText;
113
117
  }
114
118
  }
@@ -123,7 +127,8 @@
123
127
  background-color: transparent !important;
124
128
  border-color: CanvasText !important;
125
129
 
126
- &:focus-visible {
130
+ &:focus-visible,
131
+ &.#{iv.$class-prefix}is-focused {
127
132
  outline-color: CanvasText;
128
133
  }
129
134
  }