basefn 1.7.0 → 1.8.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": "basefn",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/brnrdog/basefn.git"
package/src/Basefn.res CHANGED
@@ -71,6 +71,9 @@ type gridJustifyContent = Basefn__Grid.justifyContent
71
71
  type gridAlignContent = Basefn__Grid.alignContent
72
72
  type gridItemColumnSpan = Basefn__Grid.Item.columnSpan
73
73
  type gridItemRowSpan = Basefn__Grid.Item.rowSpan
74
+ type breakpoint = Basefn__Responsive.breakpoint
75
+ type currentBreakpoint = Basefn__Responsive.currentBreakpoint
76
+ type responsiveValue<'a> = Basefn__Responsive.responsiveValue<'a>
74
77
 
75
78
  // Form Components
76
79
  module Button = {
@@ -224,3 +227,8 @@ module AlertDialog = {
224
227
  module ContextMenu = {
225
228
  include Basefn__ContextMenu
226
229
  }
230
+
231
+ // Responsive Utilities
232
+ module Responsive = {
233
+ include Basefn__Responsive
234
+ }
@@ -37,6 +37,7 @@ import * as Basefn__AppLayout from "./components/Basefn__AppLayout.res.mjs";
37
37
  import * as Basefn__HoverCard from "./components/Basefn__HoverCard.res.mjs";
38
38
  import * as Basefn__Separator from "./components/Basefn__Separator.res.mjs";
39
39
  import * as Basefn__Breadcrumb from "./components/Basefn__Breadcrumb.res.mjs";
40
+ import * as Basefn__Responsive from "./Basefn__Responsive.res.mjs";
40
41
  import * as Basefn__ScrollArea from "./components/Basefn__ScrollArea.res.mjs";
41
42
  import * as Basefn__Typography from "./components/Basefn__Typography.res.mjs";
42
43
  import * as Basefn__AlertDialog from "./components/Basefn__AlertDialog.res.mjs";
@@ -139,6 +140,8 @@ let AlertDialog = Basefn__AlertDialog;
139
140
 
140
141
  let ContextMenu = Basefn__ContextMenu;
141
142
 
143
+ let Responsive = Basefn__Responsive;
144
+
142
145
  export {
143
146
  Button,
144
147
  Input,
@@ -185,5 +188,6 @@ export {
185
188
  HoverCard,
186
189
  AlertDialog,
187
190
  ContextMenu,
191
+ Responsive,
188
192
  }
189
193
  /* Not a pure module */
@@ -0,0 +1,291 @@
1
+ %%raw(`import './components/Basefn__Responsive.css'`)
2
+ open Xote
3
+
4
+ // ============================================================================
5
+ // Breakpoints
6
+ // ============================================================================
7
+
8
+ type breakpoint = Xs | Sm | Md | Lg | Xl | Xxl
9
+
10
+ let breakpointToPixels = (bp: breakpoint): int => {
11
+ switch bp {
12
+ | Xs => 480
13
+ | Sm => 640
14
+ | Md => 768
15
+ | Lg => 1024
16
+ | Xl => 1280
17
+ | Xxl => 1536
18
+ }
19
+ }
20
+
21
+ let breakpointToString = (bp: breakpoint): string => {
22
+ switch bp {
23
+ | Xs => "xs"
24
+ | Sm => "sm"
25
+ | Md => "md"
26
+ | Lg => "lg"
27
+ | Xl => "xl"
28
+ | Xxl => "xxl"
29
+ }
30
+ }
31
+
32
+ // ============================================================================
33
+ // Media query strings
34
+ // ============================================================================
35
+
36
+ let minWidth = (bp: breakpoint): string => {
37
+ let px = breakpointToPixels(bp)
38
+ `(min-width: ${Int.toString(px)}px)`
39
+ }
40
+
41
+ let maxWidth = (bp: breakpoint): string => {
42
+ let px = breakpointToPixels(bp) - 1
43
+ `(max-width: ${Int.toString(px)}px)`
44
+ }
45
+
46
+ let between = (lower: breakpoint, upper: breakpoint): string => {
47
+ let minPx = breakpointToPixels(lower)
48
+ let maxPx = breakpointToPixels(upper) - 1
49
+ `(min-width: ${Int.toString(minPx)}px) and (max-width: ${Int.toString(maxPx)}px)`
50
+ }
51
+
52
+ // Predefined media query strings
53
+ module Query = {
54
+ let xsUp = minWidth(Xs)
55
+ let smUp = minWidth(Sm)
56
+ let mdUp = minWidth(Md)
57
+ let lgUp = minWidth(Lg)
58
+ let xlUp = minWidth(Xl)
59
+ let xxlUp = minWidth(Xxl)
60
+
61
+ let xsDown = maxWidth(Xs)
62
+ let smDown = maxWidth(Sm)
63
+ let mdDown = maxWidth(Md)
64
+ let lgDown = maxWidth(Lg)
65
+ let xlDown = maxWidth(Xl)
66
+ let xxlDown = maxWidth(Xxl)
67
+
68
+ let xsOnly = maxWidth(Sm)
69
+ let smOnly = between(Sm, Md)
70
+ let mdOnly = between(Md, Lg)
71
+ let lgOnly = between(Lg, Xl)
72
+ let xlOnly = between(Xl, Xxl)
73
+ let xxlOnly = minWidth(Xxl)
74
+
75
+ let portrait = "(orientation: portrait)"
76
+ let landscape = "(orientation: landscape)"
77
+ let prefersReducedMotion = "(prefers-reduced-motion: reduce)"
78
+ let prefersDark = "(prefers-color-scheme: dark)"
79
+ let prefersLight = "(prefers-color-scheme: light)"
80
+ let highContrast = "(prefers-contrast: more)"
81
+ let touchDevice = "(hover: none) and (pointer: coarse)"
82
+ let finePointer = "(hover: hover) and (pointer: fine)"
83
+ let retina = "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)"
84
+ }
85
+
86
+ // ============================================================================
87
+ // matchMedia binding
88
+ // ============================================================================
89
+
90
+ type mediaQueryList
91
+
92
+ @val external matchMedia: string => mediaQueryList = "window.matchMedia"
93
+ @get external matches: mediaQueryList => bool = "matches"
94
+ @send external addListener: (mediaQueryList, mediaQueryList => unit) => unit = "addEventListener"
95
+ @send external removeListener: (mediaQueryList, mediaQueryList => unit) => unit = "removeEventListener"
96
+
97
+ let addChangeListener: (mediaQueryList, mediaQueryList => unit) => unit = %raw(`
98
+ function(mql, cb) { mql.addEventListener("change", cb) }
99
+ `)
100
+
101
+ let removeChangeListener: (mediaQueryList, mediaQueryList => unit) => unit = %raw(`
102
+ function(mql, cb) { mql.removeEventListener("change", cb) }
103
+ `)
104
+
105
+ // ============================================================================
106
+ // Media query matching utilities
107
+ // ============================================================================
108
+
109
+ let matchesQuery = (query: string): bool => {
110
+ matches(matchMedia(query))
111
+ }
112
+
113
+ let matchesBreakpointUp = (bp: breakpoint): bool => matchesQuery(minWidth(bp))
114
+ let matchesBreakpointDown = (bp: breakpoint): bool => matchesQuery(maxWidth(bp))
115
+
116
+ // ============================================================================
117
+ // Signal-based media query tracking
118
+ // ============================================================================
119
+
120
+ let makeMediaSignal = (query: string): Signal.t<bool> => {
121
+ let mql = matchMedia(query)
122
+ let signal = Signal.make(matches(mql))
123
+ let handler = (evt: mediaQueryList) => {
124
+ Signal.set(signal, matches(evt))
125
+ }
126
+ addChangeListener(mql, handler)
127
+ signal
128
+ }
129
+
130
+ let makeBreakpointSignal = (bp: breakpoint): Signal.t<bool> => {
131
+ makeMediaSignal(minWidth(bp))
132
+ }
133
+
134
+ // ============================================================================
135
+ // Predefined screen size signals (memoized singletons)
136
+ // ============================================================================
137
+
138
+ // Helper: create a memoized signal that initializes on first access
139
+ let _memo = (make: unit => Signal.t<bool>): (unit => Signal.t<bool>) => {
140
+ let cached: ref<option<Signal.t<bool>>> = ref(None)
141
+ () => {
142
+ switch cached.contents {
143
+ | Some(s) => s
144
+ | None => {
145
+ let s = make()
146
+ cached := Some(s)
147
+ s
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ // Exact breakpoint range signals
154
+ let isXsScreen = _memo(() => makeMediaSignal(Query.xsDown))
155
+ let isSmScreen = _memo(() => makeMediaSignal(Query.smOnly))
156
+ let isMdScreen = _memo(() => makeMediaSignal(Query.mdOnly))
157
+ let isLgScreen = _memo(() => makeMediaSignal(Query.lgOnly))
158
+ let isXlScreen = _memo(() => makeMediaSignal(Query.xlOnly))
159
+ let isXxlScreen = _memo(() => makeMediaSignal(Query.xxlOnly))
160
+
161
+ // "And up" signals
162
+ let isSmallAndUp = _memo(() => makeMediaSignal(Query.smUp))
163
+ let isMediumAndUp = _memo(() => makeMediaSignal(Query.mdUp))
164
+ let isLargeAndUp = _memo(() => makeMediaSignal(Query.lgUp))
165
+ let isExtraLargeAndUp = _memo(() => makeMediaSignal(Query.xlUp))
166
+
167
+ // Semantic device signals
168
+ let isMobile = _memo(() => makeMediaSignal(Query.smDown))
169
+ let isTablet = _memo(() => makeMediaSignal(Query.mdOnly))
170
+ let isDesktop = _memo(() => makeMediaSignal(Query.lgUp))
171
+
172
+ // Preference / capability signals
173
+ let isPortrait = _memo(() => makeMediaSignal(Query.portrait))
174
+ let isLandscape = _memo(() => makeMediaSignal(Query.landscape))
175
+ let prefersReducedMotion = _memo(() => makeMediaSignal(Query.prefersReducedMotion))
176
+ let prefersDarkMode = _memo(() => makeMediaSignal(Query.prefersDark))
177
+ let isTouchDevice = _memo(() => makeMediaSignal(Query.touchDevice))
178
+ let isRetina = _memo(() => makeMediaSignal(Query.retina))
179
+
180
+ // ============================================================================
181
+ // Current breakpoint tracking
182
+ // ============================================================================
183
+
184
+ type currentBreakpoint = ExtraSmall | Small | Medium | Large | ExtraLarge | ExtraExtraLarge
185
+
186
+ let currentBreakpointToString = (bp: currentBreakpoint): string => {
187
+ switch bp {
188
+ | ExtraSmall => "xs"
189
+ | Small => "sm"
190
+ | Medium => "md"
191
+ | Large => "lg"
192
+ | ExtraLarge => "xl"
193
+ | ExtraExtraLarge => "xxl"
194
+ }
195
+ }
196
+
197
+ let getCurrentBreakpoint = (): currentBreakpoint => {
198
+ if matchesQuery(Query.xxlUp) {
199
+ ExtraExtraLarge
200
+ } else if matchesQuery(Query.xlUp) {
201
+ ExtraLarge
202
+ } else if matchesQuery(Query.lgUp) {
203
+ Large
204
+ } else if matchesQuery(Query.mdUp) {
205
+ Medium
206
+ } else if matchesQuery(Query.smUp) {
207
+ Small
208
+ } else {
209
+ ExtraSmall
210
+ }
211
+ }
212
+
213
+ let makeCurrentBreakpointSignal = (): Signal.t<currentBreakpoint> => {
214
+ let signal = Signal.make(getCurrentBreakpoint())
215
+
216
+ let breakpoints = [Sm, Md, Lg, Xl, Xxl]
217
+ breakpoints->Array.forEach(bp => {
218
+ let mql = matchMedia(minWidth(bp))
219
+ let _handler = (_evt: mediaQueryList) => {
220
+ Signal.set(signal, getCurrentBreakpoint())
221
+ }
222
+ addChangeListener(mql, _handler)
223
+ })
224
+
225
+ signal
226
+ }
227
+
228
+ let currentBreakpoint: ref<option<Signal.t<currentBreakpoint>>> = ref(None)
229
+
230
+ let getCurrentBreakpointSignal = (): Signal.t<currentBreakpoint> => {
231
+ switch currentBreakpoint.contents {
232
+ | Some(s) => s
233
+ | None => {
234
+ let s = makeCurrentBreakpointSignal()
235
+ currentBreakpoint := Some(s)
236
+ s
237
+ }
238
+ }
239
+ }
240
+
241
+ // ============================================================================
242
+ // Responsive value helpers
243
+ // ============================================================================
244
+
245
+ type responsiveValue<'a> = {
246
+ xs?: 'a,
247
+ sm?: 'a,
248
+ md?: 'a,
249
+ lg?: 'a,
250
+ xl?: 'a,
251
+ xxl?: 'a,
252
+ }
253
+
254
+ let resolveResponsiveValue = (value: responsiveValue<'a>, fallback: 'a): 'a => {
255
+ let bp = getCurrentBreakpoint()
256
+ // Build ordered list from current breakpoint down, cascade to find first defined value
257
+ let ordered = switch bp {
258
+ | ExtraExtraLarge => [value.xxl, value.xl, value.lg, value.md, value.sm, value.xs]
259
+ | ExtraLarge => [value.xl, value.lg, value.md, value.sm, value.xs]
260
+ | Large => [value.lg, value.md, value.sm, value.xs]
261
+ | Medium => [value.md, value.sm, value.xs]
262
+ | Small => [value.sm, value.xs]
263
+ | ExtraSmall => [value.xs]
264
+ }
265
+ let rec find = (items: array<option<'a>>, idx: int): 'a => {
266
+ if idx >= Array.length(items) {
267
+ fallback
268
+ } else {
269
+ switch items->Array.getUnsafe(idx) {
270
+ | Some(v) => v
271
+ | None => find(items, idx + 1)
272
+ }
273
+ }
274
+ }
275
+ find(ordered, 0)
276
+ }
277
+
278
+ // ============================================================================
279
+ // Visibility helpers (CSS class-based)
280
+ // ============================================================================
281
+
282
+ module Visibility = {
283
+ let hiddenBelow = (bp: breakpoint): string =>
284
+ `basefn-hidden-below-${breakpointToString(bp)}`
285
+
286
+ let hiddenAbove = (bp: breakpoint): string =>
287
+ `basefn-hidden-above-${breakpointToString(bp)}`
288
+
289
+ let visibleOnly = (bp: breakpoint): string =>
290
+ `basefn-visible-${breakpointToString(bp)}-only`
291
+ }
@@ -0,0 +1,407 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Xote from "xote/src/Xote.res.mjs";
4
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
5
+
6
+ import './components/Basefn__Responsive.css'
7
+ ;
8
+
9
+ function breakpointToPixels(bp) {
10
+ switch (bp) {
11
+ case "Xs" :
12
+ return 480;
13
+ case "Sm" :
14
+ return 640;
15
+ case "Md" :
16
+ return 768;
17
+ case "Lg" :
18
+ return 1024;
19
+ case "Xl" :
20
+ return 1280;
21
+ case "Xxl" :
22
+ return 1536;
23
+ }
24
+ }
25
+
26
+ function breakpointToString(bp) {
27
+ switch (bp) {
28
+ case "Xs" :
29
+ return "xs";
30
+ case "Sm" :
31
+ return "sm";
32
+ case "Md" :
33
+ return "md";
34
+ case "Lg" :
35
+ return "lg";
36
+ case "Xl" :
37
+ return "xl";
38
+ case "Xxl" :
39
+ return "xxl";
40
+ }
41
+ }
42
+
43
+ function minWidth(bp) {
44
+ let px = breakpointToPixels(bp);
45
+ return `(min-width: ` + px.toString() + `px)`;
46
+ }
47
+
48
+ function maxWidth(bp) {
49
+ let px = breakpointToPixels(bp) - 1 | 0;
50
+ return `(max-width: ` + px.toString() + `px)`;
51
+ }
52
+
53
+ function between(lower, upper) {
54
+ let minPx = breakpointToPixels(lower);
55
+ let maxPx = breakpointToPixels(upper) - 1 | 0;
56
+ return `(min-width: ` + minPx.toString() + `px) and (max-width: ` + maxPx.toString() + `px)`;
57
+ }
58
+
59
+ let xsUp = minWidth("Xs");
60
+
61
+ let smUp = minWidth("Sm");
62
+
63
+ let mdUp = minWidth("Md");
64
+
65
+ let lgUp = minWidth("Lg");
66
+
67
+ let xlUp = minWidth("Xl");
68
+
69
+ let xxlUp = minWidth("Xxl");
70
+
71
+ let xsDown = maxWidth("Xs");
72
+
73
+ let smDown = maxWidth("Sm");
74
+
75
+ let mdDown = maxWidth("Md");
76
+
77
+ let lgDown = maxWidth("Lg");
78
+
79
+ let xlDown = maxWidth("Xl");
80
+
81
+ let xxlDown = maxWidth("Xxl");
82
+
83
+ let xsOnly = maxWidth("Sm");
84
+
85
+ let smOnly = between("Sm", "Md");
86
+
87
+ let mdOnly = between("Md", "Lg");
88
+
89
+ let lgOnly = between("Lg", "Xl");
90
+
91
+ let xlOnly = between("Xl", "Xxl");
92
+
93
+ let xxlOnly = minWidth("Xxl");
94
+
95
+ let portrait = "(orientation: portrait)";
96
+
97
+ let landscape = "(orientation: landscape)";
98
+
99
+ let prefersReducedMotion = "(prefers-reduced-motion: reduce)";
100
+
101
+ let prefersDark = "(prefers-color-scheme: dark)";
102
+
103
+ let touchDevice = "(hover: none) and (pointer: coarse)";
104
+
105
+ let retina = "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)";
106
+
107
+ let Query = {
108
+ xsUp: xsUp,
109
+ smUp: smUp,
110
+ mdUp: mdUp,
111
+ lgUp: lgUp,
112
+ xlUp: xlUp,
113
+ xxlUp: xxlUp,
114
+ xsDown: xsDown,
115
+ smDown: smDown,
116
+ mdDown: mdDown,
117
+ lgDown: lgDown,
118
+ xlDown: xlDown,
119
+ xxlDown: xxlDown,
120
+ xsOnly: xsOnly,
121
+ smOnly: smOnly,
122
+ mdOnly: mdOnly,
123
+ lgOnly: lgOnly,
124
+ xlOnly: xlOnly,
125
+ xxlOnly: xxlOnly,
126
+ portrait: portrait,
127
+ landscape: landscape,
128
+ prefersReducedMotion: prefersReducedMotion,
129
+ prefersDark: prefersDark,
130
+ prefersLight: "(prefers-color-scheme: light)",
131
+ highContrast: "(prefers-contrast: more)",
132
+ touchDevice: touchDevice,
133
+ finePointer: "(hover: hover) and (pointer: fine)",
134
+ retina: retina
135
+ };
136
+
137
+ let addChangeListener = (function(mql, cb) { mql.addEventListener("change", cb) });
138
+
139
+ let removeChangeListener = (function(mql, cb) { mql.removeEventListener("change", cb) });
140
+
141
+ function matchesQuery(query) {
142
+ return window.matchMedia(query).matches;
143
+ }
144
+
145
+ function matchesBreakpointUp(bp) {
146
+ let query = minWidth(bp);
147
+ return window.matchMedia(query).matches;
148
+ }
149
+
150
+ function matchesBreakpointDown(bp) {
151
+ let query = maxWidth(bp);
152
+ return window.matchMedia(query).matches;
153
+ }
154
+
155
+ function makeMediaSignal(query) {
156
+ let mql = window.matchMedia(query);
157
+ let signal = Xote.Signal.make(mql.matches, undefined, undefined);
158
+ let handler = evt => Xote.Signal.set(signal, evt.matches);
159
+ addChangeListener(mql, handler);
160
+ return signal;
161
+ }
162
+
163
+ function makeBreakpointSignal(bp) {
164
+ return makeMediaSignal(minWidth(bp));
165
+ }
166
+
167
+ function _memo(make) {
168
+ let cached = {
169
+ contents: undefined
170
+ };
171
+ return () => {
172
+ let s = cached.contents;
173
+ if (s !== undefined) {
174
+ return s;
175
+ }
176
+ let s$1 = make();
177
+ cached.contents = s$1;
178
+ return s$1;
179
+ };
180
+ }
181
+
182
+ let isXsScreen = _memo(() => makeMediaSignal(xsDown));
183
+
184
+ let isSmScreen = _memo(() => makeMediaSignal(smOnly));
185
+
186
+ let isMdScreen = _memo(() => makeMediaSignal(mdOnly));
187
+
188
+ let isLgScreen = _memo(() => makeMediaSignal(lgOnly));
189
+
190
+ let isXlScreen = _memo(() => makeMediaSignal(xlOnly));
191
+
192
+ let isXxlScreen = _memo(() => makeMediaSignal(xxlOnly));
193
+
194
+ let isSmallAndUp = _memo(() => makeMediaSignal(smUp));
195
+
196
+ let isMediumAndUp = _memo(() => makeMediaSignal(mdUp));
197
+
198
+ let isLargeAndUp = _memo(() => makeMediaSignal(lgUp));
199
+
200
+ let isExtraLargeAndUp = _memo(() => makeMediaSignal(xlUp));
201
+
202
+ let isMobile = _memo(() => makeMediaSignal(smDown));
203
+
204
+ let isTablet = _memo(() => makeMediaSignal(mdOnly));
205
+
206
+ let isDesktop = _memo(() => makeMediaSignal(lgUp));
207
+
208
+ let isPortrait = _memo(() => makeMediaSignal(portrait));
209
+
210
+ let isLandscape = _memo(() => makeMediaSignal(landscape));
211
+
212
+ let prefersReducedMotion$1 = _memo(() => makeMediaSignal(prefersReducedMotion));
213
+
214
+ let prefersDarkMode = _memo(() => makeMediaSignal(prefersDark));
215
+
216
+ let isTouchDevice = _memo(() => makeMediaSignal(touchDevice));
217
+
218
+ let isRetina = _memo(() => makeMediaSignal(retina));
219
+
220
+ function currentBreakpointToString(bp) {
221
+ switch (bp) {
222
+ case "ExtraSmall" :
223
+ return "xs";
224
+ case "Small" :
225
+ return "sm";
226
+ case "Medium" :
227
+ return "md";
228
+ case "Large" :
229
+ return "lg";
230
+ case "ExtraLarge" :
231
+ return "xl";
232
+ case "ExtraExtraLarge" :
233
+ return "xxl";
234
+ }
235
+ }
236
+
237
+ function getCurrentBreakpoint() {
238
+ if (window.matchMedia(xxlUp).matches) {
239
+ return "ExtraExtraLarge";
240
+ } else if (window.matchMedia(xlUp).matches) {
241
+ return "ExtraLarge";
242
+ } else if (window.matchMedia(lgUp).matches) {
243
+ return "Large";
244
+ } else if (window.matchMedia(mdUp).matches) {
245
+ return "Medium";
246
+ } else if (window.matchMedia(smUp).matches) {
247
+ return "Small";
248
+ } else {
249
+ return "ExtraSmall";
250
+ }
251
+ }
252
+
253
+ function makeCurrentBreakpointSignal() {
254
+ let signal = Xote.Signal.make(getCurrentBreakpoint(), undefined, undefined);
255
+ let breakpoints = [
256
+ "Sm",
257
+ "Md",
258
+ "Lg",
259
+ "Xl",
260
+ "Xxl"
261
+ ];
262
+ breakpoints.forEach(bp => {
263
+ let mql = window.matchMedia(minWidth(bp));
264
+ let _handler = _evt => Xote.Signal.set(signal, getCurrentBreakpoint());
265
+ addChangeListener(mql, _handler);
266
+ });
267
+ return signal;
268
+ }
269
+
270
+ let currentBreakpoint = {
271
+ contents: undefined
272
+ };
273
+
274
+ function getCurrentBreakpointSignal() {
275
+ let s = currentBreakpoint.contents;
276
+ if (s !== undefined) {
277
+ return s;
278
+ }
279
+ let s$1 = makeCurrentBreakpointSignal();
280
+ currentBreakpoint.contents = s$1;
281
+ return s$1;
282
+ }
283
+
284
+ function resolveResponsiveValue(value, fallback) {
285
+ let bp = getCurrentBreakpoint();
286
+ let ordered;
287
+ switch (bp) {
288
+ case "ExtraSmall" :
289
+ ordered = [value.xs];
290
+ break;
291
+ case "Small" :
292
+ ordered = [
293
+ value.sm,
294
+ value.xs
295
+ ];
296
+ break;
297
+ case "Medium" :
298
+ ordered = [
299
+ value.md,
300
+ value.sm,
301
+ value.xs
302
+ ];
303
+ break;
304
+ case "Large" :
305
+ ordered = [
306
+ value.lg,
307
+ value.md,
308
+ value.sm,
309
+ value.xs
310
+ ];
311
+ break;
312
+ case "ExtraLarge" :
313
+ ordered = [
314
+ value.xl,
315
+ value.lg,
316
+ value.md,
317
+ value.sm,
318
+ value.xs
319
+ ];
320
+ break;
321
+ case "ExtraExtraLarge" :
322
+ ordered = [
323
+ value.xxl,
324
+ value.xl,
325
+ value.lg,
326
+ value.md,
327
+ value.sm,
328
+ value.xs
329
+ ];
330
+ break;
331
+ }
332
+ let _idx = 0;
333
+ while (true) {
334
+ let idx = _idx;
335
+ if (idx >= ordered.length) {
336
+ return fallback;
337
+ }
338
+ let v = ordered[idx];
339
+ if (v !== undefined) {
340
+ return Primitive_option.valFromOption(v);
341
+ }
342
+ _idx = idx + 1 | 0;
343
+ continue;
344
+ };
345
+ }
346
+
347
+ function hiddenBelow(bp) {
348
+ return `basefn-hidden-below-` + breakpointToString(bp);
349
+ }
350
+
351
+ function hiddenAbove(bp) {
352
+ return `basefn-hidden-above-` + breakpointToString(bp);
353
+ }
354
+
355
+ function visibleOnly(bp) {
356
+ return `basefn-visible-` + breakpointToString(bp) + `-only`;
357
+ }
358
+
359
+ let Visibility = {
360
+ hiddenBelow: hiddenBelow,
361
+ hiddenAbove: hiddenAbove,
362
+ visibleOnly: visibleOnly
363
+ };
364
+
365
+ export {
366
+ breakpointToPixels,
367
+ breakpointToString,
368
+ minWidth,
369
+ maxWidth,
370
+ between,
371
+ Query,
372
+ addChangeListener,
373
+ removeChangeListener,
374
+ matchesQuery,
375
+ matchesBreakpointUp,
376
+ matchesBreakpointDown,
377
+ makeMediaSignal,
378
+ makeBreakpointSignal,
379
+ _memo,
380
+ isXsScreen,
381
+ isSmScreen,
382
+ isMdScreen,
383
+ isLgScreen,
384
+ isXlScreen,
385
+ isXxlScreen,
386
+ isSmallAndUp,
387
+ isMediumAndUp,
388
+ isLargeAndUp,
389
+ isExtraLargeAndUp,
390
+ isMobile,
391
+ isTablet,
392
+ isDesktop,
393
+ isPortrait,
394
+ isLandscape,
395
+ prefersReducedMotion$1 as prefersReducedMotion,
396
+ prefersDarkMode,
397
+ isTouchDevice,
398
+ isRetina,
399
+ currentBreakpointToString,
400
+ getCurrentBreakpoint,
401
+ makeCurrentBreakpointSignal,
402
+ currentBreakpoint,
403
+ getCurrentBreakpointSignal,
404
+ resolveResponsiveValue,
405
+ Visibility,
406
+ }
407
+ /* Not a pure module */
@@ -0,0 +1,118 @@
1
+ /* ============================================================================
2
+ Responsive Visibility Utilities
3
+ ============================================================================ */
4
+
5
+ /* Hidden below breakpoint (hidden on screens smaller than) */
6
+ @media (max-width: 479px) {
7
+ .basefn-hidden-below-xs { display: none !important; }
8
+ }
9
+ @media (max-width: 639px) {
10
+ .basefn-hidden-below-sm { display: none !important; }
11
+ }
12
+ @media (max-width: 767px) {
13
+ .basefn-hidden-below-md { display: none !important; }
14
+ }
15
+ @media (max-width: 1023px) {
16
+ .basefn-hidden-below-lg { display: none !important; }
17
+ }
18
+ @media (max-width: 1279px) {
19
+ .basefn-hidden-below-xl { display: none !important; }
20
+ }
21
+ @media (max-width: 1535px) {
22
+ .basefn-hidden-below-xxl { display: none !important; }
23
+ }
24
+
25
+ /* Hidden above breakpoint (hidden on screens larger than) */
26
+ @media (min-width: 480px) {
27
+ .basefn-hidden-above-xs { display: none !important; }
28
+ }
29
+ @media (min-width: 640px) {
30
+ .basefn-hidden-above-sm { display: none !important; }
31
+ }
32
+ @media (min-width: 768px) {
33
+ .basefn-hidden-above-md { display: none !important; }
34
+ }
35
+ @media (min-width: 1024px) {
36
+ .basefn-hidden-above-lg { display: none !important; }
37
+ }
38
+ @media (min-width: 1280px) {
39
+ .basefn-hidden-above-xl { display: none !important; }
40
+ }
41
+ @media (min-width: 1536px) {
42
+ .basefn-hidden-above-xxl { display: none !important; }
43
+ }
44
+
45
+ /* Visible only at specific breakpoint */
46
+ .basefn-visible-xs-only {
47
+ display: none !important;
48
+ }
49
+ @media (max-width: 479px) {
50
+ .basefn-visible-xs-only { display: revert !important; }
51
+ }
52
+
53
+ .basefn-visible-sm-only {
54
+ display: none !important;
55
+ }
56
+ @media (min-width: 640px) and (max-width: 767px) {
57
+ .basefn-visible-sm-only { display: revert !important; }
58
+ }
59
+
60
+ .basefn-visible-md-only {
61
+ display: none !important;
62
+ }
63
+ @media (min-width: 768px) and (max-width: 1023px) {
64
+ .basefn-visible-md-only { display: revert !important; }
65
+ }
66
+
67
+ .basefn-visible-lg-only {
68
+ display: none !important;
69
+ }
70
+ @media (min-width: 1024px) and (max-width: 1279px) {
71
+ .basefn-visible-lg-only { display: revert !important; }
72
+ }
73
+
74
+ .basefn-visible-xl-only {
75
+ display: none !important;
76
+ }
77
+ @media (min-width: 1280px) and (max-width: 1535px) {
78
+ .basefn-visible-xl-only { display: revert !important; }
79
+ }
80
+
81
+ .basefn-visible-xxl-only {
82
+ display: none !important;
83
+ }
84
+ @media (min-width: 1536px) {
85
+ .basefn-visible-xxl-only { display: revert !important; }
86
+ }
87
+
88
+ /* Orientation-based visibility */
89
+ .basefn-hidden-portrait {
90
+ display: revert !important;
91
+ }
92
+ @media (orientation: portrait) {
93
+ .basefn-hidden-portrait { display: none !important; }
94
+ }
95
+
96
+ .basefn-hidden-landscape {
97
+ display: revert !important;
98
+ }
99
+ @media (orientation: landscape) {
100
+ .basefn-hidden-landscape { display: none !important; }
101
+ }
102
+
103
+ /* Device-based visibility */
104
+ @media (hover: none) and (pointer: coarse) {
105
+ .basefn-hidden-touch { display: none !important; }
106
+ }
107
+
108
+ @media (hover: hover) and (pointer: fine) {
109
+ .basefn-hidden-desktop { display: none !important; }
110
+ }
111
+
112
+ /* Print visibility */
113
+ @media print {
114
+ .basefn-hidden-print { display: none !important; }
115
+ }
116
+ @media not print {
117
+ .basefn-visible-print-only { display: none !important; }
118
+ }