opentui-responsive 0.3.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/README.md CHANGED
@@ -58,15 +58,18 @@ export function Layout(props: ParentProps) {
58
58
  import { useBreakpoint } from "./layout.tsx";
59
59
 
60
60
  export function Content() {
61
- const breakpoint = useBreakpoint();
62
- // You can use it to directly check [width, height] breakpoint
63
- const isWideAndTall = breakpoint(["wide", "tall"]);
61
+ const [width, height, viewport] = useBreakpoint();
62
+ // Each accessor can be named for its local use.
63
+ const isWideAndTall = viewport(["wide", "tall"]);
64
+ const isMediumOrWider = width.atLeast("medium");
65
+ const hasMediumViewport = viewport.atLeast(["medium", "medium"]);
64
66
 
65
67
  return (
66
- // Or you can get the current width and height breakpoint
67
- <box flexDirection={breakpoint().width === "wide" ? "row" : "column"}>
68
- <text>{`${breakpoint().width}/${breakpoint().height}`}</text>
69
- <text>{isWideAndTall ? "Full layout" : "Compact layout"}</text>
68
+ <box flexDirection={width() === "wide" ? "row" : "column"}>
69
+ <text>{`${width()}/${height()}`}</text>
70
+ <text>
71
+ {isWideAndTall || (isMediumOrWider && hasMediumViewport) ? "Full layout" : "Compact layout"}
72
+ </text>
70
73
  </box>
71
74
  );
72
75
  }
@@ -87,7 +90,20 @@ export function App() {
87
90
  }
88
91
  ```
89
92
 
90
- `useBreakpoint()` updates when the terminal crosses a configured threshold. Its accessor returns the current width and height names, or accepts an exact `[width, height]` pair and returns whether both axes match.
93
+ `useBreakpoint()` updates when the terminal crosses a configured threshold. It returns `[width, height, viewport]`, letting each caller choose local names while keeping breakpoint names scoped to their axis. Width and height accept one breakpoint name; viewport accepts a `[width, height]` pair. Viewport pair comparisons return true only when both axes satisfy the relation.
94
+
95
+ ```tsx
96
+ const [width, height, viewport] = useBreakpoint();
97
+
98
+ width.below("medium");
99
+ viewport.atMost(["medium", "tall"]);
100
+ height.only("short");
101
+ viewport.atLeast(["medium", "medium"]);
102
+ width.above("medium");
103
+
104
+ const [sidebarWidth] = useBreakpoint();
105
+ const [, , layoutViewport] = useBreakpoint();
106
+ ```
91
107
 
92
108
  Run `bun run demo` from the package directory to try the Solid example. Resize the terminal to see its layout respond.
93
109
 
@@ -97,14 +113,16 @@ The React adapter has the same factory, provider, hook, and inferred breakpoint
97
113
 
98
114
  ### `opentui-responsive/solid` and `opentui-responsive/react`
99
115
 
100
- | API | Description |
101
- | --------------------------------- | ------------------------------------------------------------------------------------- |
102
- | `createResponsiveTui(scales)` | Validates the scales and creates a `ResponsiveTUI` provider and `useBreakpoint` hook. |
103
- | `ResponsiveTUI` | Tracks terminal dimensions and provides the current breakpoint accessor. |
104
- | `useBreakpoint()` | Reads the accessor from the nearest generated provider. |
105
- | `ResponsiveBreakpointAccessor` | Reads the current match or tests an exact breakpoint pair. |
106
- | `ResponsiveTuiConfigurationError` | Thrown when the supplied breakpoint scales are invalid. |
107
- | `ResponsiveTuiProviderError` | Thrown when the generated hook is called outside its provider. |
116
+ | API | Description |
117
+ | -------------------------------------- | ------------------------------------------------------------------------------------- |
118
+ | `createResponsiveTui(scales)` | Validates the scales and creates a `ResponsiveTUI` provider and `useBreakpoint` hook. |
119
+ | `ResponsiveTUI` | Tracks terminal dimensions and provides the current breakpoint accessor. |
120
+ | `useBreakpoint()` | Reads `[width, height, viewport]` from the nearest generated provider. |
121
+ | `ResponsiveBreakpointAccessor` | The readonly `[width, height, viewport]` accessor tuple. |
122
+ | `ResponsiveBreakpointAxisAccessor` | Reads or compares one axis using that axis's breakpoint names. |
123
+ | `ResponsiveBreakpointViewportAccessor` | Reads or compares the complete viewport using `[width, height]` pairs. |
124
+ | `ResponsiveTuiConfigurationError` | Thrown when the supplied breakpoint scales are invalid. |
125
+ | `ResponsiveTuiProviderError` | Thrown when the generated hook is called outside its provider. |
108
126
 
109
127
  ### `opentui-responsive/core`
110
128
 
@@ -117,6 +135,7 @@ For implementing another framework adapter, not application setup:
117
135
  | `BreakpointScales` | The width and height scale configuration. |
118
136
  | `BreakpointMatch` | The matched breakpoint name for each axis. |
119
137
  | `BreakpointPair` | An exact breakpoint pair in width-then-height order. |
138
+ | `BreakpointRelationMatcher` | Tests one axis or an AND-combined pair against a named relation. |
120
139
  | `ResponsiveTuiConfigurationError` | Thrown when supplied breakpoint scales are invalid. |
121
140
 
122
141
  ## Breakpoint Behavior
@@ -125,6 +144,18 @@ Width and height use independent sets of inclusive minimum thresholds measured i
125
144
 
126
145
  Declaration order does not affect matching. Each axis selects its highest satisfied threshold.
127
146
 
147
+ Relation methods compare complete tiers rather than their raw threshold values. Given `width: { narrow: 0, medium: 60, wide: 100 }`, `width.atMost("medium")` matches widths from `0` through `99`.
148
+
149
+ | Method | Tier relation |
150
+ | --------- | ------------- |
151
+ | `below` | `<` |
152
+ | `atMost` | `<=` |
153
+ | `only` | `===` |
154
+ | `atLeast` | `>=` |
155
+ | `above` | `>` |
156
+
157
+ Width and height relation methods each accept a name from their own scale. Viewport relation methods accept `[width, height]` pairs and use AND semantics. Calling `viewport.only(["wide", "tall"])` is equivalent to calling `viewport(["wide", "tall"])`. The `/core` relation matchers retain `(viewport, axis, name)` and `(viewport, [width, height])` forms for adapter implementations.
158
+
128
159
  Use breakpoints for discrete layout modes. Keep continuous measurements such as progress-bar width and available list height on OpenTUI's `useTerminalDimensions()`.
129
160
 
130
161
  ## Runtime Support
@@ -24,10 +24,20 @@ export type BreakpointPair<Scales extends BreakpointScales = BreakpointScales> =
24
24
  width: BreakpointMatch<Scales>["width"],
25
25
  height: BreakpointMatch<Scales>["height"]
26
26
  ];
27
+ /** Tests one axis or both axes against a named breakpoint relation. */
28
+ export type BreakpointRelationMatcher<Scales extends BreakpointScales = BreakpointScales> = {
29
+ <Axis extends BreakpointAxis>(viewport: BreakpointViewport, axis: Axis, name: BreakpointMatch<Scales>[Axis]): boolean;
30
+ (viewport: BreakpointViewport, pair: BreakpointPair<Scales>): boolean;
31
+ };
27
32
  /** A validated breakpoint matcher for framework adapter implementations. */
28
33
  export type BreakpointDefinition<Scales extends BreakpointScales = BreakpointScales> = {
29
34
  readonly match: (viewport: BreakpointViewport) => BreakpointMatch<Scales>;
30
35
  readonly matches: (viewport: BreakpointViewport, pair: BreakpointPair<Scales>) => boolean;
36
+ readonly below: BreakpointRelationMatcher<Scales>;
37
+ readonly atMost: BreakpointRelationMatcher<Scales>;
38
+ readonly only: BreakpointRelationMatcher<Scales>;
39
+ readonly atLeast: BreakpointRelationMatcher<Scales>;
40
+ readonly above: BreakpointRelationMatcher<Scales>;
31
41
  };
32
42
  /** Thrown when breakpoint scales cannot produce a valid definition. */
33
43
  export declare class ResponsiveTuiConfigurationError extends Error {
@@ -12,12 +12,19 @@ var createBreakpointDefinition = (scales) => {
12
12
  width: matchScale(viewport.width, entries.width),
13
13
  height: matchScale(viewport.height, entries.height)
14
14
  });
15
+ const below = createRelationMatcher(entries, "below");
16
+ const atMost = createRelationMatcher(entries, "atMost");
17
+ const only = createRelationMatcher(entries, "only");
18
+ const atLeast = createRelationMatcher(entries, "atLeast");
19
+ const above = createRelationMatcher(entries, "above");
15
20
  return {
16
21
  match,
17
- matches: (viewport, pair) => {
18
- const current = match(viewport);
19
- return current.width === pair[0] && current.height === pair[1];
20
- }
22
+ matches: (viewport, pair) => only(viewport, pair),
23
+ below,
24
+ atMost,
25
+ only,
26
+ atLeast,
27
+ above
21
28
  };
22
29
  };
23
30
  var breakpointAxes = ["width", "height"];
@@ -63,14 +70,39 @@ var validateScale = (axis, scale) => {
63
70
  return validated.sort((left, right) => left[1] - right[1]);
64
71
  };
65
72
  var matchScale = (value, entries) => {
66
- let match = entries[0][0];
67
- for (const [name, threshold] of entries) {
68
- if (value < threshold)
73
+ return entries[matchScaleIndex(value, entries)][0];
74
+ };
75
+ var matchScaleIndex = (value, entries) => {
76
+ let match = 0;
77
+ for (let index = 1;index < entries.length; index += 1) {
78
+ if (value < entries[index][1])
69
79
  break;
70
- match = name;
80
+ match = index;
71
81
  }
72
82
  return match;
73
83
  };
84
+ var createRelationMatcher = (entries, relation) => (viewport, axisOrPair, name) => {
85
+ if (Array.isArray(axisOrPair)) {
86
+ return matchesRelation(viewport.width, entries.width, axisOrPair[0], relation) && matchesRelation(viewport.height, entries.height, axisOrPair[1], relation);
87
+ }
88
+ const axis = axisOrPair;
89
+ return matchesRelation(viewport[axis], entries[axis], name, relation);
90
+ };
91
+ var matchesRelation = (value, entries, name, relation) => {
92
+ const target = entries.findIndex(([entryName]) => entryName === name);
93
+ if (target === -1)
94
+ return false;
95
+ const current = matchScaleIndex(value, entries);
96
+ if (relation === "below")
97
+ return current < target;
98
+ if (relation === "atMost")
99
+ return current <= target;
100
+ if (relation === "only")
101
+ return current === target;
102
+ if (relation === "atLeast")
103
+ return current >= target;
104
+ return current > target;
105
+ };
74
106
  var isRecord = (value) => typeof value === "object" && value !== null;
75
107
  var isThreshold = (value) => typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
76
108
  export {
@@ -1,11 +1,36 @@
1
1
  import { type PropsWithChildren } from "react";
2
- import { createBreakpointDefinition, type BreakpointMatch, type BreakpointPair, type BreakpointScales } from "../core/index.js";
2
+ import { createBreakpointDefinition, type BreakpointAxis, type BreakpointMatch, type BreakpointPair, type BreakpointScales } from "../core/index.js";
3
3
  export { ResponsiveTuiConfigurationError } from "../core/index.js";
4
- /** A React accessor that reads or tests the current breakpoint pair. */
5
- export type ResponsiveBreakpointAccessor<Scales extends BreakpointScales = BreakpointScales> = {
4
+ /** A React breakpoint relation test for one axis. */
5
+ export type ResponsiveBreakpointAxisRelationMatcher<Scales extends BreakpointScales = BreakpointScales, Axis extends BreakpointAxis = BreakpointAxis> = {
6
+ (name: BreakpointMatch<Scales>[Axis]): boolean;
7
+ };
8
+ /** A React accessor that reads or tests one breakpoint axis. */
9
+ export type ResponsiveBreakpointAxisAccessor<Scales extends BreakpointScales = BreakpointScales, Axis extends BreakpointAxis = BreakpointAxis> = {
10
+ (): BreakpointMatch<Scales>[Axis];
11
+ (name: BreakpointMatch<Scales>[Axis]): boolean;
12
+ readonly below: ResponsiveBreakpointAxisRelationMatcher<Scales, Axis>;
13
+ readonly atMost: ResponsiveBreakpointAxisRelationMatcher<Scales, Axis>;
14
+ readonly only: ResponsiveBreakpointAxisRelationMatcher<Scales, Axis>;
15
+ readonly atLeast: ResponsiveBreakpointAxisRelationMatcher<Scales, Axis>;
16
+ readonly above: ResponsiveBreakpointAxisRelationMatcher<Scales, Axis>;
17
+ };
18
+ /** A React accessor that reads or tests the complete breakpoint viewport. */
19
+ export type ResponsiveBreakpointViewportAccessor<Scales extends BreakpointScales = BreakpointScales> = {
6
20
  (): BreakpointMatch<Scales>;
7
21
  (pair: BreakpointPair<Scales>): boolean;
22
+ readonly below: (pair: BreakpointPair<Scales>) => boolean;
23
+ readonly atMost: (pair: BreakpointPair<Scales>) => boolean;
24
+ readonly only: (pair: BreakpointPair<Scales>) => boolean;
25
+ readonly atLeast: (pair: BreakpointPair<Scales>) => boolean;
26
+ readonly above: (pair: BreakpointPair<Scales>) => boolean;
8
27
  };
28
+ /** React breakpoint accessors in width, height, then viewport order. */
29
+ export type ResponsiveBreakpointAccessor<Scales extends BreakpointScales = BreakpointScales> = readonly [
30
+ width: ResponsiveBreakpointAxisAccessor<Scales, "width">,
31
+ height: ResponsiveBreakpointAxisAccessor<Scales, "height">,
32
+ viewport: ResponsiveBreakpointViewportAccessor<Scales>
33
+ ];
9
34
  /** Thrown when a responsive hook is used outside its generated React provider. */
10
35
  export declare class ResponsiveTuiProviderError extends Error {
11
36
  readonly _tag = "ResponsiveTuiProviderError";
@@ -16,12 +16,19 @@ var createBreakpointDefinition = (scales) => {
16
16
  width: matchScale(viewport.width, entries.width),
17
17
  height: matchScale(viewport.height, entries.height)
18
18
  });
19
+ const below = createRelationMatcher(entries, "below");
20
+ const atMost = createRelationMatcher(entries, "atMost");
21
+ const only = createRelationMatcher(entries, "only");
22
+ const atLeast = createRelationMatcher(entries, "atLeast");
23
+ const above = createRelationMatcher(entries, "above");
19
24
  return {
20
25
  match,
21
- matches: (viewport, pair) => {
22
- const current = match(viewport);
23
- return current.width === pair[0] && current.height === pair[1];
24
- }
26
+ matches: (viewport, pair) => only(viewport, pair),
27
+ below,
28
+ atMost,
29
+ only,
30
+ atLeast,
31
+ above
25
32
  };
26
33
  };
27
34
  var breakpointAxes = ["width", "height"];
@@ -67,14 +74,39 @@ var validateScale = (axis, scale) => {
67
74
  return validated.sort((left, right) => left[1] - right[1]);
68
75
  };
69
76
  var matchScale = (value, entries) => {
70
- let match = entries[0][0];
71
- for (const [name, threshold] of entries) {
72
- if (value < threshold)
77
+ return entries[matchScaleIndex(value, entries)][0];
78
+ };
79
+ var matchScaleIndex = (value, entries) => {
80
+ let match = 0;
81
+ for (let index = 1;index < entries.length; index += 1) {
82
+ if (value < entries[index][1])
73
83
  break;
74
- match = name;
84
+ match = index;
75
85
  }
76
86
  return match;
77
87
  };
88
+ var createRelationMatcher = (entries, relation) => (viewport, axisOrPair, name) => {
89
+ if (Array.isArray(axisOrPair)) {
90
+ return matchesRelation(viewport.width, entries.width, axisOrPair[0], relation) && matchesRelation(viewport.height, entries.height, axisOrPair[1], relation);
91
+ }
92
+ const axis = axisOrPair;
93
+ return matchesRelation(viewport[axis], entries[axis], name, relation);
94
+ };
95
+ var matchesRelation = (value, entries, name, relation) => {
96
+ const target = entries.findIndex(([entryName]) => entryName === name);
97
+ if (target === -1)
98
+ return false;
99
+ const current = matchScaleIndex(value, entries);
100
+ if (relation === "below")
101
+ return current < target;
102
+ if (relation === "atMost")
103
+ return current <= target;
104
+ if (relation === "only")
105
+ return current === target;
106
+ if (relation === "atLeast")
107
+ return current >= target;
108
+ return current > target;
109
+ };
78
110
  var isRecord = (value) => typeof value === "object" && value !== null;
79
111
  var isThreshold = (value) => typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
80
112
 
@@ -91,7 +123,16 @@ var createResponsiveTui = (scales) => {
91
123
  const ResponsiveTuiContext = createContext(undefined);
92
124
  const ResponsiveTUI = (props) => {
93
125
  const dimensions = useTerminalDimensions();
94
- const breakpoint = (pair) => pair ? breakpoints.matches(dimensions, pair) : breakpoints.match(dimensions);
126
+ const width = bindAxisAccessor("width", () => dimensions, breakpoints);
127
+ const height = bindAxisAccessor("height", () => dimensions, breakpoints);
128
+ const viewport = Object.assign((pair) => pair ? breakpoints.matches(dimensions, pair) : breakpoints.match(dimensions), {
129
+ below: bindViewportRelation(() => dimensions, breakpoints.below),
130
+ atMost: bindViewportRelation(() => dimensions, breakpoints.atMost),
131
+ only: bindViewportRelation(() => dimensions, breakpoints.only),
132
+ atLeast: bindViewportRelation(() => dimensions, breakpoints.atLeast),
133
+ above: bindViewportRelation(() => dimensions, breakpoints.above)
134
+ });
135
+ const breakpoint = [width, height, viewport];
95
136
  return createElement(ResponsiveTuiContext.Provider, { value: breakpoint }, props.children);
96
137
  };
97
138
  const useBreakpoint = () => {
@@ -103,6 +144,15 @@ var createResponsiveTui = (scales) => {
103
144
  };
104
145
  return { ResponsiveTUI, useBreakpoint };
105
146
  };
147
+ var bindAxisAccessor = (axis, viewport, breakpoints) => Object.assign((name) => name === undefined ? breakpoints.match(viewport())[axis] : breakpoints.only(viewport(), axis, name), {
148
+ below: bindAxisRelation(axis, viewport, breakpoints.below),
149
+ atMost: bindAxisRelation(axis, viewport, breakpoints.atMost),
150
+ only: bindAxisRelation(axis, viewport, breakpoints.only),
151
+ atLeast: bindAxisRelation(axis, viewport, breakpoints.atLeast),
152
+ above: bindAxisRelation(axis, viewport, breakpoints.above)
153
+ });
154
+ var bindAxisRelation = (axis, viewport, relation) => (name) => relation(viewport(), axis, name);
155
+ var bindViewportRelation = (viewport, relation) => (pair) => relation(viewport(), pair);
106
156
  export {
107
157
  ResponsiveTuiConfigurationError,
108
158
  ResponsiveTuiProviderError,
@@ -1,11 +1,36 @@
1
1
  import { type ParentProps } from "solid-js";
2
- import { createBreakpointDefinition, type BreakpointMatch, type BreakpointPair, type BreakpointScales } from "../core/index.js";
2
+ import { createBreakpointDefinition, type BreakpointAxis, type BreakpointMatch, type BreakpointPair, type BreakpointScales } from "../core/index.js";
3
3
  export { ResponsiveTuiConfigurationError } from "../core/index.js";
4
- /** A reactive Solid accessor that reads or tests the current breakpoint pair. */
5
- export type ResponsiveBreakpointAccessor<Scales extends BreakpointScales = BreakpointScales> = {
4
+ /** A reactive Solid breakpoint relation test for one axis. */
5
+ export type ResponsiveBreakpointAxisRelationMatcher<Scales extends BreakpointScales = BreakpointScales, Axis extends BreakpointAxis = BreakpointAxis> = {
6
+ (name: BreakpointMatch<Scales>[Axis]): boolean;
7
+ };
8
+ /** A reactive Solid accessor that reads or tests one breakpoint axis. */
9
+ export type ResponsiveBreakpointAxisAccessor<Scales extends BreakpointScales = BreakpointScales, Axis extends BreakpointAxis = BreakpointAxis> = {
10
+ (): BreakpointMatch<Scales>[Axis];
11
+ (name: BreakpointMatch<Scales>[Axis]): boolean;
12
+ readonly below: ResponsiveBreakpointAxisRelationMatcher<Scales, Axis>;
13
+ readonly atMost: ResponsiveBreakpointAxisRelationMatcher<Scales, Axis>;
14
+ readonly only: ResponsiveBreakpointAxisRelationMatcher<Scales, Axis>;
15
+ readonly atLeast: ResponsiveBreakpointAxisRelationMatcher<Scales, Axis>;
16
+ readonly above: ResponsiveBreakpointAxisRelationMatcher<Scales, Axis>;
17
+ };
18
+ /** A reactive Solid accessor that reads or tests the complete breakpoint viewport. */
19
+ export type ResponsiveBreakpointViewportAccessor<Scales extends BreakpointScales = BreakpointScales> = {
6
20
  (): BreakpointMatch<Scales>;
7
21
  (pair: BreakpointPair<Scales>): boolean;
22
+ readonly below: (pair: BreakpointPair<Scales>) => boolean;
23
+ readonly atMost: (pair: BreakpointPair<Scales>) => boolean;
24
+ readonly only: (pair: BreakpointPair<Scales>) => boolean;
25
+ readonly atLeast: (pair: BreakpointPair<Scales>) => boolean;
26
+ readonly above: (pair: BreakpointPair<Scales>) => boolean;
8
27
  };
28
+ /** Reactive Solid breakpoint accessors in width, height, then viewport order. */
29
+ export type ResponsiveBreakpointAccessor<Scales extends BreakpointScales = BreakpointScales> = readonly [
30
+ width: ResponsiveBreakpointAxisAccessor<Scales, "width">,
31
+ height: ResponsiveBreakpointAxisAccessor<Scales, "height">,
32
+ viewport: ResponsiveBreakpointViewportAccessor<Scales>
33
+ ];
9
34
  /** Thrown when a responsive hook is used outside its generated Solid provider. */
10
35
  export declare class ResponsiveTuiProviderError extends Error {
11
36
  readonly _tag = "ResponsiveTuiProviderError";
@@ -1,6 +1,6 @@
1
1
  // src/solid/index.ts
2
2
  import { useTerminalDimensions } from "@opentui/solid";
3
- import { createContext, createMemo, useContext } from "solid-js";
3
+ import { createContext, useContext } from "solid-js";
4
4
 
5
5
  // src/core/breakpoints.ts
6
6
  class ResponsiveTuiConfigurationError extends Error {
@@ -16,12 +16,19 @@ var createBreakpointDefinition = (scales) => {
16
16
  width: matchScale(viewport.width, entries.width),
17
17
  height: matchScale(viewport.height, entries.height)
18
18
  });
19
+ const below = createRelationMatcher(entries, "below");
20
+ const atMost = createRelationMatcher(entries, "atMost");
21
+ const only = createRelationMatcher(entries, "only");
22
+ const atLeast = createRelationMatcher(entries, "atLeast");
23
+ const above = createRelationMatcher(entries, "above");
19
24
  return {
20
25
  match,
21
- matches: (viewport, pair) => {
22
- const current = match(viewport);
23
- return current.width === pair[0] && current.height === pair[1];
24
- }
26
+ matches: (viewport, pair) => only(viewport, pair),
27
+ below,
28
+ atMost,
29
+ only,
30
+ atLeast,
31
+ above
25
32
  };
26
33
  };
27
34
  var breakpointAxes = ["width", "height"];
@@ -67,14 +74,39 @@ var validateScale = (axis, scale) => {
67
74
  return validated.sort((left, right) => left[1] - right[1]);
68
75
  };
69
76
  var matchScale = (value, entries) => {
70
- let match = entries[0][0];
71
- for (const [name, threshold] of entries) {
72
- if (value < threshold)
77
+ return entries[matchScaleIndex(value, entries)][0];
78
+ };
79
+ var matchScaleIndex = (value, entries) => {
80
+ let match = 0;
81
+ for (let index = 1;index < entries.length; index += 1) {
82
+ if (value < entries[index][1])
73
83
  break;
74
- match = name;
84
+ match = index;
75
85
  }
76
86
  return match;
77
87
  };
88
+ var createRelationMatcher = (entries, relation) => (viewport, axisOrPair, name) => {
89
+ if (Array.isArray(axisOrPair)) {
90
+ return matchesRelation(viewport.width, entries.width, axisOrPair[0], relation) && matchesRelation(viewport.height, entries.height, axisOrPair[1], relation);
91
+ }
92
+ const axis = axisOrPair;
93
+ return matchesRelation(viewport[axis], entries[axis], name, relation);
94
+ };
95
+ var matchesRelation = (value, entries, name, relation) => {
96
+ const target = entries.findIndex(([entryName]) => entryName === name);
97
+ if (target === -1)
98
+ return false;
99
+ const current = matchScaleIndex(value, entries);
100
+ if (relation === "below")
101
+ return current < target;
102
+ if (relation === "atMost")
103
+ return current <= target;
104
+ if (relation === "only")
105
+ return current === target;
106
+ if (relation === "atLeast")
107
+ return current >= target;
108
+ return current > target;
109
+ };
78
110
  var isRecord = (value) => typeof value === "object" && value !== null;
79
111
  var isThreshold = (value) => typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
80
112
 
@@ -91,8 +123,16 @@ var createResponsiveTui = (scales) => {
91
123
  const ResponsiveTuiContext = createContext();
92
124
  const ResponsiveTUI = (props) => {
93
125
  const dimensions = useTerminalDimensions();
94
- const current = createMemo(() => breakpoints.match(dimensions()));
95
- const breakpoint = (pair) => pair ? breakpoints.matches(dimensions(), pair) : current();
126
+ const width = bindAxisAccessor("width", dimensions, breakpoints);
127
+ const height = bindAxisAccessor("height", dimensions, breakpoints);
128
+ const viewport = Object.assign((pair) => pair ? breakpoints.matches(dimensions(), pair) : breakpoints.match(dimensions()), {
129
+ below: bindViewportRelation(dimensions, breakpoints.below),
130
+ atMost: bindViewportRelation(dimensions, breakpoints.atMost),
131
+ only: bindViewportRelation(dimensions, breakpoints.only),
132
+ atLeast: bindViewportRelation(dimensions, breakpoints.atLeast),
133
+ above: bindViewportRelation(dimensions, breakpoints.above)
134
+ });
135
+ const breakpoint = [width, height, viewport];
96
136
  return ResponsiveTuiContext.Provider({
97
137
  get children() {
98
138
  return props.children;
@@ -109,6 +149,15 @@ var createResponsiveTui = (scales) => {
109
149
  };
110
150
  return { ResponsiveTUI, useBreakpoint };
111
151
  };
152
+ var bindAxisAccessor = (axis, viewport, breakpoints) => Object.assign((name) => name === undefined ? breakpoints.match(viewport())[axis] : breakpoints.only(viewport(), axis, name), {
153
+ below: bindAxisRelation(axis, viewport, breakpoints.below),
154
+ atMost: bindAxisRelation(axis, viewport, breakpoints.atMost),
155
+ only: bindAxisRelation(axis, viewport, breakpoints.only),
156
+ atLeast: bindAxisRelation(axis, viewport, breakpoints.atLeast),
157
+ above: bindAxisRelation(axis, viewport, breakpoints.above)
158
+ });
159
+ var bindAxisRelation = (axis, viewport, relation) => (name) => relation(viewport(), axis, name);
160
+ var bindViewportRelation = (viewport, relation) => (pair) => relation(viewport(), pair);
112
161
  export {
113
162
  ResponsiveTuiConfigurationError,
114
163
  ResponsiveTuiProviderError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opentui-responsive",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "Typed responsive breakpoints for OpenTUI.",
5
5
  "keywords": [
6
6
  "breakpoints",