opentui-responsive 0.2.4 → 0.2.6
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 +53 -46
- package/dist/core/breakpoints.d.ts +35 -0
- package/dist/core/index.d.ts +1 -42
- package/dist/core/index.js +3 -3
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +80 -1
- package/dist/solid/index.d.ts +3 -2
- package/dist/solid/index.js +80 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<a href="https://github.com/itsmeyaw/opentui-responsive/actions/workflows/ci.yml"><img src="https://github.com/itsmeyaw/opentui-responsive/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
|
|
6
6
|
<a href="https://github.com/itsmeyaw/opentui-responsive/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/opentui-responsive" alt="MIT license"></a>
|
|
7
7
|
</p>
|
|
8
|
-
<p>Typed responsive breakpoints for OpenTUI
|
|
8
|
+
<p>Typed responsive breakpoints for OpenTUI React and Solid applications.</p>
|
|
9
9
|
<p align="center">
|
|
10
10
|
<img src="https://raw.githubusercontent.com/itsmeyaw/opentui-responsive/main/docs/assets/demo.gif" alt="opentui-responsive terminal demo">
|
|
11
11
|
</p>
|
|
@@ -29,39 +29,59 @@ bun add opentui-responsive @opentui/react react
|
|
|
29
29
|
npm install opentui-responsive @opentui/react react
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Framework adapter authors can use the framework-neutral `opentui-responsive/core` entrypoint.
|
|
33
33
|
|
|
34
34
|
## Usage
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Create the responsive provider in your layout, then use its hook directly in child components:
|
|
37
|
+
|
|
38
|
+
`src/layout.tsx`
|
|
37
39
|
|
|
38
40
|
```tsx
|
|
39
|
-
import { defineBreakpoints } from "opentui-responsive/core";
|
|
40
41
|
import { createResponsiveTui } from "opentui-responsive/solid";
|
|
42
|
+
import type { ParentProps } from "solid-js";
|
|
41
43
|
|
|
42
|
-
const
|
|
44
|
+
export const { ResponsiveTUI, useResponsiveTui } = createResponsiveTui({
|
|
43
45
|
width: { narrow: 0, medium: 60, wide: 100 },
|
|
44
46
|
height: { short: 0, medium: 16, tall: 28 },
|
|
45
47
|
});
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
export function Layout(props: ParentProps) {
|
|
50
|
+
return <ResponsiveTUI>{props.children}</ResponsiveTUI>;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`src/content.tsx`
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { useResponsiveTui } from "./layout.tsx";
|
|
48
58
|
|
|
49
|
-
function Content() {
|
|
59
|
+
export function Content() {
|
|
50
60
|
const breakpoint = useResponsiveTui();
|
|
61
|
+
// You can use it to directly check [width, height] breakpoint
|
|
62
|
+
const isWideAndTall = breakpoint(["wide", "tall"]);
|
|
51
63
|
|
|
52
64
|
return (
|
|
65
|
+
// Or you can get the current width and height breakpoint
|
|
53
66
|
<box flexDirection={breakpoint().width === "wide" ? "row" : "column"}>
|
|
54
67
|
<text>{`${breakpoint().width}/${breakpoint().height}`}</text>
|
|
55
|
-
<text>{
|
|
68
|
+
<text>{isWideAndTall ? "Full layout" : "Compact layout"}</text>
|
|
56
69
|
</box>
|
|
57
70
|
);
|
|
58
71
|
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`src/app.tsx`
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { Content } from "./content.tsx";
|
|
78
|
+
import { Layout } from "./layout.tsx";
|
|
59
79
|
|
|
60
|
-
function App() {
|
|
80
|
+
export function App() {
|
|
61
81
|
return (
|
|
62
|
-
<
|
|
82
|
+
<Layout>
|
|
63
83
|
<Content />
|
|
64
|
-
</
|
|
84
|
+
</Layout>
|
|
65
85
|
);
|
|
66
86
|
}
|
|
67
87
|
```
|
|
@@ -70,52 +90,39 @@ function App() {
|
|
|
70
90
|
|
|
71
91
|
Run `bun run demo` from the package directory to try the Solid example. Resize the terminal to see its layout respond.
|
|
72
92
|
|
|
73
|
-
The React adapter has the same factory, provider, hook, and inferred breakpoint types; import `createResponsiveTui` from `opentui-responsive/react` instead.
|
|
93
|
+
The React adapter has the same factory, provider, hook, and inferred breakpoint types; import `createResponsiveTui` from `opentui-responsive/react` instead. Use `PropsWithChildren` for the layout's props.
|
|
74
94
|
|
|
75
95
|
## API Reference
|
|
76
96
|
|
|
77
|
-
### `opentui-responsive/
|
|
97
|
+
### `opentui-responsive/solid` and `opentui-responsive/react`
|
|
78
98
|
|
|
79
|
-
| API
|
|
80
|
-
|
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `BreakpointScales` | The width and height scale configuration. |
|
|
88
|
-
| `BreakpointViewport` | Explicit width and height dimensions to match. |
|
|
89
|
-
| `BreakpointMatch` | The matched breakpoint name for each axis. |
|
|
90
|
-
| `BreakpointPair` | An exact breakpoint pair in width-then-height order. |
|
|
91
|
-
| `BreakpointDefinition` | The typed result of `defineBreakpoints`. |
|
|
92
|
-
| `ResponsiveTuiConfigurationError` | Thrown when breakpoint scales are invalid. |
|
|
99
|
+
| API | Description |
|
|
100
|
+
| --------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
101
|
+
| `createResponsiveTui(scales)` | Validates the scales and creates a `ResponsiveTUI` provider and `useResponsiveTui` hook. |
|
|
102
|
+
| `ResponsiveTUI` | Tracks terminal dimensions and provides the current breakpoint accessor. |
|
|
103
|
+
| `useResponsiveTui()` | Reads the accessor from the nearest generated provider. |
|
|
104
|
+
| `ResponsiveBreakpointAccessor` | Reads the current match or tests an exact breakpoint pair. |
|
|
105
|
+
| `ResponsiveTuiConfigurationError` | Thrown when the supplied breakpoint scales are invalid. |
|
|
106
|
+
| `ResponsiveTuiProviderError` | Thrown when the generated hook is called outside its provider. |
|
|
93
107
|
|
|
94
|
-
### `opentui-responsive/
|
|
108
|
+
### `opentui-responsive/core`
|
|
109
|
+
|
|
110
|
+
For implementing another framework adapter, not application setup:
|
|
95
111
|
|
|
96
|
-
| API
|
|
97
|
-
|
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
112
|
+
| API | Description |
|
|
113
|
+
| ------------------------------------ | -------------------------------------------------------------------------- |
|
|
114
|
+
| `createBreakpointDefinition(scales)` | Validates scales and creates `match` and `matches` helpers for an adapter. |
|
|
115
|
+
| `BreakpointDefinition` | The typed breakpoint matcher returned by `createBreakpointDefinition`. |
|
|
116
|
+
| `BreakpointScales` | The width and height scale configuration. |
|
|
117
|
+
| `BreakpointMatch` | The matched breakpoint name for each axis. |
|
|
118
|
+
| `BreakpointPair` | An exact breakpoint pair in width-then-height order. |
|
|
119
|
+
| `ResponsiveTuiConfigurationError` | Thrown when supplied breakpoint scales are invalid. |
|
|
103
120
|
|
|
104
121
|
## Breakpoint Behavior
|
|
105
122
|
|
|
106
123
|
Width and height use independent sets of inclusive minimum thresholds measured in terminal cells. Each axis must contain a zero threshold so every terminal size has a match. Names must be non-empty, and thresholds must be unique finite non-negative integers within their axis.
|
|
107
124
|
|
|
108
|
-
Declaration order does not affect matching. Each axis selects its highest satisfied threshold
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
const breakpoints = defineBreakpoints({
|
|
112
|
-
width: { wide: 100, narrow: 0, medium: 60 },
|
|
113
|
-
height: { tall: 28, short: 0, medium: 16 },
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
breakpoints.match({ width: 120, height: 10 });
|
|
117
|
-
// { width: "wide", height: "short" }
|
|
118
|
-
```
|
|
125
|
+
Declaration order does not affect matching. Each axis selects its highest satisfied threshold.
|
|
119
126
|
|
|
120
127
|
Use breakpoints for discrete layout modes. Keep continuous measurements such as progress-bar width and available list height on OpenTUI's `useTerminalDimensions()`.
|
|
121
128
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** A terminal dimension used to select a breakpoint. */
|
|
2
|
+
export type BreakpointAxis = "width" | "height";
|
|
3
|
+
/** Named inclusive minimum thresholds for one terminal dimension. */
|
|
4
|
+
export type BreakpointScale = Readonly<Record<string, number>>;
|
|
5
|
+
/** Independent breakpoint scales for terminal width and height. */
|
|
6
|
+
export type BreakpointScales = {
|
|
7
|
+
readonly width: BreakpointScale;
|
|
8
|
+
readonly height: BreakpointScale;
|
|
9
|
+
};
|
|
10
|
+
/** Terminal dimensions in cells. */
|
|
11
|
+
export type BreakpointViewport = {
|
|
12
|
+
readonly width: number;
|
|
13
|
+
readonly height: number;
|
|
14
|
+
};
|
|
15
|
+
/** The breakpoint name matched on each axis. */
|
|
16
|
+
export type BreakpointMatch<Scales extends BreakpointScales = BreakpointScales> = {
|
|
17
|
+
readonly [Axis in BreakpointAxis]: Extract<keyof Scales[Axis], string>;
|
|
18
|
+
};
|
|
19
|
+
/** An exact breakpoint pair in width-then-height order. */
|
|
20
|
+
export type BreakpointPair<Scales extends BreakpointScales = BreakpointScales> = readonly [
|
|
21
|
+
width: BreakpointMatch<Scales>["width"],
|
|
22
|
+
height: BreakpointMatch<Scales>["height"]
|
|
23
|
+
];
|
|
24
|
+
/** A validated breakpoint matcher for framework adapter implementations. */
|
|
25
|
+
export type BreakpointDefinition<Scales extends BreakpointScales = BreakpointScales> = {
|
|
26
|
+
readonly match: (viewport: BreakpointViewport) => BreakpointMatch<Scales>;
|
|
27
|
+
readonly matches: (viewport: BreakpointViewport, pair: BreakpointPair<Scales>) => boolean;
|
|
28
|
+
};
|
|
29
|
+
/** Thrown when breakpoint scales cannot produce a valid definition. */
|
|
30
|
+
export declare class ResponsiveTuiConfigurationError extends Error {
|
|
31
|
+
readonly _tag = "ResponsiveTuiConfigurationError";
|
|
32
|
+
constructor(message: string);
|
|
33
|
+
}
|
|
34
|
+
/** Creates a validated breakpoint matcher for the framework adapters. */
|
|
35
|
+
export declare const createBreakpointDefinition: <const Scales extends BreakpointScales>(scales: Scales & Record<Exclude<keyof Scales, BreakpointAxis>, never>) => BreakpointDefinition<Scales>;
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,42 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export type BreakpointAxis = "width" | "height";
|
|
3
|
-
/** Named inclusive minimum thresholds for one terminal dimension. */
|
|
4
|
-
export type BreakpointScale = Readonly<Record<string, number>>;
|
|
5
|
-
/** Independent breakpoint scales for terminal width and height. */
|
|
6
|
-
export type BreakpointScales = {
|
|
7
|
-
readonly width: BreakpointScale;
|
|
8
|
-
readonly height: BreakpointScale;
|
|
9
|
-
};
|
|
10
|
-
/** Terminal dimensions in cells. */
|
|
11
|
-
export type BreakpointViewport = {
|
|
12
|
-
readonly width: number;
|
|
13
|
-
readonly height: number;
|
|
14
|
-
};
|
|
15
|
-
/** The breakpoint name matched on each axis. */
|
|
16
|
-
export type BreakpointMatch<Scales extends BreakpointScales = BreakpointScales> = {
|
|
17
|
-
readonly [Axis in BreakpointAxis]: Extract<keyof Scales[Axis], string>;
|
|
18
|
-
};
|
|
19
|
-
/** An exact breakpoint pair in width-then-height order. */
|
|
20
|
-
export type BreakpointPair<Scales extends BreakpointScales = BreakpointScales> = readonly [
|
|
21
|
-
width: BreakpointMatch<Scales>["width"],
|
|
22
|
-
height: BreakpointMatch<Scales>["height"]
|
|
23
|
-
];
|
|
24
|
-
/** A validated breakpoint definition that matches explicit terminal dimensions. */
|
|
25
|
-
export type BreakpointDefinition<Scales extends BreakpointScales = BreakpointScales> = {
|
|
26
|
-
/** Returns the highest inclusive breakpoint reached on each axis. */
|
|
27
|
-
readonly match: (viewport: BreakpointViewport) => BreakpointMatch<Scales>;
|
|
28
|
-
/** Tests whether a viewport matches an exact width and height pair. */
|
|
29
|
-
readonly matches: (viewport: BreakpointViewport, pair: BreakpointPair<Scales>) => boolean;
|
|
30
|
-
};
|
|
31
|
-
/** Extracts the configured breakpoint-name union for one axis. */
|
|
32
|
-
export type BreakpointOf<Input, Axis extends BreakpointAxis> = Input extends BreakpointDefinition<infer Scales> ? Extract<keyof Scales[Axis], string> : Input extends BreakpointScales ? Extract<keyof Input[Axis], string> : never;
|
|
33
|
-
/** Thrown when breakpoint scales cannot produce a valid definition. */
|
|
34
|
-
export declare class ResponsiveTuiConfigurationError extends Error {
|
|
35
|
-
readonly _tag = "ResponsiveTuiConfigurationError";
|
|
36
|
-
constructor(message: string);
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Creates a typed breakpoint definition from inclusive width and height thresholds.
|
|
40
|
-
* Each axis must contain a zero threshold.
|
|
41
|
-
*/
|
|
42
|
-
export declare const defineBreakpoints: <const Scales extends BreakpointScales>(scales: Scales & Record<Exclude<keyof Scales, BreakpointAxis>, never>) => BreakpointDefinition<Scales>;
|
|
1
|
+
export * from "./breakpoints.js";
|
package/dist/core/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/core/
|
|
1
|
+
// src/core/breakpoints.ts
|
|
2
2
|
class ResponsiveTuiConfigurationError extends Error {
|
|
3
3
|
_tag = "ResponsiveTuiConfigurationError";
|
|
4
4
|
constructor(message) {
|
|
@@ -6,7 +6,7 @@ class ResponsiveTuiConfigurationError extends Error {
|
|
|
6
6
|
this.name = this._tag;
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
-
var
|
|
9
|
+
var createBreakpointDefinition = (scales) => {
|
|
10
10
|
const entries = validateScales(scales);
|
|
11
11
|
const match = (viewport) => ({
|
|
12
12
|
width: matchScale(viewport.width, entries.width),
|
|
@@ -75,5 +75,5 @@ var isRecord = (value) => typeof value === "object" && value !== null;
|
|
|
75
75
|
var isThreshold = (value) => typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
|
|
76
76
|
export {
|
|
77
77
|
ResponsiveTuiConfigurationError,
|
|
78
|
-
|
|
78
|
+
createBreakpointDefinition
|
|
79
79
|
};
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type PropsWithChildren } from "react";
|
|
2
|
-
import
|
|
2
|
+
import { type BreakpointMatch, type BreakpointPair, type BreakpointScales } from "../core/index.js";
|
|
3
|
+
export { ResponsiveTuiConfigurationError } from "../core/index.js";
|
|
3
4
|
/** A React accessor that reads or tests the current breakpoint pair. */
|
|
4
5
|
export type ResponsiveBreakpointAccessor<Scales extends BreakpointScales = BreakpointScales> = {
|
|
5
6
|
(): BreakpointMatch<Scales>;
|
|
@@ -11,7 +12,7 @@ export declare class ResponsiveTuiProviderError extends Error {
|
|
|
11
12
|
constructor();
|
|
12
13
|
}
|
|
13
14
|
/** Creates a React provider and hook bound to one breakpoint definition. */
|
|
14
|
-
export declare const createResponsiveTui: <const Scales extends BreakpointScales>(
|
|
15
|
+
export declare const createResponsiveTui: <const Scales extends BreakpointScales>(scales: Scales & Record<Exclude<keyof Scales, "width" | "height">, never>) => {
|
|
15
16
|
ResponsiveTUI: (props: PropsWithChildren) => import("react").FunctionComponentElement<import("react").ProviderProps<ResponsiveBreakpointAccessor<Scales> | undefined>>;
|
|
16
17
|
useResponsiveTui: () => ResponsiveBreakpointAccessor<Scales>;
|
|
17
18
|
};
|
package/dist/react/index.js
CHANGED
|
@@ -2,6 +2,83 @@
|
|
|
2
2
|
import { useTerminalDimensions } from "@opentui/react";
|
|
3
3
|
import { createContext, createElement, useContext } from "react";
|
|
4
4
|
|
|
5
|
+
// src/core/breakpoints.ts
|
|
6
|
+
class ResponsiveTuiConfigurationError extends Error {
|
|
7
|
+
_tag = "ResponsiveTuiConfigurationError";
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = this._tag;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
var createBreakpointDefinition = (scales) => {
|
|
14
|
+
const entries = validateScales(scales);
|
|
15
|
+
const match = (viewport) => ({
|
|
16
|
+
width: matchScale(viewport.width, entries.width),
|
|
17
|
+
height: matchScale(viewport.height, entries.height)
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
match,
|
|
21
|
+
matches: (viewport, pair) => {
|
|
22
|
+
const current = match(viewport);
|
|
23
|
+
return current.width === pair[0] && current.height === pair[1];
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
var breakpointAxes = ["width", "height"];
|
|
28
|
+
var validateScales = (scales) => {
|
|
29
|
+
if (!isRecord(scales) || Array.isArray(scales)) {
|
|
30
|
+
throw new ResponsiveTuiConfigurationError("Breakpoint definitions must be an object.");
|
|
31
|
+
}
|
|
32
|
+
const unknown = Object.keys(scales).find((key) => !breakpointAxes.includes(key));
|
|
33
|
+
if (unknown) {
|
|
34
|
+
throw new ResponsiveTuiConfigurationError(`Unknown breakpoint axis: ${unknown}.`);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
width: validateScale("width", scales.width),
|
|
38
|
+
height: validateScale("height", scales.height)
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
var validateScale = (axis, scale) => {
|
|
42
|
+
if (!isRecord(scale) || Array.isArray(scale)) {
|
|
43
|
+
throw new ResponsiveTuiConfigurationError(`Breakpoint ${axis} scale must be an object.`);
|
|
44
|
+
}
|
|
45
|
+
const entries = Object.entries(scale);
|
|
46
|
+
if (entries.length === 0) {
|
|
47
|
+
throw new ResponsiveTuiConfigurationError(`Breakpoint ${axis} scale cannot be empty.`);
|
|
48
|
+
}
|
|
49
|
+
const thresholds = new Set;
|
|
50
|
+
const validated = [];
|
|
51
|
+
for (const [name, threshold] of entries) {
|
|
52
|
+
if (name.length === 0) {
|
|
53
|
+
throw new ResponsiveTuiConfigurationError("Breakpoint names must be non-empty strings.");
|
|
54
|
+
}
|
|
55
|
+
if (!isThreshold(threshold)) {
|
|
56
|
+
throw new ResponsiveTuiConfigurationError("Breakpoint thresholds must be finite non-negative integers.");
|
|
57
|
+
}
|
|
58
|
+
if (thresholds.has(threshold)) {
|
|
59
|
+
throw new ResponsiveTuiConfigurationError(`Breakpoint ${axis} thresholds must be unique.`);
|
|
60
|
+
}
|
|
61
|
+
thresholds.add(threshold);
|
|
62
|
+
validated.push([name, threshold]);
|
|
63
|
+
}
|
|
64
|
+
if (!thresholds.has(0)) {
|
|
65
|
+
throw new ResponsiveTuiConfigurationError(`Breakpoint ${axis} scale must include zero.`);
|
|
66
|
+
}
|
|
67
|
+
return validated.sort((left, right) => left[1] - right[1]);
|
|
68
|
+
};
|
|
69
|
+
var matchScale = (value, entries) => {
|
|
70
|
+
let match = entries[0][0];
|
|
71
|
+
for (const [name, threshold] of entries) {
|
|
72
|
+
if (value < threshold)
|
|
73
|
+
break;
|
|
74
|
+
match = name;
|
|
75
|
+
}
|
|
76
|
+
return match;
|
|
77
|
+
};
|
|
78
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
79
|
+
var isThreshold = (value) => typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
|
|
80
|
+
|
|
81
|
+
// src/react/index.ts
|
|
5
82
|
class ResponsiveTuiProviderError extends Error {
|
|
6
83
|
_tag = "ResponsiveTuiProviderError";
|
|
7
84
|
constructor() {
|
|
@@ -9,7 +86,8 @@ class ResponsiveTuiProviderError extends Error {
|
|
|
9
86
|
this.name = this._tag;
|
|
10
87
|
}
|
|
11
88
|
}
|
|
12
|
-
var createResponsiveTui = (
|
|
89
|
+
var createResponsiveTui = (scales) => {
|
|
90
|
+
const breakpoints = createBreakpointDefinition(scales);
|
|
13
91
|
const ResponsiveTuiContext = createContext(undefined);
|
|
14
92
|
const ResponsiveTUI = (props) => {
|
|
15
93
|
const dimensions = useTerminalDimensions();
|
|
@@ -26,6 +104,7 @@ var createResponsiveTui = (breakpoints) => {
|
|
|
26
104
|
return { ResponsiveTUI, useResponsiveTui };
|
|
27
105
|
};
|
|
28
106
|
export {
|
|
107
|
+
ResponsiveTuiConfigurationError,
|
|
29
108
|
ResponsiveTuiProviderError,
|
|
30
109
|
createResponsiveTui
|
|
31
110
|
};
|
package/dist/solid/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ParentProps } from "solid-js";
|
|
2
|
-
import
|
|
2
|
+
import { type BreakpointMatch, type BreakpointPair, type BreakpointScales } from "../core/index.js";
|
|
3
|
+
export { ResponsiveTuiConfigurationError } from "../core/index.js";
|
|
3
4
|
/** A reactive Solid accessor that reads or tests the current breakpoint pair. */
|
|
4
5
|
export type ResponsiveBreakpointAccessor<Scales extends BreakpointScales = BreakpointScales> = {
|
|
5
6
|
(): BreakpointMatch<Scales>;
|
|
@@ -11,7 +12,7 @@ export declare class ResponsiveTuiProviderError extends Error {
|
|
|
11
12
|
constructor();
|
|
12
13
|
}
|
|
13
14
|
/** Creates a Solid provider and hook bound to one breakpoint definition. */
|
|
14
|
-
export declare const createResponsiveTui: <const Scales extends BreakpointScales>(
|
|
15
|
+
export declare const createResponsiveTui: <const Scales extends BreakpointScales>(scales: Scales & Record<Exclude<keyof Scales, "width" | "height">, never>) => {
|
|
15
16
|
ResponsiveTUI: (props: ParentProps) => any;
|
|
16
17
|
useResponsiveTui: () => ResponsiveBreakpointAccessor<Scales>;
|
|
17
18
|
};
|
package/dist/solid/index.js
CHANGED
|
@@ -2,6 +2,83 @@
|
|
|
2
2
|
import { useTerminalDimensions } from "@opentui/solid";
|
|
3
3
|
import { createContext, createMemo, useContext } from "solid-js";
|
|
4
4
|
|
|
5
|
+
// src/core/breakpoints.ts
|
|
6
|
+
class ResponsiveTuiConfigurationError extends Error {
|
|
7
|
+
_tag = "ResponsiveTuiConfigurationError";
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = this._tag;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
var createBreakpointDefinition = (scales) => {
|
|
14
|
+
const entries = validateScales(scales);
|
|
15
|
+
const match = (viewport) => ({
|
|
16
|
+
width: matchScale(viewport.width, entries.width),
|
|
17
|
+
height: matchScale(viewport.height, entries.height)
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
match,
|
|
21
|
+
matches: (viewport, pair) => {
|
|
22
|
+
const current = match(viewport);
|
|
23
|
+
return current.width === pair[0] && current.height === pair[1];
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
var breakpointAxes = ["width", "height"];
|
|
28
|
+
var validateScales = (scales) => {
|
|
29
|
+
if (!isRecord(scales) || Array.isArray(scales)) {
|
|
30
|
+
throw new ResponsiveTuiConfigurationError("Breakpoint definitions must be an object.");
|
|
31
|
+
}
|
|
32
|
+
const unknown = Object.keys(scales).find((key) => !breakpointAxes.includes(key));
|
|
33
|
+
if (unknown) {
|
|
34
|
+
throw new ResponsiveTuiConfigurationError(`Unknown breakpoint axis: ${unknown}.`);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
width: validateScale("width", scales.width),
|
|
38
|
+
height: validateScale("height", scales.height)
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
var validateScale = (axis, scale) => {
|
|
42
|
+
if (!isRecord(scale) || Array.isArray(scale)) {
|
|
43
|
+
throw new ResponsiveTuiConfigurationError(`Breakpoint ${axis} scale must be an object.`);
|
|
44
|
+
}
|
|
45
|
+
const entries = Object.entries(scale);
|
|
46
|
+
if (entries.length === 0) {
|
|
47
|
+
throw new ResponsiveTuiConfigurationError(`Breakpoint ${axis} scale cannot be empty.`);
|
|
48
|
+
}
|
|
49
|
+
const thresholds = new Set;
|
|
50
|
+
const validated = [];
|
|
51
|
+
for (const [name, threshold] of entries) {
|
|
52
|
+
if (name.length === 0) {
|
|
53
|
+
throw new ResponsiveTuiConfigurationError("Breakpoint names must be non-empty strings.");
|
|
54
|
+
}
|
|
55
|
+
if (!isThreshold(threshold)) {
|
|
56
|
+
throw new ResponsiveTuiConfigurationError("Breakpoint thresholds must be finite non-negative integers.");
|
|
57
|
+
}
|
|
58
|
+
if (thresholds.has(threshold)) {
|
|
59
|
+
throw new ResponsiveTuiConfigurationError(`Breakpoint ${axis} thresholds must be unique.`);
|
|
60
|
+
}
|
|
61
|
+
thresholds.add(threshold);
|
|
62
|
+
validated.push([name, threshold]);
|
|
63
|
+
}
|
|
64
|
+
if (!thresholds.has(0)) {
|
|
65
|
+
throw new ResponsiveTuiConfigurationError(`Breakpoint ${axis} scale must include zero.`);
|
|
66
|
+
}
|
|
67
|
+
return validated.sort((left, right) => left[1] - right[1]);
|
|
68
|
+
};
|
|
69
|
+
var matchScale = (value, entries) => {
|
|
70
|
+
let match = entries[0][0];
|
|
71
|
+
for (const [name, threshold] of entries) {
|
|
72
|
+
if (value < threshold)
|
|
73
|
+
break;
|
|
74
|
+
match = name;
|
|
75
|
+
}
|
|
76
|
+
return match;
|
|
77
|
+
};
|
|
78
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
79
|
+
var isThreshold = (value) => typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
|
|
80
|
+
|
|
81
|
+
// src/solid/index.ts
|
|
5
82
|
class ResponsiveTuiProviderError extends Error {
|
|
6
83
|
_tag = "ResponsiveTuiProviderError";
|
|
7
84
|
constructor() {
|
|
@@ -9,7 +86,8 @@ class ResponsiveTuiProviderError extends Error {
|
|
|
9
86
|
this.name = this._tag;
|
|
10
87
|
}
|
|
11
88
|
}
|
|
12
|
-
var createResponsiveTui = (
|
|
89
|
+
var createResponsiveTui = (scales) => {
|
|
90
|
+
const breakpoints = createBreakpointDefinition(scales);
|
|
13
91
|
const ResponsiveTuiContext = createContext();
|
|
14
92
|
const ResponsiveTUI = (props) => {
|
|
15
93
|
const dimensions = useTerminalDimensions();
|
|
@@ -32,6 +110,7 @@ var createResponsiveTui = (breakpoints) => {
|
|
|
32
110
|
return { ResponsiveTUI, useResponsiveTui };
|
|
33
111
|
};
|
|
34
112
|
export {
|
|
113
|
+
ResponsiveTuiConfigurationError,
|
|
35
114
|
ResponsiveTuiProviderError,
|
|
36
115
|
createResponsiveTui
|
|
37
116
|
};
|