@moyarich/console-addon-resizable 0.0.0-stage → 0.1.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
@@ -1,3 +1,33 @@
1
- # Temporary Holding Version
1
+ # @moyarich/console-addon-resizable
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Resize the Console panel. Add configurable resize handles, sizing limits, and resize callbacks without changing output behavior.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @moyarich/console @moyarich/console-addon-resizable
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```tsx
14
+ import { Console } from "@moyarich/console";
15
+ import { createResizableConsoleAddon } from "@moyarich/console-addon-resizable";
16
+
17
+ const addons = [createResizableConsoleAddon()];
18
+
19
+ // Pass your application's structured messages to Console.
20
+ <Console messages={messages} addons={addons} />;
21
+ ```
22
+
23
+ The addon is optional and can be combined with other Console addons. The host application remains responsible for supplying and retaining messages.
24
+
25
+ ## Learn more
26
+
27
+ Explore [runnable package examples](./examples) and the [Console documentation](https://moyarich.github.io/console/) for available settings and integration patterns.
28
+
29
+ Review the [changelog](./CHANGELOG.md) for changes by version and [package metadata](./package.json) for dependency requirements.
30
+
31
+ ## License
32
+
33
+ MIT
@@ -0,0 +1,47 @@
1
+ import "./ResizableConsoleFrame.css";
2
+ import { type CSSProperties, type ReactNode } from "react";
3
+ import type { ConsoleMode } from "@moyarich/console-core";
4
+ export type ConsoleResizeDirection = "vertical" | "horizontal" | "both" | "block" | "inline";
5
+ export type ConsoleResizeEdge = "start" | "end";
6
+ export interface ConsoleResizeSize {
7
+ width: number;
8
+ height: number;
9
+ }
10
+ export interface ResizableConsoleAddonOptions {
11
+ /** Resize axis. Logical block/inline map to vertical/horizontal in the default writing mode. @default "both" */
12
+ direction?: ConsoleResizeDirection;
13
+ /** Initial width used when horizontal resizing is active. @default 720 */
14
+ defaultWidth?: number | string;
15
+ /** Initial height used when vertical resizing is active. @default 300 */
16
+ defaultHeight?: number | string;
17
+ /** Minimum width while dragging. @default 320 */
18
+ minWidth?: number;
19
+ /** Minimum height while dragging. @default 180 */
20
+ minHeight?: number;
21
+ /** Optional maximum width while dragging. The parent width is always respected. */
22
+ maxWidth?: number;
23
+ /** Optional maximum height while dragging. */
24
+ maxHeight?: number;
25
+ /** Physical edge that owns the horizontal resize handle. @default "end" */
26
+ horizontalEdge?: ConsoleResizeEdge;
27
+ /** Physical edge that owns the vertical resize handle. @default "end" */
28
+ verticalEdge?: ConsoleResizeEdge;
29
+ /** Additional class name applied to the addon-owned frame. */
30
+ className?: string;
31
+ /** Additional inline styles and CSS custom-property overrides applied to the addon-owned frame. */
32
+ style?: CSSProperties;
33
+ /** Called whenever a drag updates the rendered frame size. */
34
+ onResize?: (size: ConsoleResizeSize) => void;
35
+ }
36
+ interface ResizeAxes {
37
+ horizontal: boolean;
38
+ vertical: boolean;
39
+ }
40
+ interface ResizableConsoleFrameProps {
41
+ mode: ConsoleMode;
42
+ options: ResizableConsoleAddonOptions;
43
+ children: ReactNode;
44
+ }
45
+ export declare function resolveConsoleResizeAxes(direction: ConsoleResizeDirection): ResizeAxes;
46
+ export declare function ResizableConsoleFrame({ mode, options, children, }: ResizableConsoleFrameProps): import("react").JSX.Element;
47
+ export {};
@@ -0,0 +1,293 @@
1
+ /* ==========================================================================
2
+ Resizable console addon
3
+ ========================================================================== */
4
+
5
+ .console-resizable-frame,
6
+ .console-resizable-frame *,
7
+ .console-resizable-frame *::before,
8
+ .console-resizable-frame *::after {
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ .console-resizable-frame {
13
+ --_console-resize-color-scheme: var(
14
+ --console-resize-color-scheme,
15
+ var(--console-panel-color-scheme, var(--console-color-scheme, inherit))
16
+ );
17
+ --_console-resize-separator-line-background-color: var(
18
+ --console-resize-separator-line-background-color,
19
+ color-mix(in oklab, currentColor 18%, transparent)
20
+ );
21
+ --_console-resize-separator-hover-line-background-color: var(
22
+ --console-resize-separator-hover-line-background-color,
23
+ color-mix(in oklab, currentColor 38%, transparent)
24
+ );
25
+ --_console-resize-separator-active-line-background-color: var(
26
+ --console-resize-separator-active-line-background-color,
27
+ light-dark(#635bff, #8b83ff)
28
+ );
29
+ --_console-resize-separator-grip-color: var(
30
+ --console-resize-separator-grip-color,
31
+ color-mix(in oklab, currentColor 58%, transparent)
32
+ );
33
+ --_console-resize-separator-active-grip-color: var(
34
+ --console-resize-separator-active-grip-color,
35
+ light-dark(#635bff, #8b83ff)
36
+ );
37
+ --_console-resize-separator-grip-border: var(
38
+ --console-resize-separator-grip-border,
39
+ 1px solid color-mix(in oklab, currentColor 18%, transparent)
40
+ );
41
+ --_console-resize-separator-grip-background-color: var(
42
+ --console-resize-separator-grip-background-color,
43
+ light-dark(
44
+ color-mix(in oklab, white 96%, currentColor 4%),
45
+ color-mix(in oklab, #111113 94%, currentColor 6%)
46
+ )
47
+ );
48
+ --_console-resize-separator-grip-border-radius: var(
49
+ --console-resize-separator-grip-border-radius,
50
+ 999px
51
+ );
52
+ --_console-resize-separator-grip-box-shadow: var(
53
+ --console-resize-separator-grip-box-shadow,
54
+ 0 4px 14px color-mix(in oklab, currentColor 14%, transparent)
55
+ );
56
+ --_console-resize-handle-size: var(--console-resize-handle-size, 10px);
57
+ --_console-resize-corner-size: var(--console-resize-corner-size, 28px);
58
+
59
+ color-scheme: var(--_console-resize-color-scheme);
60
+ position: relative;
61
+ display: flex;
62
+ width: 100%;
63
+ max-width: 100%;
64
+ min-width: 0;
65
+ height: 100%;
66
+ max-height: 100%;
67
+ min-height: 0;
68
+ }
69
+
70
+ .console-resizable-frame[data-console-resize-direction="horizontal"],
71
+ .console-resizable-frame[data-console-resize-direction="inline"],
72
+ .console-resizable-frame[data-console-resize-direction="both"] {
73
+ width: var(--_console-resizable-width);
74
+ min-width: var(--_console-resizable-min-width);
75
+ max-width: var(--_console-resizable-max-width);
76
+ }
77
+
78
+ .console-resizable-frame[data-console-resize-direction="vertical"],
79
+ .console-resizable-frame[data-console-resize-direction="block"],
80
+ .console-resizable-frame[data-console-resize-direction="both"] {
81
+ height: var(--_console-resizable-height);
82
+ min-height: var(--_console-resizable-min-height);
83
+ max-height: var(--_console-resizable-max-height);
84
+ }
85
+
86
+ .console-resizable-frame-content {
87
+ display: flex;
88
+ width: 100%;
89
+ min-width: 0;
90
+ height: 100%;
91
+ min-height: 0;
92
+ overflow: hidden;
93
+ }
94
+
95
+ .console-resize-handle {
96
+ position: absolute;
97
+ z-index: 3;
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ color: var(--_console-resize-separator-grip-color);
102
+ outline: none;
103
+ touch-action: none;
104
+ }
105
+
106
+ .console-resize-handle[data-console-resize-axis="horizontal"] {
107
+ top: 0;
108
+ width: var(--_console-resize-handle-size);
109
+ height: 100%;
110
+ cursor: col-resize;
111
+ }
112
+
113
+ .console-resizable-frame[data-console-resize-horizontal-edge="end"]
114
+ > .console-resize-handle[data-console-resize-axis="horizontal"] {
115
+ right: calc(var(--_console-resize-handle-size) / -2);
116
+ }
117
+
118
+ .console-resizable-frame[data-console-resize-horizontal-edge="start"]
119
+ > .console-resize-handle[data-console-resize-axis="horizontal"] {
120
+ left: calc(var(--_console-resize-handle-size) / -2);
121
+ }
122
+
123
+ .console-resize-handle[data-console-resize-axis="vertical"] {
124
+ left: 0;
125
+ width: 100%;
126
+ height: var(--_console-resize-handle-size);
127
+ cursor: row-resize;
128
+ }
129
+
130
+ .console-resizable-frame[data-console-resize-vertical-edge="end"]
131
+ > .console-resize-handle[data-console-resize-axis="vertical"] {
132
+ bottom: calc(var(--_console-resize-handle-size) / -2);
133
+ }
134
+
135
+ .console-resizable-frame[data-console-resize-vertical-edge="start"]
136
+ > .console-resize-handle[data-console-resize-axis="vertical"] {
137
+ top: calc(var(--_console-resize-handle-size) / -2);
138
+ }
139
+
140
+ .console-resize-handle::before {
141
+ position: absolute;
142
+ background: var(--_console-resize-separator-line-background-color);
143
+ content: "";
144
+ pointer-events: none;
145
+ transition: background 120ms ease;
146
+ margin-top: -1px;
147
+ border-radius: 6px;
148
+ opacity: 0;
149
+ }
150
+
151
+ .console-resize-handle[data-console-resize-axis="horizontal"]::before {
152
+ width: 5px;
153
+ height: 90%;
154
+ }
155
+
156
+ .console-resize-handle[data-console-resize-axis="vertical"]::before {
157
+ width: 90%;
158
+ height: 5px;
159
+ }
160
+
161
+ .console-resize-handle:hover::before,
162
+ .console-resize-handle:focus-visible::before {
163
+ background: var(--_console-resize-separator-hover-line-background-color);
164
+ opacity: 1;
165
+ }
166
+
167
+ .console-resize-handle[data-active="true"]::before {
168
+ background: var(--_console-resize-separator-active-line-background-color);
169
+ }
170
+
171
+ .console-resize-grip {
172
+ position: relative;
173
+ display: inline-flex;
174
+ align-items: center;
175
+ justify-content: center;
176
+ border: 1px solid transparent;
177
+ border-radius: var(--_console-resize-separator-grip-border-radius);
178
+ background: transparent;
179
+ box-shadow: none;
180
+ color: inherit;
181
+ opacity: 0.55;
182
+ pointer-events: none;
183
+ transition:
184
+ opacity 120ms ease,
185
+ background 120ms ease,
186
+ border-color 120ms ease,
187
+ box-shadow 120ms ease,
188
+ color 120ms ease;
189
+ }
190
+
191
+ .console-resize-handle[data-console-resize-axis="horizontal"]
192
+ > .console-resize-grip {
193
+ width: 22px;
194
+ height: 34px;
195
+ }
196
+
197
+ .console-resize-handle[data-console-resize-axis="vertical"]
198
+ > .console-resize-grip {
199
+ width: 34px;
200
+ height: 22px;
201
+ }
202
+
203
+ .console-resize-handle:hover > .console-resize-grip,
204
+ .console-resize-handle:focus-visible > .console-resize-grip,
205
+ .console-resize-handle[data-active="true"] > .console-resize-grip {
206
+ border: var(--_console-resize-separator-grip-border);
207
+ background: var(--_console-resize-separator-grip-background-color);
208
+ box-shadow: var(--_console-resize-separator-grip-box-shadow);
209
+ opacity: 1;
210
+ }
211
+
212
+ .console-resize-handle[data-active="true"] > .console-resize-grip {
213
+ color: var(--_console-resize-separator-active-grip-color);
214
+ }
215
+
216
+ .console-resize-corner-handle {
217
+ position: absolute;
218
+ z-index: 4;
219
+ display: flex;
220
+ width: var(--_console-resize-corner-size);
221
+ height: var(--_console-resize-corner-size);
222
+ align-items: center;
223
+ justify-content: center;
224
+ border: 1px solid transparent;
225
+ border-radius: 8px;
226
+ background: transparent;
227
+ box-shadow: none;
228
+ color: var(--_console-resize-separator-grip-color);
229
+ opacity: 0.65;
230
+ outline: none;
231
+ touch-action: none;
232
+ transition:
233
+ opacity 120ms ease,
234
+ background 120ms ease,
235
+ border-color 120ms ease,
236
+ box-shadow 120ms ease,
237
+ color 120ms ease;
238
+ }
239
+
240
+ .console-resizable-frame[data-console-resize-horizontal-edge="end"]
241
+ > .console-resize-corner-handle {
242
+ right: -4px;
243
+ }
244
+
245
+ .console-resizable-frame[data-console-resize-horizontal-edge="start"]
246
+ > .console-resize-corner-handle {
247
+ left: -4px;
248
+ }
249
+
250
+ .console-resizable-frame[data-console-resize-vertical-edge="end"]
251
+ > .console-resize-corner-handle {
252
+ bottom: -4px;
253
+ }
254
+
255
+ .console-resizable-frame[data-console-resize-vertical-edge="start"]
256
+ > .console-resize-corner-handle {
257
+ top: -4px;
258
+ }
259
+
260
+ .console-resizable-frame[data-console-resize-horizontal-edge="end"][data-console-resize-vertical-edge="end"]
261
+ > .console-resize-corner-handle,
262
+ .console-resizable-frame[data-console-resize-horizontal-edge="start"][data-console-resize-vertical-edge="start"]
263
+ > .console-resize-corner-handle {
264
+ cursor: nwse-resize;
265
+ }
266
+
267
+ .console-resizable-frame[data-console-resize-horizontal-edge="end"][data-console-resize-vertical-edge="start"]
268
+ > .console-resize-corner-handle,
269
+ .console-resizable-frame[data-console-resize-horizontal-edge="start"][data-console-resize-vertical-edge="end"]
270
+ > .console-resize-corner-handle {
271
+ cursor: nesw-resize;
272
+ }
273
+
274
+ .console-resize-corner-handle:hover,
275
+ .console-resize-corner-handle:focus-visible,
276
+ .console-resize-corner-handle[data-active="true"] {
277
+ border: var(--_console-resize-separator-grip-border);
278
+ background: var(--_console-resize-separator-grip-background-color);
279
+ box-shadow: var(--_console-resize-separator-grip-box-shadow);
280
+ opacity: 1;
281
+ }
282
+
283
+ .console-resize-corner-handle[data-active="true"] {
284
+ color: var(--_console-resize-separator-active-grip-color);
285
+ }
286
+
287
+ @media (prefers-reduced-motion: reduce) {
288
+ .console-resize-handle::before,
289
+ .console-resize-grip,
290
+ .console-resize-corner-handle {
291
+ transition: none;
292
+ }
293
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const lucideReact = require("lucide-react");
5
+ const react = require("react");
6
+ const consoleCore = require("@moyarich/console-core");
7
+ function resolveConsoleResizeAxes(direction) {
8
+ return {
9
+ horizontal: direction === "horizontal" || direction === "both" || direction === "inline",
10
+ vertical: direction === "vertical" || direction === "both" || direction === "block"
11
+ };
12
+ }
13
+ function clamp(value, min, max) {
14
+ return Math.min(Math.max(value, min), Math.max(min, max));
15
+ }
16
+ function getCornerCursor(horizontalEdge, verticalEdge) {
17
+ return horizontalEdge === verticalEdge ? "nwse-resize" : "nesw-resize";
18
+ }
19
+ function toCssSize(value) {
20
+ return typeof value === "number" ? `${value}px` : value;
21
+ }
22
+ function ResizableConsoleFrame({
23
+ mode,
24
+ options,
25
+ children
26
+ }) {
27
+ const {
28
+ direction = "both",
29
+ defaultWidth = 720,
30
+ defaultHeight = 300,
31
+ minWidth = 320,
32
+ minHeight = 180,
33
+ maxWidth,
34
+ maxHeight,
35
+ horizontalEdge = "end",
36
+ verticalEdge = "end",
37
+ className = "",
38
+ style,
39
+ onResize
40
+ } = options;
41
+ const axes = resolveConsoleResizeAxes(direction);
42
+ const frameRef = react.useRef(null);
43
+ const dragRef = react.useRef(null);
44
+ const bodyStyleRef = react.useRef(
45
+ null
46
+ );
47
+ const [width, setWidth] = react.useState(defaultWidth);
48
+ const [height, setHeight] = react.useState(defaultHeight);
49
+ const [activeAxis, setActiveAxis] = react.useState(null);
50
+ const restoreBodyStyles = react.useCallback(() => {
51
+ if (typeof document === "undefined" || !bodyStyleRef.current) {
52
+ return;
53
+ }
54
+ document.body.style.cursor = bodyStyleRef.current.cursor;
55
+ document.body.style.userSelect = bodyStyleRef.current.userSelect;
56
+ bodyStyleRef.current = null;
57
+ }, []);
58
+ const stopResize = react.useCallback(
59
+ (event) => {
60
+ const drag = dragRef.current;
61
+ if (drag && event && drag.pointerId === event.pointerId && drag.target.hasPointerCapture(event.pointerId)) {
62
+ drag.target.releasePointerCapture(event.pointerId);
63
+ }
64
+ dragRef.current = null;
65
+ setActiveAxis(null);
66
+ restoreBodyStyles();
67
+ },
68
+ [restoreBodyStyles]
69
+ );
70
+ react.useEffect(
71
+ () => () => {
72
+ dragRef.current = null;
73
+ restoreBodyStyles();
74
+ },
75
+ [restoreBodyStyles]
76
+ );
77
+ react.useEffect(() => {
78
+ if (!dragRef.current) {
79
+ return;
80
+ }
81
+ dragRef.current = null;
82
+ setActiveAxis(null);
83
+ restoreBodyStyles();
84
+ }, [direction, restoreBodyStyles]);
85
+ const startResize = (event, axis) => {
86
+ event.preventDefault();
87
+ const frame = frameRef.current;
88
+ if (!frame) {
89
+ return;
90
+ }
91
+ const bounds = frame.getBoundingClientRect();
92
+ const parentBounds = frame.parentElement?.getBoundingClientRect();
93
+ const parentMaxWidth = parentBounds?.width ?? Number.POSITIVE_INFINITY;
94
+ const resolvedMaxWidth = Math.min(
95
+ maxWidth ?? parentMaxWidth,
96
+ parentMaxWidth
97
+ );
98
+ const resolvedMaxHeight = maxHeight ?? Number.POSITIVE_INFINITY;
99
+ event.currentTarget.setPointerCapture(event.pointerId);
100
+ dragRef.current = {
101
+ axis,
102
+ pointerId: event.pointerId,
103
+ target: event.currentTarget,
104
+ startX: event.clientX,
105
+ startY: event.clientY,
106
+ startWidth: bounds.width,
107
+ startHeight: bounds.height,
108
+ maxWidth: resolvedMaxWidth,
109
+ maxHeight: resolvedMaxHeight
110
+ };
111
+ setActiveAxis(axis);
112
+ if (typeof document !== "undefined") {
113
+ bodyStyleRef.current = {
114
+ cursor: document.body.style.cursor,
115
+ userSelect: document.body.style.userSelect
116
+ };
117
+ document.body.style.cursor = axis === "both" ? getCornerCursor(horizontalEdge, verticalEdge) : axis === "horizontal" ? "col-resize" : "row-resize";
118
+ document.body.style.userSelect = "none";
119
+ }
120
+ };
121
+ const moveResize = (event) => {
122
+ const drag = dragRef.current;
123
+ if (!drag || drag.pointerId !== event.pointerId) {
124
+ return;
125
+ }
126
+ let nextWidth = drag.startWidth;
127
+ let nextHeight = drag.startHeight;
128
+ if (drag.axis === "horizontal" || drag.axis === "both") {
129
+ const directionMultiplier = horizontalEdge === "end" ? 1 : -1;
130
+ nextWidth = clamp(
131
+ drag.startWidth + (event.clientX - drag.startX) * directionMultiplier,
132
+ minWidth,
133
+ drag.maxWidth
134
+ );
135
+ setWidth(nextWidth);
136
+ }
137
+ if (drag.axis === "vertical" || drag.axis === "both") {
138
+ const directionMultiplier = verticalEdge === "end" ? 1 : -1;
139
+ nextHeight = clamp(
140
+ drag.startHeight + (event.clientY - drag.startY) * directionMultiplier,
141
+ minHeight,
142
+ drag.maxHeight
143
+ );
144
+ setHeight(nextHeight);
145
+ }
146
+ onResize?.({
147
+ width: nextWidth,
148
+ height: nextHeight
149
+ });
150
+ };
151
+ const renderHorizontalHandle = () => {
152
+ if (!axes.horizontal) {
153
+ return null;
154
+ }
155
+ return /* @__PURE__ */ jsxRuntime.jsx(
156
+ "div",
157
+ {
158
+ className: "console-resize-handle",
159
+ role: "separator",
160
+ "aria-orientation": "vertical",
161
+ "aria-label": "Resize console horizontally",
162
+ title: "Drag to resize horizontally",
163
+ tabIndex: 0,
164
+ "data-console-resize-axis": "horizontal",
165
+ "data-active": activeAxis === "horizontal" || void 0,
166
+ onPointerDown: (event) => startResize(event, "horizontal"),
167
+ onPointerMove: moveResize,
168
+ onPointerUp: stopResize,
169
+ onPointerCancel: stopResize,
170
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "console-resize-grip", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GripVertical, { size: 14 }) })
171
+ }
172
+ );
173
+ };
174
+ const renderVerticalHandle = () => {
175
+ if (!axes.vertical) {
176
+ return null;
177
+ }
178
+ return /* @__PURE__ */ jsxRuntime.jsx(
179
+ "div",
180
+ {
181
+ className: "console-resize-handle",
182
+ role: "separator",
183
+ "aria-orientation": "horizontal",
184
+ "aria-label": "Resize console vertically",
185
+ title: "Drag to resize vertically",
186
+ tabIndex: 0,
187
+ "data-console-resize-axis": "vertical",
188
+ "data-active": activeAxis === "vertical" || void 0,
189
+ onPointerDown: (event) => startResize(event, "vertical"),
190
+ onPointerMove: moveResize,
191
+ onPointerUp: stopResize,
192
+ onPointerCancel: stopResize,
193
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "console-resize-grip", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GripHorizontal, { size: 14 }) })
194
+ }
195
+ );
196
+ };
197
+ const renderCornerHandle = () => {
198
+ if (direction !== "both") {
199
+ return null;
200
+ }
201
+ return /* @__PURE__ */ jsxRuntime.jsx(
202
+ "div",
203
+ {
204
+ className: "console-resize-corner-handle",
205
+ role: "separator",
206
+ "aria-label": "Resize console in both directions",
207
+ title: "Drag to resize width and height",
208
+ tabIndex: 0,
209
+ "data-console-resize-axis": "both",
210
+ "data-active": activeAxis === "both" || void 0,
211
+ onPointerDown: (event) => startResize(event, "both"),
212
+ onPointerMove: moveResize,
213
+ onPointerUp: stopResize,
214
+ onPointerCancel: stopResize,
215
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoveDiagonal2, { size: 15, "aria-hidden": "true" })
216
+ }
217
+ );
218
+ };
219
+ return /* @__PURE__ */ jsxRuntime.jsxs(
220
+ "div",
221
+ {
222
+ ref: frameRef,
223
+ className: `console-resizable-frame ${className}`.trim(),
224
+ "data-console-resize-direction": direction,
225
+ "data-console-resize-horizontal-edge": horizontalEdge,
226
+ "data-console-resize-vertical-edge": verticalEdge,
227
+ "data-console-mode": mode,
228
+ style: {
229
+ "--_console-resizable-width": toCssSize(width),
230
+ "--_console-resizable-height": toCssSize(height),
231
+ "--_console-resizable-min-width": `${minWidth}px`,
232
+ "--_console-resizable-min-height": `${minHeight}px`,
233
+ "--_console-resizable-max-width": maxWidth === void 0 ? "100%" : `${maxWidth}px`,
234
+ "--_console-resizable-max-height": maxHeight === void 0 ? "100%" : `${maxHeight}px`,
235
+ ...style
236
+ },
237
+ children: [
238
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "console-resizable-frame-content", children }),
239
+ renderHorizontalHandle(),
240
+ renderVerticalHandle(),
241
+ renderCornerHandle()
242
+ ]
243
+ }
244
+ );
245
+ }
246
+ const RESIZABLE_CONSOLE_ADDON_ID = "@moyarich/console-addon-resizable";
247
+ function createResizableConsoleAddon(options = {}) {
248
+ const decorator = {
249
+ render(context) {
250
+ return /* @__PURE__ */ jsxRuntime.jsx(ResizableConsoleFrame, { mode: context.mode, options, children: context.renderDefault() });
251
+ }
252
+ };
253
+ return {
254
+ id: RESIZABLE_CONSOLE_ADDON_ID,
255
+ activate(host) {
256
+ host.extensions.register(
257
+ consoleCore.consoleExtensionPoints.frameDecorator,
258
+ decorator,
259
+ {
260
+ id: `${RESIZABLE_CONSOLE_ADDON_ID}:frame`
261
+ }
262
+ );
263
+ }
264
+ };
265
+ }
266
+ exports.RESIZABLE_CONSOLE_ADDON_ID = RESIZABLE_CONSOLE_ADDON_ID;
267
+ exports.createResizableConsoleAddon = createResizableConsoleAddon;
268
+ exports.resolveConsoleResizeAxes = resolveConsoleResizeAxes;
@@ -0,0 +1,13 @@
1
+ /** Stable package-qualified identity for the resizable frame addon. */
2
+ export declare const RESIZABLE_CONSOLE_ADDON_ID = "@moyarich/console-addon-resizable";
3
+ export { resolveConsoleResizeAxes } from "./components/ResizableConsoleFrame/ResizableConsoleFrame";
4
+ export type { ConsoleResizeDirection, ConsoleResizeEdge, ConsoleResizeSize, ResizableConsoleAddonOptions, } from "./components/ResizableConsoleFrame/ResizableConsoleFrame";
5
+ import type { ResizableConsoleAddonOptions } from "./components/ResizableConsoleFrame/ResizableConsoleFrame";
6
+ import { type ConsoleAddon } from "@moyarich/console-core";
7
+ /**
8
+ * Creates a first-party frame-resizing addon.
9
+ *
10
+ * The addon contributes a generic frame decorator and leaves message/output
11
+ * rendering, viewport state, and host layout ownership unchanged.
12
+ */
13
+ export declare function createResizableConsoleAddon(options?: ResizableConsoleAddonOptions): ConsoleAddon;
package/dist/index.js ADDED
@@ -0,0 +1,268 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { GripVertical, GripHorizontal, MoveDiagonal2 } from "lucide-react";
3
+ import { useRef, useState, useCallback, useEffect } from "react";
4
+ import { consoleExtensionPoints } from "@moyarich/console-core";
5
+ function resolveConsoleResizeAxes(direction) {
6
+ return {
7
+ horizontal: direction === "horizontal" || direction === "both" || direction === "inline",
8
+ vertical: direction === "vertical" || direction === "both" || direction === "block"
9
+ };
10
+ }
11
+ function clamp(value, min, max) {
12
+ return Math.min(Math.max(value, min), Math.max(min, max));
13
+ }
14
+ function getCornerCursor(horizontalEdge, verticalEdge) {
15
+ return horizontalEdge === verticalEdge ? "nwse-resize" : "nesw-resize";
16
+ }
17
+ function toCssSize(value) {
18
+ return typeof value === "number" ? `${value}px` : value;
19
+ }
20
+ function ResizableConsoleFrame({
21
+ mode,
22
+ options,
23
+ children
24
+ }) {
25
+ const {
26
+ direction = "both",
27
+ defaultWidth = 720,
28
+ defaultHeight = 300,
29
+ minWidth = 320,
30
+ minHeight = 180,
31
+ maxWidth,
32
+ maxHeight,
33
+ horizontalEdge = "end",
34
+ verticalEdge = "end",
35
+ className = "",
36
+ style,
37
+ onResize
38
+ } = options;
39
+ const axes = resolveConsoleResizeAxes(direction);
40
+ const frameRef = useRef(null);
41
+ const dragRef = useRef(null);
42
+ const bodyStyleRef = useRef(
43
+ null
44
+ );
45
+ const [width, setWidth] = useState(defaultWidth);
46
+ const [height, setHeight] = useState(defaultHeight);
47
+ const [activeAxis, setActiveAxis] = useState(null);
48
+ const restoreBodyStyles = useCallback(() => {
49
+ if (typeof document === "undefined" || !bodyStyleRef.current) {
50
+ return;
51
+ }
52
+ document.body.style.cursor = bodyStyleRef.current.cursor;
53
+ document.body.style.userSelect = bodyStyleRef.current.userSelect;
54
+ bodyStyleRef.current = null;
55
+ }, []);
56
+ const stopResize = useCallback(
57
+ (event) => {
58
+ const drag = dragRef.current;
59
+ if (drag && event && drag.pointerId === event.pointerId && drag.target.hasPointerCapture(event.pointerId)) {
60
+ drag.target.releasePointerCapture(event.pointerId);
61
+ }
62
+ dragRef.current = null;
63
+ setActiveAxis(null);
64
+ restoreBodyStyles();
65
+ },
66
+ [restoreBodyStyles]
67
+ );
68
+ useEffect(
69
+ () => () => {
70
+ dragRef.current = null;
71
+ restoreBodyStyles();
72
+ },
73
+ [restoreBodyStyles]
74
+ );
75
+ useEffect(() => {
76
+ if (!dragRef.current) {
77
+ return;
78
+ }
79
+ dragRef.current = null;
80
+ setActiveAxis(null);
81
+ restoreBodyStyles();
82
+ }, [direction, restoreBodyStyles]);
83
+ const startResize = (event, axis) => {
84
+ event.preventDefault();
85
+ const frame = frameRef.current;
86
+ if (!frame) {
87
+ return;
88
+ }
89
+ const bounds = frame.getBoundingClientRect();
90
+ const parentBounds = frame.parentElement?.getBoundingClientRect();
91
+ const parentMaxWidth = parentBounds?.width ?? Number.POSITIVE_INFINITY;
92
+ const resolvedMaxWidth = Math.min(
93
+ maxWidth ?? parentMaxWidth,
94
+ parentMaxWidth
95
+ );
96
+ const resolvedMaxHeight = maxHeight ?? Number.POSITIVE_INFINITY;
97
+ event.currentTarget.setPointerCapture(event.pointerId);
98
+ dragRef.current = {
99
+ axis,
100
+ pointerId: event.pointerId,
101
+ target: event.currentTarget,
102
+ startX: event.clientX,
103
+ startY: event.clientY,
104
+ startWidth: bounds.width,
105
+ startHeight: bounds.height,
106
+ maxWidth: resolvedMaxWidth,
107
+ maxHeight: resolvedMaxHeight
108
+ };
109
+ setActiveAxis(axis);
110
+ if (typeof document !== "undefined") {
111
+ bodyStyleRef.current = {
112
+ cursor: document.body.style.cursor,
113
+ userSelect: document.body.style.userSelect
114
+ };
115
+ document.body.style.cursor = axis === "both" ? getCornerCursor(horizontalEdge, verticalEdge) : axis === "horizontal" ? "col-resize" : "row-resize";
116
+ document.body.style.userSelect = "none";
117
+ }
118
+ };
119
+ const moveResize = (event) => {
120
+ const drag = dragRef.current;
121
+ if (!drag || drag.pointerId !== event.pointerId) {
122
+ return;
123
+ }
124
+ let nextWidth = drag.startWidth;
125
+ let nextHeight = drag.startHeight;
126
+ if (drag.axis === "horizontal" || drag.axis === "both") {
127
+ const directionMultiplier = horizontalEdge === "end" ? 1 : -1;
128
+ nextWidth = clamp(
129
+ drag.startWidth + (event.clientX - drag.startX) * directionMultiplier,
130
+ minWidth,
131
+ drag.maxWidth
132
+ );
133
+ setWidth(nextWidth);
134
+ }
135
+ if (drag.axis === "vertical" || drag.axis === "both") {
136
+ const directionMultiplier = verticalEdge === "end" ? 1 : -1;
137
+ nextHeight = clamp(
138
+ drag.startHeight + (event.clientY - drag.startY) * directionMultiplier,
139
+ minHeight,
140
+ drag.maxHeight
141
+ );
142
+ setHeight(nextHeight);
143
+ }
144
+ onResize?.({
145
+ width: nextWidth,
146
+ height: nextHeight
147
+ });
148
+ };
149
+ const renderHorizontalHandle = () => {
150
+ if (!axes.horizontal) {
151
+ return null;
152
+ }
153
+ return /* @__PURE__ */ jsx(
154
+ "div",
155
+ {
156
+ className: "console-resize-handle",
157
+ role: "separator",
158
+ "aria-orientation": "vertical",
159
+ "aria-label": "Resize console horizontally",
160
+ title: "Drag to resize horizontally",
161
+ tabIndex: 0,
162
+ "data-console-resize-axis": "horizontal",
163
+ "data-active": activeAxis === "horizontal" || void 0,
164
+ onPointerDown: (event) => startResize(event, "horizontal"),
165
+ onPointerMove: moveResize,
166
+ onPointerUp: stopResize,
167
+ onPointerCancel: stopResize,
168
+ children: /* @__PURE__ */ jsx("span", { className: "console-resize-grip", "aria-hidden": "true", children: /* @__PURE__ */ jsx(GripVertical, { size: 14 }) })
169
+ }
170
+ );
171
+ };
172
+ const renderVerticalHandle = () => {
173
+ if (!axes.vertical) {
174
+ return null;
175
+ }
176
+ return /* @__PURE__ */ jsx(
177
+ "div",
178
+ {
179
+ className: "console-resize-handle",
180
+ role: "separator",
181
+ "aria-orientation": "horizontal",
182
+ "aria-label": "Resize console vertically",
183
+ title: "Drag to resize vertically",
184
+ tabIndex: 0,
185
+ "data-console-resize-axis": "vertical",
186
+ "data-active": activeAxis === "vertical" || void 0,
187
+ onPointerDown: (event) => startResize(event, "vertical"),
188
+ onPointerMove: moveResize,
189
+ onPointerUp: stopResize,
190
+ onPointerCancel: stopResize,
191
+ children: /* @__PURE__ */ jsx("span", { className: "console-resize-grip", "aria-hidden": "true", children: /* @__PURE__ */ jsx(GripHorizontal, { size: 14 }) })
192
+ }
193
+ );
194
+ };
195
+ const renderCornerHandle = () => {
196
+ if (direction !== "both") {
197
+ return null;
198
+ }
199
+ return /* @__PURE__ */ jsx(
200
+ "div",
201
+ {
202
+ className: "console-resize-corner-handle",
203
+ role: "separator",
204
+ "aria-label": "Resize console in both directions",
205
+ title: "Drag to resize width and height",
206
+ tabIndex: 0,
207
+ "data-console-resize-axis": "both",
208
+ "data-active": activeAxis === "both" || void 0,
209
+ onPointerDown: (event) => startResize(event, "both"),
210
+ onPointerMove: moveResize,
211
+ onPointerUp: stopResize,
212
+ onPointerCancel: stopResize,
213
+ children: /* @__PURE__ */ jsx(MoveDiagonal2, { size: 15, "aria-hidden": "true" })
214
+ }
215
+ );
216
+ };
217
+ return /* @__PURE__ */ jsxs(
218
+ "div",
219
+ {
220
+ ref: frameRef,
221
+ className: `console-resizable-frame ${className}`.trim(),
222
+ "data-console-resize-direction": direction,
223
+ "data-console-resize-horizontal-edge": horizontalEdge,
224
+ "data-console-resize-vertical-edge": verticalEdge,
225
+ "data-console-mode": mode,
226
+ style: {
227
+ "--_console-resizable-width": toCssSize(width),
228
+ "--_console-resizable-height": toCssSize(height),
229
+ "--_console-resizable-min-width": `${minWidth}px`,
230
+ "--_console-resizable-min-height": `${minHeight}px`,
231
+ "--_console-resizable-max-width": maxWidth === void 0 ? "100%" : `${maxWidth}px`,
232
+ "--_console-resizable-max-height": maxHeight === void 0 ? "100%" : `${maxHeight}px`,
233
+ ...style
234
+ },
235
+ children: [
236
+ /* @__PURE__ */ jsx("div", { className: "console-resizable-frame-content", children }),
237
+ renderHorizontalHandle(),
238
+ renderVerticalHandle(),
239
+ renderCornerHandle()
240
+ ]
241
+ }
242
+ );
243
+ }
244
+ const RESIZABLE_CONSOLE_ADDON_ID = "@moyarich/console-addon-resizable";
245
+ function createResizableConsoleAddon(options = {}) {
246
+ const decorator = {
247
+ render(context) {
248
+ return /* @__PURE__ */ jsx(ResizableConsoleFrame, { mode: context.mode, options, children: context.renderDefault() });
249
+ }
250
+ };
251
+ return {
252
+ id: RESIZABLE_CONSOLE_ADDON_ID,
253
+ activate(host) {
254
+ host.extensions.register(
255
+ consoleExtensionPoints.frameDecorator,
256
+ decorator,
257
+ {
258
+ id: `${RESIZABLE_CONSOLE_ADDON_ID}:frame`
259
+ }
260
+ );
261
+ }
262
+ };
263
+ }
264
+ export {
265
+ RESIZABLE_CONSOLE_ADDON_ID,
266
+ createResizableConsoleAddon,
267
+ resolveConsoleResizeAxes
268
+ };
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "@moyarich/console-addon-resizable",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "First-party resizable frame addon for @moyarich/console hosts.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "development": "./src/index.tsx",
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ },
17
+ "./styles.css": "./src/styles.css"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "src/styles.css"
22
+ ],
23
+ "publishConfig": {
24
+ "registry": "https://npm.pkg.github.com"
25
+ },
26
+ "scripts": {
27
+ "build": "vite build && tsc -p tsconfig.build.json",
28
+ "typecheck": "tsc --noEmit"
29
+ },
30
+ "dependencies": {
31
+ "lucide-react": "^1.47.0"
32
+ },
33
+ "peerDependencies": {
34
+ "@moyarich/console-core": "^0.1.0",
35
+ "react": ">=18"
36
+ },
37
+ "devDependencies": {
38
+ "@moyarich/console-core": "0.1.0",
39
+ "@types/react": "^19.1.12",
40
+ "react": "^19.2.7",
41
+ "typescript": "^5.9.2",
42
+ "vite": "^7.1.5"
43
+ },
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/moyarich/console.git",
47
+ "directory": "packages/addons/resizable"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/moyarich/console/issues"
51
+ },
52
+ "homepage": "https://github.com/moyarich/console#readme",
53
+ "keywords": [
54
+ "console",
55
+ "addon",
56
+ "resize",
57
+ "developer-tools"
58
+ ],
59
+ "sideEffects": [
60
+ "./src/index.tsx",
61
+ "./src/components/ResizableConsoleFrame/ResizableConsoleFrame.css",
62
+ "./dist/index.js",
63
+ "./dist/index.cjs"
64
+ ],
65
+ "publisher": "moyarich",
66
+ "author": "Moya Richards",
67
+ "private": false
68
+ }