@beehexa/vue-components 0.0.0-stage → 2611.5.1
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 +150 -2
- package/dist/ResizablePanel.d.ts +122 -0
- package/dist/ResizablePanel.js +359 -0
- package/dist/css.d.ts +2 -0
- package/dist/geometry.d.ts +9 -0
- package/dist/geometry.js +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/styles.css +272 -0
- package/package.json +46 -4
package/README.md
CHANGED
|
@@ -1,3 +1,151 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @beehexa/vue-components
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Source-owned Vue 3 components from beehexa.design, independent of Ant Design Vue or
|
|
4
|
+
any other UI framework. `ResizablePanel` is the first component. The private
|
|
5
|
+
`0.0.0` source package is for local development; staging does not authorize a
|
|
6
|
+
release or substitution of an invented registry version.
|
|
7
|
+
|
|
8
|
+
## Install and use
|
|
9
|
+
|
|
10
|
+
Use the actual local workspace/package reference until an authorized version is
|
|
11
|
+
published. Vue 3.5 or newer is a peer dependency. The only package dependency is
|
|
12
|
+
`@beehexa/design-tokens` at the matching local `0.0.0` version.
|
|
13
|
+
|
|
14
|
+
```vue
|
|
15
|
+
<script setup lang="ts">
|
|
16
|
+
import { ref } from 'vue';
|
|
17
|
+
import { ResizablePanel, type ResizablePanelResizeEvent } from '@beehexa/vue-components';
|
|
18
|
+
import '@beehexa/vue-components/css';
|
|
19
|
+
|
|
20
|
+
const ratio = ref(0.6);
|
|
21
|
+
function onResize(event: ResizablePanelResizeEvent) {
|
|
22
|
+
// DOM layout is complete: ask an editor/chart/canvas to measure its retained host.
|
|
23
|
+
// event.first and event.second contain actual rendered width and height.
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<div style="height: 32rem; min-height: 0; min-width: 0">
|
|
29
|
+
<ResizablePanel v-model:ratio="ratio" separator-label="Resize source and preview" @resize="onResize">
|
|
30
|
+
<template #first><SourceEditor /></template>
|
|
31
|
+
<template #second><PreviewCanvas /></template>
|
|
32
|
+
</ResizablePanel>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The CSS entry has an export declaration for strict TypeScript side-effect imports
|
|
38
|
+
and imports the source design-token CSS. Import it once. No app-owned AntD adapter,
|
|
39
|
+
SFC compiler, global registration or global body style is required. Runtime imports
|
|
40
|
+
are safe during SSR; observation, pointer capture and layout reads begin on mount.
|
|
41
|
+
IDs use Vue's SSR-safe `useId` rather than a module-global instance counter.
|
|
42
|
+
|
|
43
|
+
## Public exports
|
|
44
|
+
|
|
45
|
+
- `ResizablePanel`: Vue component, slots `first` and `second`.
|
|
46
|
+
- `ResizablePanelDirection = 'horizontal' | 'vertical'`.
|
|
47
|
+
- `ResizablePanelResizeEvent`: the event contract below.
|
|
48
|
+
- `@beehexa/vue-components/css`: typed side-effect CSS entry.
|
|
49
|
+
|
|
50
|
+
### Props
|
|
51
|
+
|
|
52
|
+
| Prop | Type | Default | Behavior |
|
|
53
|
+
| --- | --- | --- | --- |
|
|
54
|
+
| `direction` | `ResizablePanelDirection` | `'horizontal'` | Horizontal is left/right panes with a vertical separator. Vertical is top/bottom panes with a horizontal separator. |
|
|
55
|
+
| `ratio` | `number` | `0.5` | Requested first-pane fraction, supports `v-model:ratio`. Pointer and keyboard changes emit a bounded ratio. |
|
|
56
|
+
| `min` | `number` | `100` | Minimum CSS pixels for **each** pane on the split axis. Use `0` for real endpoint collapse. |
|
|
57
|
+
| `max` | `number` | `Infinity` | First-pane maximum in CSS pixels. |
|
|
58
|
+
| `minRatio` | `number` | `0` | Lower first-pane fraction, constrained to `[0, 1]`. |
|
|
59
|
+
| `maxRatio` | `number` | `1` | Upper first-pane fraction, constrained to `[0, 1]`. |
|
|
60
|
+
| `secondVisible` | `boolean` | `true` | Hides the second pane without unmounting either slot. First fills the host; separator is hidden. Restores the requested ratio when shown. |
|
|
61
|
+
| `resizerSize` | `string` | `'90%'` | Reactive CSS length/percentage for the visible divider **line**, centered on the cross axis and capped at host length. The centered handle remains visible; the hit target spans the cross axis. |
|
|
62
|
+
| `separatorLabel` | `string` | `'Resize panels'` | Accessible name. Consumer supplies the translated/contextual label. |
|
|
63
|
+
|
|
64
|
+
Both panes stay mounted when one reaches zero or the second is hidden. Child
|
|
65
|
+
state, Monaco models, diagram zoom and fullscreen state are not reset by ratio,
|
|
66
|
+
direction or visibility changes. Pane wrappers clip ordinary overflowing content;
|
|
67
|
+
the physical gutter keeps the complete handle clear of their scroll targets.
|
|
68
|
+
At rest root and panes do not create stacking contexts: nested fixed overlays share
|
|
69
|
+
their actual overlay owner's stack above the outer divider. Root isolation and
|
|
70
|
+
pane stacking apply only during a pointer gesture, beneath its local iframe shield.
|
|
71
|
+
The component adds no heading, chrome, app breakpoint or global overlay layer.
|
|
72
|
+
The consumer computes `direction` from its existing breakpoint policy and provides
|
|
73
|
+
a bounded host (`min-width: 0`, `min-height: 0` in surrounding flex/grid layouts).
|
|
74
|
+
|
|
75
|
+
Pane wrappers are shrinkable column flex containers. Fill-layout Cards, editors
|
|
76
|
+
and canvases can consume the available pane height without relying on an
|
|
77
|
+
intrinsic content height or a consumer patch to the wrapper.
|
|
78
|
+
|
|
79
|
+
Pixel minima yield symmetrically when a host is too small to fit both, never
|
|
80
|
+
forcing overflow. Explicit ratio/first-pane maximums take precedence over
|
|
81
|
+
contradictory minima. A temporarily small host clamps **effective layout**, not the
|
|
82
|
+
stored requested ratio; growing the host restores it. External model updates also
|
|
83
|
+
request a ratio subject to current bounds. `resize.ratio` reports that effective
|
|
84
|
+
split fraction (retained while the second pane is hidden); pane dimensions report
|
|
85
|
+
the actual visible layout. A fixed interval (`minRatio === maxRatio`, including
|
|
86
|
+
`0` or `1` with `min=0`) hides/disables the separator and cancels an active gesture.
|
|
87
|
+
|
|
88
|
+
### Events
|
|
89
|
+
|
|
90
|
+
`update:ratio` supplies the new numeric first-pane fraction for user resizing.
|
|
91
|
+
`resize-start`, `resize` and `resize-end` carry:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
interface ResizablePanelResizeEvent {
|
|
95
|
+
ratio: number;
|
|
96
|
+
direction: 'horizontal' | 'vertical';
|
|
97
|
+
first: { width: number; height: number };
|
|
98
|
+
second: { width: number; height: number };
|
|
99
|
+
source: 'pointer' | 'keyboard' | 'container' | 'programmatic';
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Dimensions are actual `getBoundingClientRect()` sizes, read after Vue's DOM update
|
|
104
|
+
and the next animation frame. A hidden second pane reports zero dimensions.
|
|
105
|
+
Movement notifications coalesce within a frame; lifecycle ordering is retained.
|
|
106
|
+
Parents should use `resize` to update editor/chart/canvas host layout, not change a
|
|
107
|
+
child key, replace its model, reset zoom or toggle it out of the render tree.
|
|
108
|
+
A parent's normal `v-model` echo retains the original pointer/keyboard source.
|
|
109
|
+
|
|
110
|
+
- Pointer down starts; movement resizes; up/cancel/lost capture/window blur ends.
|
|
111
|
+
- Keyboard changes start on the first supported keydown; repeats resize; keyup or
|
|
112
|
+
focus loss ends. Pointer cancellation keeps the last valid ratio.
|
|
113
|
+
- ResizeObserver changes emit `resize` with `source: 'container'` after initial
|
|
114
|
+
mount and whenever the host's dimensions change.
|
|
115
|
+
- Model, constraints, visibility and direction changes emit `resize` with
|
|
116
|
+
`source: 'programmatic'`; they do not fabricate an interactive start/end.
|
|
117
|
+
- Active gestures end before a direction/visibility/constraint change is reported.
|
|
118
|
+
Listeners, capture, observers and pending animation work are removed on unmount.
|
|
119
|
+
|
|
120
|
+
### Keyboard and accessibility
|
|
121
|
+
|
|
122
|
+
The focusable separator has a localized name, `role="separator"`, the actual
|
|
123
|
+
separator's `aria-orientation`, `aria-controls` referencing the first pane, and
|
|
124
|
+
percentage `aria-valuemin`, `aria-valuemax`, `aria-valuenow`. Hidden or fixed-bound
|
|
125
|
+
separators are neither visible nor keyboard reachable.
|
|
126
|
+
|
|
127
|
+
- Left/Right change a horizontal split; Up/Down change a vertical split.
|
|
128
|
+
- Arrow changes are 1 percentage point; Shift+Arrow changes 10.
|
|
129
|
+
- Home/End move to live lower/upper bounds.
|
|
130
|
+
- Enter or separator double-click resets to the **initial** ratio, within live
|
|
131
|
+
bounds, with a complete keyboard/pointer resize lifecycle. Programmatic model,
|
|
132
|
+
direction and container changes do not redefine this reset point.
|
|
133
|
+
- Pointer/touch uses a source-token target, expanded for coarse pointers. Pointer
|
|
134
|
+
capture, a pane-local drag shield and gesture-scoped selection prevention avoid
|
|
135
|
+
iframe/editor interception or accidental text selection without mutating body
|
|
136
|
+
styles or arbitrary framework controls.
|
|
137
|
+
|
|
138
|
+
### Theme and presentation
|
|
139
|
+
|
|
140
|
+
Resize geometry, colors, grip, active background and focus use the `resize.*` and
|
|
141
|
+
border/spacing source tokens. The default line is centered at 90% cross-axis
|
|
142
|
+
length (5% inset at either end), with a handle centered at 50%. Both orientations
|
|
143
|
+
keep that handle outside the panes' overflow and above positioned pane children;
|
|
144
|
+
only local stacking levels are used. Native rem token values respect reader text
|
|
145
|
+
scaling. Source `data-theme="dark"` changes apply live; no component theme or
|
|
146
|
+
locale singleton is introduced. Geometry does not animate; reduced-motion and
|
|
147
|
+
forced-colors behavior retain usable focus/contrast.
|
|
148
|
+
|
|
149
|
+
Complete active usage, code, examples, accessibility and changelog rules live at
|
|
150
|
+
`src/content/components/resizable-panel/` in beehexa.design and are distributed by
|
|
151
|
+
`@beehexa/design-rules` under stable `en/components/resizable-panel/*` IDs.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { type PropType } from 'vue';
|
|
2
|
+
export type ResizablePanelDirection = 'horizontal' | 'vertical';
|
|
3
|
+
export interface ResizablePanelResizeEvent {
|
|
4
|
+
ratio: number;
|
|
5
|
+
direction: ResizablePanelDirection;
|
|
6
|
+
first: {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
};
|
|
10
|
+
second: {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
source: 'pointer' | 'keyboard' | 'container' | 'programmatic';
|
|
15
|
+
}
|
|
16
|
+
/** A bounded two-pane layout. Children stay mounted when a pane is hidden. */
|
|
17
|
+
export declare const ResizablePanel: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
18
|
+
direction: {
|
|
19
|
+
type: PropType<ResizablePanelDirection>;
|
|
20
|
+
default: string;
|
|
21
|
+
};
|
|
22
|
+
ratio: {
|
|
23
|
+
type: NumberConstructor;
|
|
24
|
+
default: number;
|
|
25
|
+
};
|
|
26
|
+
min: {
|
|
27
|
+
type: NumberConstructor;
|
|
28
|
+
default: number;
|
|
29
|
+
};
|
|
30
|
+
max: {
|
|
31
|
+
type: NumberConstructor;
|
|
32
|
+
default: number;
|
|
33
|
+
};
|
|
34
|
+
preserveMinimum: {
|
|
35
|
+
type: BooleanConstructor;
|
|
36
|
+
default: boolean;
|
|
37
|
+
};
|
|
38
|
+
minRatio: {
|
|
39
|
+
type: NumberConstructor;
|
|
40
|
+
default: number;
|
|
41
|
+
};
|
|
42
|
+
maxRatio: {
|
|
43
|
+
type: NumberConstructor;
|
|
44
|
+
default: number;
|
|
45
|
+
};
|
|
46
|
+
secondVisible: {
|
|
47
|
+
type: BooleanConstructor;
|
|
48
|
+
default: boolean;
|
|
49
|
+
};
|
|
50
|
+
resizerSize: {
|
|
51
|
+
type: StringConstructor;
|
|
52
|
+
default: string;
|
|
53
|
+
};
|
|
54
|
+
separatorLabel: {
|
|
55
|
+
type: StringConstructor;
|
|
56
|
+
default: string;
|
|
57
|
+
};
|
|
58
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
59
|
+
[key: string]: any;
|
|
60
|
+
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
61
|
+
'update:ratio': (ratio: number) => boolean;
|
|
62
|
+
'resize-start': (_event: ResizablePanelResizeEvent) => true;
|
|
63
|
+
resize: (_event: ResizablePanelResizeEvent) => true;
|
|
64
|
+
'resize-end': (_event: ResizablePanelResizeEvent) => true;
|
|
65
|
+
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
66
|
+
direction: {
|
|
67
|
+
type: PropType<ResizablePanelDirection>;
|
|
68
|
+
default: string;
|
|
69
|
+
};
|
|
70
|
+
ratio: {
|
|
71
|
+
type: NumberConstructor;
|
|
72
|
+
default: number;
|
|
73
|
+
};
|
|
74
|
+
min: {
|
|
75
|
+
type: NumberConstructor;
|
|
76
|
+
default: number;
|
|
77
|
+
};
|
|
78
|
+
max: {
|
|
79
|
+
type: NumberConstructor;
|
|
80
|
+
default: number;
|
|
81
|
+
};
|
|
82
|
+
preserveMinimum: {
|
|
83
|
+
type: BooleanConstructor;
|
|
84
|
+
default: boolean;
|
|
85
|
+
};
|
|
86
|
+
minRatio: {
|
|
87
|
+
type: NumberConstructor;
|
|
88
|
+
default: number;
|
|
89
|
+
};
|
|
90
|
+
maxRatio: {
|
|
91
|
+
type: NumberConstructor;
|
|
92
|
+
default: number;
|
|
93
|
+
};
|
|
94
|
+
secondVisible: {
|
|
95
|
+
type: BooleanConstructor;
|
|
96
|
+
default: boolean;
|
|
97
|
+
};
|
|
98
|
+
resizerSize: {
|
|
99
|
+
type: StringConstructor;
|
|
100
|
+
default: string;
|
|
101
|
+
};
|
|
102
|
+
separatorLabel: {
|
|
103
|
+
type: StringConstructor;
|
|
104
|
+
default: string;
|
|
105
|
+
};
|
|
106
|
+
}>> & Readonly<{
|
|
107
|
+
"onResize-start"?: ((_event: ResizablePanelResizeEvent) => any) | undefined;
|
|
108
|
+
onResize?: ((_event: ResizablePanelResizeEvent) => any) | undefined;
|
|
109
|
+
"onResize-end"?: ((_event: ResizablePanelResizeEvent) => any) | undefined;
|
|
110
|
+
"onUpdate:ratio"?: ((ratio: number) => any) | undefined;
|
|
111
|
+
}>, {
|
|
112
|
+
direction: ResizablePanelDirection;
|
|
113
|
+
ratio: number;
|
|
114
|
+
min: number;
|
|
115
|
+
max: number;
|
|
116
|
+
preserveMinimum: boolean;
|
|
117
|
+
minRatio: number;
|
|
118
|
+
maxRatio: number;
|
|
119
|
+
secondVisible: boolean;
|
|
120
|
+
resizerSize: string;
|
|
121
|
+
separatorLabel: string;
|
|
122
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { computed, defineComponent, h, nextTick, onBeforeUnmount, onMounted, ref, useId, watch, } from 'vue';
|
|
2
|
+
import { clampRatio, panelBounds } from './geometry.js';
|
|
3
|
+
/** A bounded two-pane layout. Children stay mounted when a pane is hidden. */
|
|
4
|
+
export const ResizablePanel = defineComponent({
|
|
5
|
+
name: 'ResizablePanel',
|
|
6
|
+
props: {
|
|
7
|
+
direction: {
|
|
8
|
+
type: String,
|
|
9
|
+
default: 'horizontal',
|
|
10
|
+
},
|
|
11
|
+
ratio: { type: Number, default: 0.5 },
|
|
12
|
+
min: { type: Number, default: 100 },
|
|
13
|
+
max: { type: Number, default: Number.POSITIVE_INFINITY },
|
|
14
|
+
preserveMinimum: { type: Boolean, default: false },
|
|
15
|
+
minRatio: { type: Number, default: 0 },
|
|
16
|
+
maxRatio: { type: Number, default: 1 },
|
|
17
|
+
secondVisible: { type: Boolean, default: true },
|
|
18
|
+
resizerSize: { type: String, default: '90%' },
|
|
19
|
+
separatorLabel: { type: String, default: 'Resize panels' },
|
|
20
|
+
},
|
|
21
|
+
emits: {
|
|
22
|
+
'update:ratio': (ratio) => Number.isFinite(ratio),
|
|
23
|
+
'resize-start': (_event) => true,
|
|
24
|
+
resize: (_event) => true,
|
|
25
|
+
'resize-end': (_event) => true,
|
|
26
|
+
},
|
|
27
|
+
setup(props, { slots, emit }) {
|
|
28
|
+
const id = useId();
|
|
29
|
+
const firstId = `bh-panel-${id}-first`;
|
|
30
|
+
const secondId = `bh-panel-${id}-second`;
|
|
31
|
+
const root = ref();
|
|
32
|
+
const first = ref();
|
|
33
|
+
const second = ref();
|
|
34
|
+
const separator = ref();
|
|
35
|
+
const width = ref(0);
|
|
36
|
+
const height = ref(0);
|
|
37
|
+
const gutter = ref(0);
|
|
38
|
+
// Requested ratio is retained while hidden or temporarily constrained by a small host.
|
|
39
|
+
const initialRatio = clampRatio(props.ratio);
|
|
40
|
+
const requestedRatio = ref(initialRatio);
|
|
41
|
+
const dragging = ref(false);
|
|
42
|
+
const horizontal = computed(() => props.direction === 'horizontal');
|
|
43
|
+
const bounds = computed(() => panelBounds(horizontal.value ? width.value : height.value, gutter.value, props.min, props.max, props.minRatio, props.maxRatio));
|
|
44
|
+
const actualRatio = computed(() => clampRatio(requestedRatio.value, bounds.value.min, bounds.value.max));
|
|
45
|
+
const gutterHidden = computed(() => !props.secondVisible ||
|
|
46
|
+
(bounds.value.min === bounds.value.max && (bounds.value.min === 0 || bounds.value.min === 1)));
|
|
47
|
+
const disabled = computed(() => !props.secondVisible || bounds.value.min === bounds.value.max ||
|
|
48
|
+
(horizontal.value ? width.value : height.value) <= gutter.value);
|
|
49
|
+
const minimumAllocation = computed(() => {
|
|
50
|
+
if (!props.preserveMinimum)
|
|
51
|
+
return undefined;
|
|
52
|
+
const minimum = Math.max(0, Number.isFinite(props.min) ? props.min : 100);
|
|
53
|
+
return gutterHidden.value
|
|
54
|
+
? `${minimum}px`
|
|
55
|
+
: `calc(${minimum * 2}px + var(--bh-panel-hit-target))`;
|
|
56
|
+
});
|
|
57
|
+
let mounted = false;
|
|
58
|
+
let disposed = false;
|
|
59
|
+
let frame;
|
|
60
|
+
let observer;
|
|
61
|
+
let ownerWindow;
|
|
62
|
+
let activePointer;
|
|
63
|
+
let dragOffset = 0;
|
|
64
|
+
let dragDocument;
|
|
65
|
+
let keyboardActive = false;
|
|
66
|
+
let pending = [];
|
|
67
|
+
function queueResize(source, phase = 'resize') {
|
|
68
|
+
if (!mounted || disposed)
|
|
69
|
+
return;
|
|
70
|
+
// Keep lifecycle order while coalescing repeated moves/observer notifications.
|
|
71
|
+
const previous = pending[pending.length - 1];
|
|
72
|
+
if (phase === 'resize' && previous?.phase === 'resize') {
|
|
73
|
+
if (source !== 'container')
|
|
74
|
+
previous.source = source;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
pending.push({ phase, source });
|
|
78
|
+
}
|
|
79
|
+
if (frame !== undefined)
|
|
80
|
+
return;
|
|
81
|
+
void nextTick(() => {
|
|
82
|
+
if (disposed || frame !== undefined || !ownerWindow)
|
|
83
|
+
return;
|
|
84
|
+
frame = ownerWindow.requestAnimationFrame(() => {
|
|
85
|
+
frame = undefined;
|
|
86
|
+
if (!first.value || !second.value || disposed)
|
|
87
|
+
return;
|
|
88
|
+
const firstRect = first.value.getBoundingClientRect();
|
|
89
|
+
const secondRect = second.value.getBoundingClientRect();
|
|
90
|
+
const measured = {
|
|
91
|
+
ratio: actualRatio.value,
|
|
92
|
+
direction: props.direction,
|
|
93
|
+
first: { width: firstRect.width, height: firstRect.height },
|
|
94
|
+
second: { width: secondRect.width, height: secondRect.height },
|
|
95
|
+
};
|
|
96
|
+
const events = pending;
|
|
97
|
+
pending = [];
|
|
98
|
+
for (const event of events) {
|
|
99
|
+
const payload = { ...measured, source: event.source };
|
|
100
|
+
switch (event.phase) {
|
|
101
|
+
case 'resize-start':
|
|
102
|
+
emit('resize-start', payload);
|
|
103
|
+
break;
|
|
104
|
+
case 'resize':
|
|
105
|
+
emit('resize', payload);
|
|
106
|
+
break;
|
|
107
|
+
case 'resize-end':
|
|
108
|
+
emit('resize-end', payload);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function measureContainer() {
|
|
116
|
+
const element = root.value;
|
|
117
|
+
if (!element)
|
|
118
|
+
return;
|
|
119
|
+
const nextWidth = element.clientWidth;
|
|
120
|
+
const nextHeight = element.clientHeight;
|
|
121
|
+
const nextGutter = separator.value
|
|
122
|
+
? horizontal.value ? separator.value.offsetWidth : separator.value.offsetHeight
|
|
123
|
+
: 0;
|
|
124
|
+
if (width.value !== nextWidth || height.value !== nextHeight || gutter.value !== nextGutter) {
|
|
125
|
+
width.value = nextWidth;
|
|
126
|
+
height.value = nextHeight;
|
|
127
|
+
gutter.value = nextGutter;
|
|
128
|
+
queueResize('container');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function setRatio(value, source) {
|
|
132
|
+
const next = clampRatio(value, bounds.value.min, bounds.value.max);
|
|
133
|
+
if (next !== requestedRatio.value) {
|
|
134
|
+
requestedRatio.value = next;
|
|
135
|
+
emit('update:ratio', next);
|
|
136
|
+
queueResize(source);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function preventSelection(event) {
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
}
|
|
142
|
+
function finishPointer() {
|
|
143
|
+
if (activePointer === undefined)
|
|
144
|
+
return;
|
|
145
|
+
const pointer = activePointer;
|
|
146
|
+
activePointer = undefined;
|
|
147
|
+
dragging.value = false;
|
|
148
|
+
dragDocument?.removeEventListener('pointermove', movePointer, true);
|
|
149
|
+
dragDocument?.removeEventListener('pointerup', endPointer, true);
|
|
150
|
+
dragDocument?.removeEventListener('pointercancel', endPointer, true);
|
|
151
|
+
dragDocument?.removeEventListener('selectstart', preventSelection, true);
|
|
152
|
+
dragDocument = undefined;
|
|
153
|
+
ownerWindow?.removeEventListener('blur', finishPointer);
|
|
154
|
+
if (separator.value?.hasPointerCapture(pointer))
|
|
155
|
+
separator.value.releasePointerCapture(pointer);
|
|
156
|
+
queueResize('pointer', 'resize-end');
|
|
157
|
+
}
|
|
158
|
+
function ratioAtPointer(event) {
|
|
159
|
+
if (!root.value)
|
|
160
|
+
return undefined;
|
|
161
|
+
const rect = root.value.getBoundingClientRect();
|
|
162
|
+
const size = horizontal.value ? rect.width : rect.height;
|
|
163
|
+
const layoutSize = horizontal.value ? width.value : height.value;
|
|
164
|
+
const visibleGutter = layoutSize > 0 ? gutter.value * size / layoutSize : 0;
|
|
165
|
+
const available = size - visibleGutter;
|
|
166
|
+
if (available <= 0)
|
|
167
|
+
return undefined;
|
|
168
|
+
const position = horizontal.value ? event.clientX - rect.left : event.clientY - rect.top;
|
|
169
|
+
return (position - visibleGutter / 2) / available;
|
|
170
|
+
}
|
|
171
|
+
function movePointer(event) {
|
|
172
|
+
if (event.pointerId !== activePointer)
|
|
173
|
+
return;
|
|
174
|
+
const ratio = ratioAtPointer(event);
|
|
175
|
+
if (ratio === undefined)
|
|
176
|
+
return;
|
|
177
|
+
event.preventDefault();
|
|
178
|
+
setRatio(ratio - dragOffset, 'pointer');
|
|
179
|
+
}
|
|
180
|
+
function endPointer(event) {
|
|
181
|
+
if (event.pointerId !== activePointer)
|
|
182
|
+
return;
|
|
183
|
+
if (event.type === 'pointerup')
|
|
184
|
+
movePointer(event);
|
|
185
|
+
finishPointer();
|
|
186
|
+
}
|
|
187
|
+
function beginPointer(event) {
|
|
188
|
+
if (disabled.value || activePointer !== undefined || event.button !== 0 || !root.value)
|
|
189
|
+
return;
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
finishKeyboard();
|
|
192
|
+
measureContainer();
|
|
193
|
+
const ratio = ratioAtPointer(event);
|
|
194
|
+
if (ratio === undefined || disabled.value)
|
|
195
|
+
return;
|
|
196
|
+
dragOffset = ratio - actualRatio.value;
|
|
197
|
+
activePointer = event.pointerId;
|
|
198
|
+
dragging.value = true;
|
|
199
|
+
separator.value?.focus({ preventScroll: true });
|
|
200
|
+
separator.value?.setPointerCapture(event.pointerId);
|
|
201
|
+
dragDocument = root.value.ownerDocument;
|
|
202
|
+
dragDocument.addEventListener('pointermove', movePointer, { capture: true, passive: false });
|
|
203
|
+
dragDocument.addEventListener('pointerup', endPointer, true);
|
|
204
|
+
dragDocument.addEventListener('pointercancel', endPointer, true);
|
|
205
|
+
dragDocument.addEventListener('selectstart', preventSelection, true);
|
|
206
|
+
ownerWindow?.addEventListener('blur', finishPointer);
|
|
207
|
+
queueResize('pointer', 'resize-start');
|
|
208
|
+
}
|
|
209
|
+
function finishKeyboard() {
|
|
210
|
+
if (!keyboardActive)
|
|
211
|
+
return;
|
|
212
|
+
keyboardActive = false;
|
|
213
|
+
queueResize('keyboard', 'resize-end');
|
|
214
|
+
}
|
|
215
|
+
function resetRatio(source) {
|
|
216
|
+
if (disabled.value || activePointer !== undefined)
|
|
217
|
+
return;
|
|
218
|
+
finishKeyboard();
|
|
219
|
+
queueResize(source, 'resize-start');
|
|
220
|
+
setRatio(initialRatio, source);
|
|
221
|
+
queueResize(source, 'resize-end');
|
|
222
|
+
}
|
|
223
|
+
function keydown(event) {
|
|
224
|
+
if (disabled.value || activePointer !== undefined || event.altKey || event.ctrlKey || event.metaKey)
|
|
225
|
+
return;
|
|
226
|
+
if (event.key === 'Enter') {
|
|
227
|
+
event.preventDefault();
|
|
228
|
+
if (!event.repeat)
|
|
229
|
+
resetRatio('keyboard');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const decrease = horizontal.value ? 'ArrowLeft' : 'ArrowUp';
|
|
233
|
+
const increase = horizontal.value ? 'ArrowRight' : 'ArrowDown';
|
|
234
|
+
if (![decrease, increase, 'Home', 'End'].includes(event.key))
|
|
235
|
+
return;
|
|
236
|
+
event.preventDefault();
|
|
237
|
+
if (!keyboardActive) {
|
|
238
|
+
keyboardActive = true;
|
|
239
|
+
queueResize('keyboard', 'resize-start');
|
|
240
|
+
}
|
|
241
|
+
const step = event.shiftKey ? 0.1 : 0.01;
|
|
242
|
+
const value = event.key === 'Home' ? bounds.value.min
|
|
243
|
+
: event.key === 'End' ? bounds.value.max
|
|
244
|
+
: actualRatio.value + (event.key === increase ? step : -step);
|
|
245
|
+
setRatio(value, 'keyboard');
|
|
246
|
+
}
|
|
247
|
+
function keyup(event) {
|
|
248
|
+
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key))
|
|
249
|
+
finishKeyboard();
|
|
250
|
+
}
|
|
251
|
+
watch(() => props.ratio, value => {
|
|
252
|
+
const next = clampRatio(value);
|
|
253
|
+
// A parent's v-model echo must not relabel a pointer/keyboard resize.
|
|
254
|
+
if (next === requestedRatio.value)
|
|
255
|
+
return;
|
|
256
|
+
requestedRatio.value = next;
|
|
257
|
+
queueResize('programmatic');
|
|
258
|
+
});
|
|
259
|
+
watch(disabled, value => {
|
|
260
|
+
if (value) {
|
|
261
|
+
finishPointer();
|
|
262
|
+
finishKeyboard();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
watch(() => [props.direction, props.min, props.max, props.minRatio, props.maxRatio, props.secondVisible, props.preserveMinimum], () => {
|
|
266
|
+
finishPointer();
|
|
267
|
+
finishKeyboard();
|
|
268
|
+
queueResize('programmatic');
|
|
269
|
+
});
|
|
270
|
+
onMounted(() => {
|
|
271
|
+
mounted = true;
|
|
272
|
+
ownerWindow = root.value?.ownerDocument.defaultView ?? undefined;
|
|
273
|
+
measureContainer();
|
|
274
|
+
const Observer = typeof ResizeObserver === 'undefined' ? undefined : ResizeObserver;
|
|
275
|
+
if (Observer && root.value) {
|
|
276
|
+
observer = new Observer(measureContainer);
|
|
277
|
+
observer.observe(root.value);
|
|
278
|
+
if (separator.value)
|
|
279
|
+
observer.observe(separator.value);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
ownerWindow?.addEventListener('resize', measureContainer);
|
|
283
|
+
}
|
|
284
|
+
queueResize('container');
|
|
285
|
+
});
|
|
286
|
+
onBeforeUnmount(() => {
|
|
287
|
+
disposed = true;
|
|
288
|
+
finishPointer();
|
|
289
|
+
finishKeyboard();
|
|
290
|
+
observer?.disconnect();
|
|
291
|
+
ownerWindow?.removeEventListener('resize', measureContainer);
|
|
292
|
+
if (frame !== undefined)
|
|
293
|
+
ownerWindow?.cancelAnimationFrame(frame);
|
|
294
|
+
pending = [];
|
|
295
|
+
});
|
|
296
|
+
return () => h('div', {
|
|
297
|
+
ref: root,
|
|
298
|
+
class: ['bh-resizable-panel', `bh-resizable-panel--${props.direction}`],
|
|
299
|
+
'data-resizing': dragging.value ? 'true' : undefined,
|
|
300
|
+
style: {
|
|
301
|
+
'--bh-panel-resizer-size': props.resizerSize,
|
|
302
|
+
'--bh-panel-gutter': gutterHidden.value ? '0px' : undefined,
|
|
303
|
+
minWidth: horizontal.value ? minimumAllocation.value : undefined,
|
|
304
|
+
minHeight: horizontal.value ? undefined : minimumAllocation.value,
|
|
305
|
+
},
|
|
306
|
+
}, [
|
|
307
|
+
h('div', {
|
|
308
|
+
ref: first,
|
|
309
|
+
id: firstId,
|
|
310
|
+
class: 'bh-resizable-panel__pane bh-resizable-panel__pane--first',
|
|
311
|
+
inert: props.secondVisible && actualRatio.value === 0,
|
|
312
|
+
'aria-hidden': props.secondVisible && actualRatio.value === 0 ? 'true' : undefined,
|
|
313
|
+
style: {
|
|
314
|
+
flexBasis: props.secondVisible
|
|
315
|
+
? `calc((100% - var(--bh-panel-gutter)) * ${actualRatio.value})`
|
|
316
|
+
: '100%',
|
|
317
|
+
},
|
|
318
|
+
}, slots.first?.()),
|
|
319
|
+
h('div', {
|
|
320
|
+
ref: separator,
|
|
321
|
+
class: 'bh-resizable-panel__separator nokey',
|
|
322
|
+
role: 'separator',
|
|
323
|
+
tabindex: disabled.value ? -1 : 0,
|
|
324
|
+
'aria-label': props.separatorLabel,
|
|
325
|
+
'aria-orientation': horizontal.value ? 'vertical' : 'horizontal',
|
|
326
|
+
'aria-controls': firstId,
|
|
327
|
+
'aria-valuemin': Math.round(bounds.value.min * 100),
|
|
328
|
+
'aria-valuemax': Math.round(bounds.value.max * 100),
|
|
329
|
+
'aria-valuenow': Math.round(actualRatio.value * 100),
|
|
330
|
+
'aria-disabled': disabled.value ? 'true' : undefined,
|
|
331
|
+
style: { display: gutterHidden.value ? 'none' : undefined },
|
|
332
|
+
onPointerdown: beginPointer,
|
|
333
|
+
onDblclick: (event) => {
|
|
334
|
+
if (disabled.value)
|
|
335
|
+
return;
|
|
336
|
+
event.preventDefault();
|
|
337
|
+
event.stopPropagation();
|
|
338
|
+
resetRatio('pointer');
|
|
339
|
+
},
|
|
340
|
+
onLostpointercapture: finishPointer,
|
|
341
|
+
onKeydown: keydown,
|
|
342
|
+
onKeyup: keyup,
|
|
343
|
+
onBlur: finishKeyboard,
|
|
344
|
+
}, [
|
|
345
|
+
h('span', { class: 'bh-resizable-panel__line', 'aria-hidden': 'true' }),
|
|
346
|
+
h('span', { class: 'bh-resizable-panel__handle', 'aria-hidden': 'true' }),
|
|
347
|
+
]),
|
|
348
|
+
h('div', {
|
|
349
|
+
ref: second,
|
|
350
|
+
id: secondId,
|
|
351
|
+
class: 'bh-resizable-panel__pane bh-resizable-panel__pane--second',
|
|
352
|
+
style: { display: props.secondVisible ? undefined : 'none' },
|
|
353
|
+
inert: !props.secondVisible || actualRatio.value === 1,
|
|
354
|
+
'aria-hidden': !props.secondVisible || actualRatio.value === 1 ? 'true' : undefined,
|
|
355
|
+
}, slots.second?.()),
|
|
356
|
+
dragging.value ? h('div', { class: 'bh-resizable-panel__drag-shield', 'aria-hidden': 'true' }) : null,
|
|
357
|
+
]);
|
|
358
|
+
},
|
|
359
|
+
});
|
package/dist/css.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface PanelBounds {
|
|
2
|
+
min: number;
|
|
3
|
+
max: number;
|
|
4
|
+
}
|
|
5
|
+
export declare function clampRatio(value: number, min?: number, max?: number): number;
|
|
6
|
+
/** Pixel constraints apply to pane space after reserving the divider gutter.
|
|
7
|
+
* Minima yield symmetrically in undersized hosts; explicit maximums win conflicts.
|
|
8
|
+
*/
|
|
9
|
+
export declare function panelBounds(size: number, gutter: number, min: number, max: number, minRatio: number, maxRatio: number): PanelBounds;
|
package/dist/geometry.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function clampRatio(value, min = 0, max = 1) {
|
|
2
|
+
return Math.min(max, Math.max(min, Number.isFinite(value) ? value : 0.5));
|
|
3
|
+
}
|
|
4
|
+
/** Pixel constraints apply to pane space after reserving the divider gutter.
|
|
5
|
+
* Minima yield symmetrically in undersized hosts; explicit maximums win conflicts.
|
|
6
|
+
*/
|
|
7
|
+
export function panelBounds(size, gutter, min, max, minRatio, maxRatio) {
|
|
8
|
+
const available = Math.max(0, size - gutter);
|
|
9
|
+
const ratioMax = clampRatio(maxRatio);
|
|
10
|
+
const ratioMin = Math.min(ratioMax, clampRatio(minRatio));
|
|
11
|
+
if (available <= 0)
|
|
12
|
+
return { min: ratioMin, max: ratioMax };
|
|
13
|
+
const minimum = Math.min(Math.max(0, Number.isFinite(min) ? min : 100), available / 2) / available;
|
|
14
|
+
const maximum = Number.isFinite(max) ? Math.max(0, max) / available : 1;
|
|
15
|
+
const upper = Math.min(ratioMax, maximum, 1);
|
|
16
|
+
const lower = Math.min(upper, Math.max(ratioMin, minimum));
|
|
17
|
+
return { min: lower, max: Math.max(lower, Math.min(upper, 1 - minimum)) };
|
|
18
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ResizablePanel } from './ResizablePanel.js';
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
@import '@beehexa/design-tokens/css';
|
|
2
|
+
|
|
3
|
+
.bh-resizable-panel {
|
|
4
|
+
--bh-panel-hit-target: var(--bh-resize-target);
|
|
5
|
+
--bh-panel-gutter: min(100%, var(--bh-panel-hit-target));
|
|
6
|
+
position: relative;
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: row;
|
|
9
|
+
align-items: stretch;
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 100%;
|
|
13
|
+
min-width: 0;
|
|
14
|
+
min-height: 0;
|
|
15
|
+
max-width: 100%;
|
|
16
|
+
max-height: 100%;
|
|
17
|
+
gap: 0;
|
|
18
|
+
overflow: visible;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.bh-resizable-panel--vertical {
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.bh-resizable-panel__pane {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
position: relative;
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
min-width: 0;
|
|
31
|
+
min-height: 0;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.bh-resizable-panel__pane--first {
|
|
36
|
+
flex: 0 0 auto;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.bh-resizable-panel__pane--second {
|
|
40
|
+
flex: 1 1 0%;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.bh-resizable-panel__separator {
|
|
44
|
+
position: relative;
|
|
45
|
+
flex: 0 0 auto;
|
|
46
|
+
min-width: 0;
|
|
47
|
+
min-height: 0;
|
|
48
|
+
z-index: 1;
|
|
49
|
+
box-sizing: border-box;
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: center;
|
|
53
|
+
padding: 0;
|
|
54
|
+
border: 0;
|
|
55
|
+
outline: none;
|
|
56
|
+
overflow: visible;
|
|
57
|
+
touch-action: none;
|
|
58
|
+
user-select: none;
|
|
59
|
+
-webkit-user-select: none;
|
|
60
|
+
color: var(--bh-resize-grip);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.bh-resizable-panel--horizontal > .bh-resizable-panel__separator {
|
|
64
|
+
width: var(--bh-panel-gutter);
|
|
65
|
+
cursor: col-resize;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.bh-resizable-panel--vertical > .bh-resizable-panel__separator {
|
|
69
|
+
height: var(--bh-panel-gutter);
|
|
70
|
+
cursor: row-resize;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.bh-resizable-panel__separator[aria-disabled='true'] {
|
|
74
|
+
cursor: default;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.bh-resizable-panel__line,
|
|
78
|
+
.bh-resizable-panel__handle {
|
|
79
|
+
position: absolute;
|
|
80
|
+
top: 50%;
|
|
81
|
+
left: 50%;
|
|
82
|
+
box-sizing: border-box;
|
|
83
|
+
transform: translate(-50%, -50%);
|
|
84
|
+
pointer-events: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.bh-resizable-panel__line {
|
|
88
|
+
background: var(--bh-resize-color);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.bh-resizable-panel--horizontal > .bh-resizable-panel__separator > .bh-resizable-panel__line {
|
|
92
|
+
width: var(--bh-resize-divider);
|
|
93
|
+
height: clamp(0px, var(--bh-panel-resizer-size, 90%), 100%);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.bh-resizable-panel--vertical > .bh-resizable-panel__separator > .bh-resizable-panel__line {
|
|
97
|
+
width: clamp(0px, var(--bh-panel-resizer-size, 90%), 100%);
|
|
98
|
+
height: var(--bh-resize-divider);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.bh-resizable-panel__handle {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
width: var(--bh-resize-handle-width);
|
|
106
|
+
height: var(--bh-resize-handle-height);
|
|
107
|
+
border: var(--bh-border-width-default) solid var(--bh-resize-color);
|
|
108
|
+
border-radius: var(--bh-resize-handle-radius);
|
|
109
|
+
background: var(--bh-resize-handle-background);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.bh-resizable-panel__handle::after {
|
|
113
|
+
content: '';
|
|
114
|
+
width: var(--bh-resize-dot-size);
|
|
115
|
+
height: var(--bh-resize-dot-size);
|
|
116
|
+
border-radius: var(--bh-radius-full);
|
|
117
|
+
background: currentColor;
|
|
118
|
+
box-shadow:
|
|
119
|
+
0 calc(-1 * var(--bh-resize-dot-pitch)) 0 currentColor,
|
|
120
|
+
0 var(--bh-resize-dot-pitch) 0 currentColor;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.bh-resizable-panel--vertical > .bh-resizable-panel__separator > .bh-resizable-panel__handle {
|
|
124
|
+
width: var(--bh-resize-handle-height);
|
|
125
|
+
height: var(--bh-resize-handle-width);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.bh-resizable-panel--vertical > .bh-resizable-panel__separator > .bh-resizable-panel__handle::after {
|
|
129
|
+
transform: rotate(90deg);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.bh-resizable-panel__separator:not([aria-disabled='true']):is(:hover, :focus-visible) > .bh-resizable-panel__line,
|
|
133
|
+
.bh-resizable-panel[data-resizing='true'] > .bh-resizable-panel__separator > .bh-resizable-panel__line {
|
|
134
|
+
background: var(--bh-resize-active);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.bh-resizable-panel__separator:not([aria-disabled='true']):is(:hover, :focus-visible) > .bh-resizable-panel__handle,
|
|
138
|
+
.bh-resizable-panel[data-resizing='true'] > .bh-resizable-panel__separator > .bh-resizable-panel__handle {
|
|
139
|
+
color: var(--bh-resize-activeGrip);
|
|
140
|
+
background: var(--bh-resize-handle-activeBackground);
|
|
141
|
+
border-color: var(--bh-resize-active);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.bh-resizable-panel__separator:focus-visible > .bh-resizable-panel__handle {
|
|
145
|
+
outline: var(--bh-border-width-focused) solid var(--bh-resize-active);
|
|
146
|
+
outline-offset: var(--bh-border-width-focused);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* The physical gutter keeps the complete handle off pane content. At rest neither
|
|
150
|
+
root nor panes create stacking contexts: a nested fixed overlay must participate
|
|
151
|
+
in its actual overlay owner's stack, above this panel's local separator.
|
|
152
|
+
Only an active pointer gesture isolates panes beneath its local iframe shield. */
|
|
153
|
+
.bh-resizable-panel[data-resizing='true'] {
|
|
154
|
+
isolation: isolate;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.bh-resizable-panel[data-resizing='true'] > .bh-resizable-panel__pane {
|
|
158
|
+
z-index: 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Pointer capture and document listeners retain the gesture outside the host;
|
|
162
|
+
no permanent pane stack, global cursor, body style or high-z overlay. */
|
|
163
|
+
.bh-resizable-panel__drag-shield {
|
|
164
|
+
position: absolute;
|
|
165
|
+
inset: 0;
|
|
166
|
+
z-index: 2;
|
|
167
|
+
touch-action: none;
|
|
168
|
+
user-select: none;
|
|
169
|
+
-webkit-user-select: none;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.bh-resizable-panel--horizontal > .bh-resizable-panel__drag-shield {
|
|
173
|
+
cursor: col-resize;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.bh-resizable-panel--vertical > .bh-resizable-panel__drag-shield {
|
|
177
|
+
cursor: row-resize;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@media (pointer: coarse) {
|
|
181
|
+
.bh-resizable-panel {
|
|
182
|
+
--bh-panel-hit-target: var(--bh-resize-touchTarget);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Resize geometry never animates, including when reduced motion is requested. */
|
|
187
|
+
@media (prefers-reduced-motion: reduce) {
|
|
188
|
+
.bh-resizable-panel > .bh-resizable-panel__pane,
|
|
189
|
+
.bh-resizable-panel > .bh-resizable-panel__separator {
|
|
190
|
+
transition: none;
|
|
191
|
+
animation: none;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@media (forced-colors: active) {
|
|
196
|
+
.bh-resizable-panel__line {
|
|
197
|
+
background: CanvasText;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.bh-resizable-panel__handle {
|
|
201
|
+
color: CanvasText;
|
|
202
|
+
background: Canvas;
|
|
203
|
+
border-color: CanvasText;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.bh-resizable-panel__separator:focus-visible > .bh-resizable-panel__handle {
|
|
207
|
+
outline-color: Highlight;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/* Opt in on existing interactive renderer hosts, not on panes or static content.
|
|
212
|
+
The outer allocation can shrink; only its readable surface retains a minimum. */
|
|
213
|
+
.bh-renderer-frame,
|
|
214
|
+
.bh-renderer-viewport {
|
|
215
|
+
display: flex;
|
|
216
|
+
flex-direction: column;
|
|
217
|
+
flex: 1 1 0%;
|
|
218
|
+
box-sizing: border-box;
|
|
219
|
+
inline-size: 100%;
|
|
220
|
+
min-inline-size: 0;
|
|
221
|
+
min-block-size: 0;
|
|
222
|
+
overflow: auto;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* A usable outer interaction strip, not the readable SDK surface minimum.
|
|
226
|
+
Reserve a native toolbar row, one interaction row and their source gap when
|
|
227
|
+
surrounding page chrome would otherwise reduce the allocation to zero. */
|
|
228
|
+
:root {
|
|
229
|
+
--bh-renderer-access-minimum-block-size: calc(var(--bh-size-control-default) * 2 + var(--bh-space-100));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.bh-renderer-access {
|
|
233
|
+
min-block-size: var(--bh-renderer-access-minimum-block-size);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@media (pointer: coarse) {
|
|
237
|
+
:root {
|
|
238
|
+
--bh-renderer-access-minimum-block-size: calc(var(--bh-size-target-touch) * 2 + var(--bh-space-100));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.bh-renderer-viewport__surface {
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
/* A definite readable flex basis also resolves SDK percentage-height children
|
|
246
|
+
in Firefox. It still grows with the allocation, never caps the renderer. */
|
|
247
|
+
flex: 1 0 var(--bh-layout-renderer-minimum-block-size);
|
|
248
|
+
box-sizing: border-box;
|
|
249
|
+
min-inline-size: var(--bh-layout-renderer-minimum-inline-size);
|
|
250
|
+
min-block-size: var(--bh-layout-renderer-minimum-block-size);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Chrome remains outside the actual viewport. Below its readable minimum,
|
|
254
|
+
the existing frame owns the one access scroller across chrome and surface. */
|
|
255
|
+
.bh-renderer-frame > .bh-renderer-viewport {
|
|
256
|
+
flex: 1 0 auto;
|
|
257
|
+
block-size: auto;
|
|
258
|
+
overflow: visible;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* A retained renderer inside an already scrolling document delegates access to
|
|
262
|
+
that document; it must not create another clipped, independently scrolling box. */
|
|
263
|
+
.bh-renderer-frame.bh-renderer-frame--document:not(:fullscreen):not([data-bh-overlay-owner]) {
|
|
264
|
+
flex: 1 0 auto;
|
|
265
|
+
block-size: auto;
|
|
266
|
+
overflow: visible;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.bh-renderer-frame > .bh-renderer-viewport > .bh-renderer-viewport__surface,
|
|
270
|
+
.bh-renderer-frame > .bh-renderer-viewport__surface {
|
|
271
|
+
contain: size;
|
|
272
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beehexa/vue-components",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "2611.5.1",
|
|
4
|
+
"description": "Accessible, source-token-backed Vue components independent of a UI framework",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/beehexacorp/beehexa.design.git",
|
|
8
|
+
"directory": "packages/vue-components"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"sideEffects": [
|
|
18
|
+
"**/*.css"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./css": {
|
|
26
|
+
"types": "./dist/css.d.ts",
|
|
27
|
+
"default": "./dist/styles.css"
|
|
28
|
+
},
|
|
29
|
+
"./package.json": "./package.json"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@beehexa/design-tokens": "2611.5.1"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"vue": "^3.5.0"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "node ../../scripts/build-runtime-packages.mjs vue-components",
|
|
42
|
+
"check": "node ../../scripts/build-runtime-packages.mjs vue-components --check",
|
|
43
|
+
"prepack": "npm run build"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=22.22.0"
|
|
47
|
+
}
|
|
48
|
+
}
|