opentui-responsive 0.4.0 → 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,17 +58,15 @@ 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"]);
64
- // Compare one axis, or compare both axes with AND semantics
65
- const isMediumOrWider = breakpoint.atLeast("width", "medium");
66
- const hasMediumViewport = breakpoint.atLeast(["medium", "medium"]);
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"]);
67
66
 
68
67
  return (
69
- // Or you can get the current width and height breakpoint
70
- <box flexDirection={breakpoint().width === "wide" ? "row" : "column"}>
71
- <text>{`${breakpoint().width}/${breakpoint().height}`}</text>
68
+ <box flexDirection={width() === "wide" ? "row" : "column"}>
69
+ <text>{`${width()}/${height()}`}</text>
72
70
  <text>
73
71
  {isWideAndTall || (isMediumOrWider && hasMediumViewport) ? "Full layout" : "Compact layout"}
74
72
  </text>
@@ -92,14 +90,19 @@ export function App() {
92
90
  }
93
91
  ```
94
92
 
95
- `useBreakpoint()` updates when the terminal crosses a configured threshold. Its accessor returns the current width and height names, accepts an exact `[width, height]` pair, and provides named relation methods. Relation methods accept either an axis and name or a pair; pair comparisons return true only when both axes satisfy the relation.
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.
96
94
 
97
95
  ```tsx
98
- breakpoint.below("width", "medium");
99
- breakpoint.atMost(["medium", "tall"]);
100
- breakpoint.only("height", "short");
101
- breakpoint.atLeast(["medium", "medium"]);
102
- breakpoint.above("width", "medium");
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();
103
106
  ```
104
107
 
105
108
  Run `bun run demo` from the package directory to try the Solid example. Resize the terminal to see its layout respond.
@@ -110,15 +113,16 @@ The React adapter has the same factory, provider, hook, and inferred breakpoint
110
113
 
111
114
  ### `opentui-responsive/solid` and `opentui-responsive/react`
112
115
 
113
- | API | Description |
114
- | ------------------------------------- | ------------------------------------------------------------------------------------- |
115
- | `createResponsiveTui(scales)` | Validates the scales and creates a `ResponsiveTUI` provider and `useBreakpoint` hook. |
116
- | `ResponsiveTUI` | Tracks terminal dimensions and provides the current breakpoint accessor. |
117
- | `useBreakpoint()` | Reads the accessor from the nearest generated provider. |
118
- | `ResponsiveBreakpointAccessor` | Reads the current match or tests exact and relative breakpoint positions. |
119
- | `ResponsiveBreakpointRelationMatcher` | Tests one axis or an AND-combined width and height pair. |
120
- | `ResponsiveTuiConfigurationError` | Thrown when the supplied breakpoint scales are invalid. |
121
- | `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. |
122
126
 
123
127
  ### `opentui-responsive/core`
124
128
 
@@ -140,7 +144,7 @@ Width and height use independent sets of inclusive minimum thresholds measured i
140
144
 
141
145
  Declaration order does not affect matching. Each axis selects its highest satisfied threshold.
142
146
 
143
- Relation methods compare complete tiers rather than their raw threshold values. Given `width: { narrow: 0, medium: 60, wide: 100 }`, `atMost("width", "medium")` matches widths from `0` through `99`.
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`.
144
148
 
145
149
  | Method | Tier relation |
146
150
  | --------- | ------------- |
@@ -150,7 +154,7 @@ Relation methods compare complete tiers rather than their raw threshold values.
150
154
  | `atLeast` | `>=` |
151
155
  | `above` | `>` |
152
156
 
153
- Each method accepts `(axis, name)` for a single-axis comparison or `[width, height]` for a two-axis comparison. Two-axis comparisons use AND semantics. Calling `breakpoint.only(["wide", "tall"])` is equivalent to calling `breakpoint(["wide", "tall"])`.
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.
154
158
 
155
159
  Use breakpoints for discrete layout modes. Keep continuous measurements such as progress-bar width and available list height on OpenTUI's `useTerminalDimensions()`.
156
160
 
@@ -1,21 +1,36 @@
1
1
  import { type PropsWithChildren } from "react";
2
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 breakpoint relation test for one axis or both axes. */
5
- export type ResponsiveBreakpointRelationMatcher<Scales extends BreakpointScales = BreakpointScales> = {
6
- <Axis extends BreakpointAxis>(axis: Axis, name: BreakpointMatch<Scales>[Axis]): boolean;
7
- (pair: BreakpointPair<Scales>): boolean;
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>;
8
17
  };
9
- /** A React accessor that reads or tests the current breakpoint pair. */
10
- export type ResponsiveBreakpointAccessor<Scales extends BreakpointScales = BreakpointScales> = {
18
+ /** A React accessor that reads or tests the complete breakpoint viewport. */
19
+ export type ResponsiveBreakpointViewportAccessor<Scales extends BreakpointScales = BreakpointScales> = {
11
20
  (): BreakpointMatch<Scales>;
12
21
  (pair: BreakpointPair<Scales>): boolean;
13
- readonly below: ResponsiveBreakpointRelationMatcher<Scales>;
14
- readonly atMost: ResponsiveBreakpointRelationMatcher<Scales>;
15
- readonly only: ResponsiveBreakpointRelationMatcher<Scales>;
16
- readonly atLeast: ResponsiveBreakpointRelationMatcher<Scales>;
17
- readonly above: ResponsiveBreakpointRelationMatcher<Scales>;
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;
18
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
+ ];
19
34
  /** Thrown when a responsive hook is used outside its generated React provider. */
20
35
  export declare class ResponsiveTuiProviderError extends Error {
21
36
  readonly _tag = "ResponsiveTuiProviderError";
@@ -123,14 +123,16 @@ var createResponsiveTui = (scales) => {
123
123
  const ResponsiveTuiContext = createContext(undefined);
124
124
  const ResponsiveTUI = (props) => {
125
125
  const dimensions = useTerminalDimensions();
126
- const bind = (relation) => bindRelation(() => dimensions, relation);
127
- const breakpoint = Object.assign((pair) => pair ? breakpoints.matches(dimensions, pair) : breakpoints.match(dimensions), {
128
- below: bind(breakpoints.below),
129
- atMost: bind(breakpoints.atMost),
130
- only: bind(breakpoints.only),
131
- atLeast: bind(breakpoints.atLeast),
132
- above: bind(breakpoints.above)
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)
133
134
  });
135
+ const breakpoint = [width, height, viewport];
134
136
  return createElement(ResponsiveTuiContext.Provider, { value: breakpoint }, props.children);
135
137
  };
136
138
  const useBreakpoint = () => {
@@ -142,12 +144,15 @@ var createResponsiveTui = (scales) => {
142
144
  };
143
145
  return { ResponsiveTUI, useBreakpoint };
144
146
  };
145
- var bindRelation = (viewport, relation) => (axisOrPair, name) => {
146
- if (typeof axisOrPair !== "string") {
147
- return relation(viewport(), axisOrPair);
148
- }
149
- return relation(viewport(), axisOrPair, name);
150
- };
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);
151
156
  export {
152
157
  ResponsiveTuiConfigurationError,
153
158
  ResponsiveTuiProviderError,
@@ -1,21 +1,36 @@
1
1
  import { type ParentProps } from "solid-js";
2
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 breakpoint relation test for one axis or both axes. */
5
- export type ResponsiveBreakpointRelationMatcher<Scales extends BreakpointScales = BreakpointScales> = {
6
- <Axis extends BreakpointAxis>(axis: Axis, name: BreakpointMatch<Scales>[Axis]): boolean;
7
- (pair: BreakpointPair<Scales>): boolean;
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>;
8
17
  };
9
- /** A reactive Solid accessor that reads or tests the current breakpoint pair. */
10
- export type ResponsiveBreakpointAccessor<Scales extends BreakpointScales = BreakpointScales> = {
18
+ /** A reactive Solid accessor that reads or tests the complete breakpoint viewport. */
19
+ export type ResponsiveBreakpointViewportAccessor<Scales extends BreakpointScales = BreakpointScales> = {
11
20
  (): BreakpointMatch<Scales>;
12
21
  (pair: BreakpointPair<Scales>): boolean;
13
- readonly below: ResponsiveBreakpointRelationMatcher<Scales>;
14
- readonly atMost: ResponsiveBreakpointRelationMatcher<Scales>;
15
- readonly only: ResponsiveBreakpointRelationMatcher<Scales>;
16
- readonly atLeast: ResponsiveBreakpointRelationMatcher<Scales>;
17
- readonly above: ResponsiveBreakpointRelationMatcher<Scales>;
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;
18
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
+ ];
19
34
  /** Thrown when a responsive hook is used outside its generated Solid provider. */
20
35
  export declare class ResponsiveTuiProviderError extends Error {
21
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 {
@@ -123,15 +123,16 @@ var createResponsiveTui = (scales) => {
123
123
  const ResponsiveTuiContext = createContext();
124
124
  const ResponsiveTUI = (props) => {
125
125
  const dimensions = useTerminalDimensions();
126
- const current = createMemo(() => breakpoints.match(dimensions()));
127
- const bind = (relation) => bindRelation(dimensions, relation);
128
- const breakpoint = Object.assign((pair) => pair ? breakpoints.matches(dimensions(), pair) : current(), {
129
- below: bind(breakpoints.below),
130
- atMost: bind(breakpoints.atMost),
131
- only: bind(breakpoints.only),
132
- atLeast: bind(breakpoints.atLeast),
133
- above: bind(breakpoints.above)
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
134
  });
135
+ const breakpoint = [width, height, viewport];
135
136
  return ResponsiveTuiContext.Provider({
136
137
  get children() {
137
138
  return props.children;
@@ -148,12 +149,15 @@ var createResponsiveTui = (scales) => {
148
149
  };
149
150
  return { ResponsiveTUI, useBreakpoint };
150
151
  };
151
- var bindRelation = (viewport, relation) => (axisOrPair, name) => {
152
- if (typeof axisOrPair !== "string") {
153
- return relation(viewport(), axisOrPair);
154
- }
155
- return relation(viewport(), axisOrPair, name);
156
- };
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);
157
161
  export {
158
162
  ResponsiveTuiConfigurationError,
159
163
  ResponsiveTuiProviderError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opentui-responsive",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Typed responsive breakpoints for OpenTUI.",
5
5
  "keywords": [
6
6
  "breakpoints",