basefn 1.9.1 → 1.11.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/package.json +4 -4
- package/rescript.json +1 -1
- package/src/Basefn.res +5 -0
- package/src/Basefn.res.mjs +4 -0
- package/src/Basefn__Responsive.res.mjs +5 -5
- package/src/Basefn__Utils.res.mjs +2 -2
- package/src/Demo.res +59 -59
- package/src/Demo.res.mjs +630 -628
- package/src/Example.res +3 -3
- package/src/Example.res.mjs +48 -47
- package/src/components/Basefn__Accordion.res +5 -5
- package/src/components/Basefn__Accordion.res.mjs +25 -26
- package/src/components/Basefn__Alert.res +4 -4
- package/src/components/Basefn__Alert.res.mjs +20 -18
- package/src/components/Basefn__AlertDialog.res +5 -5
- package/src/components/Basefn__AlertDialog.res.mjs +23 -21
- package/src/components/Basefn__AppLayout.res +3 -3
- package/src/components/Basefn__AppLayout.res.mjs +25 -24
- package/src/components/Basefn__AspectRatio.res +1 -1
- package/src/components/Basefn__AspectRatio.res.mjs +2 -2
- package/src/components/Basefn__Avatar.res.mjs +3 -3
- package/src/components/Basefn__Badge.res +1 -1
- package/src/components/Basefn__Badge.res.mjs +2 -2
- package/src/components/Basefn__Breadcrumb.res +5 -5
- package/src/components/Basefn__Breadcrumb.res.mjs +14 -14
- package/src/components/Basefn__Button.res +1 -1
- package/src/components/Basefn__Button.res.mjs +10 -9
- package/src/components/Basefn__ButtonGroup.res +1 -1
- package/src/components/Basefn__ButtonGroup.res.mjs +2 -2
- package/src/components/Basefn__Card.res +3 -3
- package/src/components/Basefn__Card.res.mjs +11 -11
- package/src/components/Basefn__Checkbox.res +1 -1
- package/src/components/Basefn__Checkbox.res.mjs +7 -7
- package/src/components/Basefn__ContextMenu.res +4 -4
- package/src/components/Basefn__ContextMenu.res.mjs +22 -20
- package/src/components/Basefn__Drawer.res +5 -5
- package/src/components/Basefn__Drawer.res.mjs +22 -20
- package/src/components/Basefn__Dropdown.res +4 -4
- package/src/components/Basefn__Dropdown.res.mjs +20 -18
- package/src/components/Basefn__Grid.res.mjs +15 -14
- package/src/components/Basefn__HoverCard.res +3 -3
- package/src/components/Basefn__HoverCard.res.mjs +14 -12
- package/src/components/Basefn__Icon.res.mjs +8 -7
- package/src/components/Basefn__Input.res.mjs +4 -4
- package/src/components/Basefn__Kbd.res +2 -2
- package/src/components/Basefn__Kbd.res.mjs +5 -5
- package/src/components/Basefn__Label.res +1 -1
- package/src/components/Basefn__Label.res.mjs +4 -4
- package/src/components/Basefn__Modal.res +5 -5
- package/src/components/Basefn__Modal.res.mjs +20 -18
- package/src/components/Basefn__Popover.res +3 -3
- package/src/components/Basefn__Popover.res.mjs +15 -13
- package/src/components/Basefn__Progress.res +2 -2
- package/src/components/Basefn__Progress.res.mjs +16 -14
- package/src/components/Basefn__Radio.res +1 -1
- package/src/components/Basefn__Radio.res.mjs +7 -7
- package/src/components/Basefn__Resizable.css +156 -0
- package/src/components/Basefn__Resizable.res +318 -0
- package/src/components/Basefn__Resizable.res.mjs +343 -0
- package/src/components/Basefn__ScrollArea.res +1 -1
- package/src/components/Basefn__ScrollArea.res.mjs +2 -2
- package/src/components/Basefn__Select.res +2 -2
- package/src/components/Basefn__Select.res.mjs +7 -6
- package/src/components/Basefn__Separator.res +1 -1
- package/src/components/Basefn__Separator.res.mjs +9 -9
- package/src/components/Basefn__Sidebar.res +7 -7
- package/src/components/Basefn__Sidebar.res.mjs +28 -24
- package/src/components/Basefn__Skeleton.res.mjs +2 -2
- package/src/components/Basefn__Slider.res +4 -4
- package/src/components/Basefn__Slider.res.mjs +21 -20
- package/src/components/Basefn__Spinner.res +1 -1
- package/src/components/Basefn__Spinner.res.mjs +8 -8
- package/src/components/Basefn__Spotlight.res +22 -14
- package/src/components/Basefn__Spotlight.res.mjs +79 -65
- package/src/components/Basefn__Stepper.res +6 -6
- package/src/components/Basefn__Stepper.res.mjs +20 -24
- package/src/components/Basefn__Switch.res +1 -1
- package/src/components/Basefn__Switch.res.mjs +16 -14
- package/src/components/Basefn__Tabs.res +4 -4
- package/src/components/Basefn__Tabs.res.mjs +19 -17
- package/src/components/Basefn__Textarea.res.mjs +4 -4
- package/src/components/Basefn__ThemeToggle.res +1 -1
- package/src/components/Basefn__ThemeToggle.res.mjs +13 -10
- package/src/components/Basefn__Timeline.res +6 -6
- package/src/components/Basefn__Timeline.res.mjs +21 -21
- package/src/components/Basefn__Toast.res +4 -4
- package/src/components/Basefn__Toast.res.mjs +23 -20
- package/src/components/Basefn__Toggle.res +1 -1
- package/src/components/Basefn__Toggle.res.mjs +8 -7
- package/src/components/Basefn__ToggleGroup.res +2 -2
- package/src/components/Basefn__ToggleGroup.res.mjs +12 -10
- package/src/components/Basefn__Tooltip.res +3 -3
- package/src/components/Basefn__Tooltip.res.mjs +14 -12
- package/src/components/Basefn__Topbar.res +7 -7
- package/src/components/Basefn__Topbar.res.mjs +22 -22
- package/src/components/Basefn__Typography.res +2 -2
- package/src/components/Basefn__Typography.res.mjs +13 -13
- package/src/styles/Basefn__Theme.res.mjs +5 -5
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as Xote from "xote/src/
|
|
4
|
-
import * as
|
|
3
|
+
import * as Node$Xote from "xote/src/Node.res.mjs";
|
|
4
|
+
import * as Signal$Xote from "xote/src/Signal.res.mjs";
|
|
5
|
+
import * as XoteJSX$Xote from "xote/src/XoteJSX.res.mjs";
|
|
6
|
+
import * as Computed$Xote from "xote/src/Computed.res.mjs";
|
|
5
7
|
|
|
6
8
|
import './Basefn__Popover.css'
|
|
7
9
|
;
|
|
@@ -42,14 +44,14 @@ function Basefn__Popover(props) {
|
|
|
42
44
|
let align = __align !== undefined ? __align : "Center";
|
|
43
45
|
let closeOnClickOutside = __closeOnClickOutside !== undefined ? __closeOnClickOutside : true;
|
|
44
46
|
let handleTriggerClick = param => {
|
|
45
|
-
let newValue = !Xote.
|
|
46
|
-
Xote.
|
|
47
|
+
let newValue = !Signal$Xote.get(isOpen);
|
|
48
|
+
Signal$Xote.set(isOpen, newValue);
|
|
47
49
|
if (onOpenChange !== undefined) {
|
|
48
50
|
return onOpenChange(newValue);
|
|
49
51
|
}
|
|
50
52
|
};
|
|
51
53
|
let handleClose = () => {
|
|
52
|
-
Xote.
|
|
54
|
+
Signal$Xote.set(isOpen, false);
|
|
53
55
|
if (onOpenChange !== undefined) {
|
|
54
56
|
return onOpenChange(false);
|
|
55
57
|
}
|
|
@@ -68,14 +70,14 @@ function Basefn__Popover(props) {
|
|
|
68
70
|
let alignClass = " basefn-popover__content--align-" + alignToString(align);
|
|
69
71
|
return "basefn-popover__content" + positionClass + alignClass;
|
|
70
72
|
};
|
|
71
|
-
let popoverContent = Xote.
|
|
72
|
-
if (Xote.
|
|
73
|
+
let popoverContent = Computed$Xote.make(() => {
|
|
74
|
+
if (Signal$Xote.get(isOpen)) {
|
|
73
75
|
return [
|
|
74
|
-
|
|
76
|
+
XoteJSX$Xote.Elements.jsx("div", {
|
|
75
77
|
class: "basefn-popover__backdrop",
|
|
76
78
|
onClick: handleBackdropClick
|
|
77
79
|
}),
|
|
78
|
-
|
|
80
|
+
XoteJSX$Xote.Elements.jsx("div", {
|
|
79
81
|
class: getContentClassName(),
|
|
80
82
|
children: content
|
|
81
83
|
})
|
|
@@ -84,15 +86,15 @@ function Basefn__Popover(props) {
|
|
|
84
86
|
return [];
|
|
85
87
|
}
|
|
86
88
|
}, undefined);
|
|
87
|
-
return
|
|
89
|
+
return XoteJSX$Xote.Elements.jsxs("div", {
|
|
88
90
|
class: getPopoverClassName(),
|
|
89
|
-
children:
|
|
90
|
-
|
|
91
|
+
children: XoteJSX$Xote.array([
|
|
92
|
+
XoteJSX$Xote.Elements.jsx("div", {
|
|
91
93
|
class: "basefn-popover__trigger",
|
|
92
94
|
onClick: handleTriggerClick,
|
|
93
95
|
children: props.trigger
|
|
94
96
|
}),
|
|
95
|
-
Xote.
|
|
97
|
+
Node$Xote.signalFragment(popoverContent)
|
|
96
98
|
])
|
|
97
99
|
});
|
|
98
100
|
}
|
|
@@ -74,14 +74,14 @@ let make = (
|
|
|
74
74
|
{showLabel || label->Option.isSome
|
|
75
75
|
? <div class="basefn-progress__label">
|
|
76
76
|
<span>
|
|
77
|
-
{
|
|
77
|
+
{Node.text(
|
|
78
78
|
switch label {
|
|
79
79
|
| Some(labelText) => labelText
|
|
80
80
|
| None => "Progress"
|
|
81
81
|
},
|
|
82
82
|
)}
|
|
83
83
|
</span>
|
|
84
|
-
{!indeterminate ? {
|
|
84
|
+
{!indeterminate ? {Node.signalText(() => getPercentage() ++ "%")} : <> </>}
|
|
85
85
|
</div>
|
|
86
86
|
: <> </>}
|
|
87
87
|
</>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as Xote from "xote/src/
|
|
4
|
-
import * as
|
|
3
|
+
import * as Node$Xote from "xote/src/Node.res.mjs";
|
|
4
|
+
import * as Signal$Xote from "xote/src/Signal.res.mjs";
|
|
5
5
|
import * as Core__Option from "@rescript/core/src/Core__Option.res.mjs";
|
|
6
|
+
import * as XoteJSX$Xote from "xote/src/XoteJSX.res.mjs";
|
|
7
|
+
import * as Computed$Xote from "xote/src/Computed.res.mjs";
|
|
6
8
|
|
|
7
9
|
import './Basefn__Progress.css'
|
|
8
10
|
;
|
|
@@ -56,20 +58,20 @@ function Basefn__Progress(props) {
|
|
|
56
58
|
return "basefn-progress__bar " + variantClass + animatedClass;
|
|
57
59
|
};
|
|
58
60
|
let getPercentage = () => {
|
|
59
|
-
let currentValue = Xote.
|
|
61
|
+
let currentValue = Signal$Xote.get(value);
|
|
60
62
|
let percent = currentValue / max * 100.0;
|
|
61
63
|
let clamped = percent > 100.0 ? 100.0 : (
|
|
62
64
|
percent < 0.0 ? 0.0 : percent
|
|
63
65
|
);
|
|
64
66
|
return clamped.toString();
|
|
65
67
|
};
|
|
66
|
-
return
|
|
67
|
-
children:
|
|
68
|
-
|
|
68
|
+
return XoteJSX$Xote.jsxs(XoteJSX$Xote.jsxFragment, {
|
|
69
|
+
children: XoteJSX$Xote.array([
|
|
70
|
+
XoteJSX$Xote.Elements.jsx("div", {
|
|
69
71
|
class: getProgressClass(),
|
|
70
|
-
children:
|
|
72
|
+
children: XoteJSX$Xote.Elements.jsx("div", {
|
|
71
73
|
class: getBarClass(),
|
|
72
|
-
style: Xote.
|
|
74
|
+
style: Computed$Xote.make(() => {
|
|
73
75
|
if (indeterminate) {
|
|
74
76
|
return "width: 30%";
|
|
75
77
|
} else {
|
|
@@ -78,15 +80,15 @@ function Basefn__Progress(props) {
|
|
|
78
80
|
}, undefined)
|
|
79
81
|
})
|
|
80
82
|
}),
|
|
81
|
-
showLabel || Core__Option.isSome(label) ?
|
|
83
|
+
showLabel || Core__Option.isSome(label) ? XoteJSX$Xote.Elements.jsxs("div", {
|
|
82
84
|
class: "basefn-progress__label",
|
|
83
|
-
children:
|
|
84
|
-
|
|
85
|
-
children: Xote.
|
|
85
|
+
children: XoteJSX$Xote.array([
|
|
86
|
+
XoteJSX$Xote.Elements.jsx("span", {
|
|
87
|
+
children: Node$Xote.text(label !== undefined ? label : "Progress")
|
|
86
88
|
}),
|
|
87
|
-
indeterminate ?
|
|
89
|
+
indeterminate ? XoteJSX$Xote.jsx(XoteJSX$Xote.jsxFragment, {}) : Node$Xote.signalText(() => getPercentage() + "%")
|
|
88
90
|
])
|
|
89
|
-
}) :
|
|
91
|
+
}) : XoteJSX$Xote.jsx(XoteJSX$Xote.jsxFragment, {})
|
|
90
92
|
])
|
|
91
93
|
});
|
|
92
94
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as Xote from "xote/src/
|
|
4
|
-
import * as
|
|
3
|
+
import * as Node$Xote from "xote/src/Node.res.mjs";
|
|
4
|
+
import * as XoteJSX$Xote from "xote/src/XoteJSX.res.mjs";
|
|
5
5
|
|
|
6
6
|
import './Basefn__Radio.css'
|
|
7
7
|
;
|
|
@@ -10,10 +10,10 @@ function Basefn__Radio(props) {
|
|
|
10
10
|
let __disabled = props.disabled;
|
|
11
11
|
let disabled = __disabled !== undefined ? __disabled : false;
|
|
12
12
|
let base = "basefn-radio-wrapper";
|
|
13
|
-
return
|
|
13
|
+
return XoteJSX$Xote.Elements.jsxs("label", {
|
|
14
14
|
class: disabled ? base + " basefn-radio-wrapper--disabled" : base,
|
|
15
|
-
children:
|
|
16
|
-
|
|
15
|
+
children: XoteJSX$Xote.array([
|
|
16
|
+
XoteJSX$Xote.Elements.jsx("input", {
|
|
17
17
|
class: "basefn-radio-input",
|
|
18
18
|
type: "radio",
|
|
19
19
|
name: props.name,
|
|
@@ -22,9 +22,9 @@ function Basefn__Radio(props) {
|
|
|
22
22
|
checked: props.checked,
|
|
23
23
|
onChange: props.onChange
|
|
24
24
|
}),
|
|
25
|
-
|
|
25
|
+
XoteJSX$Xote.Elements.jsx("span", {
|
|
26
26
|
class: "basefn-radio-label",
|
|
27
|
-
children: Xote.
|
|
27
|
+
children: Node$Xote.text(props.label)
|
|
28
28
|
})
|
|
29
29
|
])
|
|
30
30
|
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
@import "../styles/variables.css";
|
|
2
|
+
|
|
3
|
+
/* Panel Group Container */
|
|
4
|
+
.basefn-resizable {
|
|
5
|
+
display: flex;
|
|
6
|
+
width: 100%;
|
|
7
|
+
height: 100%;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.basefn-resizable--horizontal {
|
|
12
|
+
flex-direction: row;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.basefn-resizable--vertical {
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Panel */
|
|
20
|
+
.basefn-resizable__panel {
|
|
21
|
+
overflow: auto;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.basefn-resizable__panel--dragging {
|
|
25
|
+
pointer-events: none;
|
|
26
|
+
user-select: none;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Handle */
|
|
30
|
+
.basefn-resizable__handle {
|
|
31
|
+
flex-shrink: 0;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
position: relative;
|
|
36
|
+
outline: none;
|
|
37
|
+
background: transparent;
|
|
38
|
+
border: 0;
|
|
39
|
+
padding: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.basefn-resizable__handle--horizontal {
|
|
43
|
+
width: 8px;
|
|
44
|
+
cursor: col-resize;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.basefn-resizable__handle--vertical {
|
|
48
|
+
height: 8px;
|
|
49
|
+
cursor: row-resize;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Handle visible line */
|
|
53
|
+
.basefn-resizable__handle::after {
|
|
54
|
+
content: "";
|
|
55
|
+
position: absolute;
|
|
56
|
+
background-color: var(--basefn-border-primary);
|
|
57
|
+
transition: background-color var(--basefn-transition-fast);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.basefn-resizable__handle--horizontal::after {
|
|
61
|
+
width: 1px;
|
|
62
|
+
height: 100%;
|
|
63
|
+
left: 50%;
|
|
64
|
+
transform: translateX(-50%);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.basefn-resizable__handle--vertical::after {
|
|
68
|
+
height: 1px;
|
|
69
|
+
width: 100%;
|
|
70
|
+
top: 50%;
|
|
71
|
+
transform: translateY(-50%);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.basefn-resizable__handle:hover::after,
|
|
75
|
+
.basefn-resizable__handle:focus-visible::after {
|
|
76
|
+
background-color: var(--basefn-color-primary);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Grip indicator */
|
|
80
|
+
.basefn-resizable__handle-grip {
|
|
81
|
+
position: relative;
|
|
82
|
+
z-index: 1;
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
border-radius: var(--basefn-radius-sm);
|
|
87
|
+
background-color: var(--basefn-border-primary);
|
|
88
|
+
border: 1px solid var(--basefn-border-secondary);
|
|
89
|
+
transition:
|
|
90
|
+
background-color var(--basefn-transition-fast),
|
|
91
|
+
border-color var(--basefn-transition-fast);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.basefn-resizable__handle--horizontal .basefn-resizable__handle-grip {
|
|
95
|
+
width: 12px;
|
|
96
|
+
height: 24px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.basefn-resizable__handle--vertical .basefn-resizable__handle-grip {
|
|
100
|
+
width: 24px;
|
|
101
|
+
height: 12px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Grip dots pattern */
|
|
105
|
+
.basefn-resizable__handle-grip::after {
|
|
106
|
+
content: "";
|
|
107
|
+
display: block;
|
|
108
|
+
border-radius: var(--basefn-radius-full);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.basefn-resizable__handle--horizontal .basefn-resizable__handle-grip::after {
|
|
112
|
+
width: 4px;
|
|
113
|
+
height: 16px;
|
|
114
|
+
background-image: radial-gradient(
|
|
115
|
+
circle,
|
|
116
|
+
var(--basefn-text-muted) 1px,
|
|
117
|
+
transparent 1px
|
|
118
|
+
);
|
|
119
|
+
background-size: 4px 4px;
|
|
120
|
+
background-position: center;
|
|
121
|
+
background-repeat: repeat;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.basefn-resizable__handle--vertical .basefn-resizable__handle-grip::after {
|
|
125
|
+
width: 16px;
|
|
126
|
+
height: 4px;
|
|
127
|
+
background-image: radial-gradient(
|
|
128
|
+
circle,
|
|
129
|
+
var(--basefn-text-muted) 1px,
|
|
130
|
+
transparent 1px
|
|
131
|
+
);
|
|
132
|
+
background-size: 4px 4px;
|
|
133
|
+
background-position: center;
|
|
134
|
+
background-repeat: repeat;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Hover and focus states */
|
|
138
|
+
.basefn-resizable__handle:hover .basefn-resizable__handle-grip,
|
|
139
|
+
.basefn-resizable__handle:focus-visible .basefn-resizable__handle-grip {
|
|
140
|
+
background-color: var(--basefn-bg-tertiary);
|
|
141
|
+
border-color: var(--basefn-color-primary);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.basefn-resizable__handle:focus-visible .basefn-resizable__handle-grip {
|
|
145
|
+
box-shadow: 0 0 0 var(--basefn-focus-ring-width) var(--basefn-focus-ring-color);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Hide line when grip is present */
|
|
149
|
+
.basefn-resizable__handle--with-grip::after {
|
|
150
|
+
background-color: transparent;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.basefn-resizable__handle--with-grip:hover::after,
|
|
154
|
+
.basefn-resizable__handle--with-grip:focus-visible::after {
|
|
155
|
+
background-color: transparent;
|
|
156
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
%%raw(`import './Basefn__Resizable.css'`)
|
|
2
|
+
|
|
3
|
+
open Xote
|
|
4
|
+
|
|
5
|
+
type direction = Horizontal | Vertical
|
|
6
|
+
|
|
7
|
+
type panel = {
|
|
8
|
+
content: Node.node,
|
|
9
|
+
defaultSize: float,
|
|
10
|
+
minSize?: float,
|
|
11
|
+
maxSize?: float,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let directionToString = (d: direction) =>
|
|
15
|
+
switch d {
|
|
16
|
+
| Horizontal => "horizontal"
|
|
17
|
+
| Vertical => "vertical"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// DOM helpers
|
|
21
|
+
let getClientX: Dom.event => float = %raw(`function(e) {
|
|
22
|
+
var t = e.touches ? e.touches[0] : e;
|
|
23
|
+
return t.clientX;
|
|
24
|
+
}`)
|
|
25
|
+
|
|
26
|
+
let getClientY: Dom.event => float = %raw(`function(e) {
|
|
27
|
+
var t = e.touches ? e.touches[0] : e;
|
|
28
|
+
return t.clientY;
|
|
29
|
+
}`)
|
|
30
|
+
|
|
31
|
+
let addDocListener: (string, Dom.event => unit) => unit = %raw(`function(ev, fn) {
|
|
32
|
+
document.addEventListener(ev, fn);
|
|
33
|
+
}`)
|
|
34
|
+
|
|
35
|
+
let removeDocListener: (string, Dom.event => unit) => unit = %raw(`function(ev, fn) {
|
|
36
|
+
document.removeEventListener(ev, fn);
|
|
37
|
+
}`)
|
|
38
|
+
|
|
39
|
+
let disableSelect: unit => unit = %raw(`function() {
|
|
40
|
+
document.body.style.userSelect = "none";
|
|
41
|
+
document.body.style.webkitUserSelect = "none";
|
|
42
|
+
}`)
|
|
43
|
+
|
|
44
|
+
let enableSelect: unit => unit = %raw(`function() {
|
|
45
|
+
document.body.style.userSelect = "";
|
|
46
|
+
document.body.style.webkitUserSelect = "";
|
|
47
|
+
}`)
|
|
48
|
+
|
|
49
|
+
let findClosest: (Dom.element, string) => Nullable.t<Dom.element> = %raw(`function(el, sel) {
|
|
50
|
+
return el.closest(sel);
|
|
51
|
+
}`)
|
|
52
|
+
|
|
53
|
+
let getElementRect: Dom.element => {..} = %raw(`function(el) {
|
|
54
|
+
return el.getBoundingClientRect();
|
|
55
|
+
}`)
|
|
56
|
+
|
|
57
|
+
let getKey: Dom.event => string = %raw(`function(e) { return e.key || "" }`)
|
|
58
|
+
|
|
59
|
+
let getEventTarget: Dom.event => Dom.element = %raw(`function(e) {
|
|
60
|
+
return e.target;
|
|
61
|
+
}`)
|
|
62
|
+
|
|
63
|
+
let preventDefault: Dom.event => unit = %raw(`function(e) { e.preventDefault() }`)
|
|
64
|
+
|
|
65
|
+
let genId: unit => string = %raw(`function() {
|
|
66
|
+
return "basefn-resizable-" + Math.random().toString(36).substr(2, 9);
|
|
67
|
+
}`)
|
|
68
|
+
|
|
69
|
+
// Global event delegation: register callbacks by container ID, one document
|
|
70
|
+
// listener handles all instances. No timing dependency on DOM rendering.
|
|
71
|
+
let registerInstance: (string, (int, Dom.event) => unit) => unit = %raw(`function(id, cb) {
|
|
72
|
+
if (!window.__basefn_resizable) {
|
|
73
|
+
window.__basefn_resizable = {};
|
|
74
|
+
|
|
75
|
+
function handler(e) {
|
|
76
|
+
var target = e.target;
|
|
77
|
+
if (!target || !target.closest) return;
|
|
78
|
+
var handle = target.closest(".basefn-resizable__handle");
|
|
79
|
+
if (!handle) return;
|
|
80
|
+
var container = handle.closest(".basefn-resizable");
|
|
81
|
+
if (!container || !container.id) return;
|
|
82
|
+
var entry = window.__basefn_resizable[container.id];
|
|
83
|
+
if (!entry) return;
|
|
84
|
+
// Find the handle index among direct children of the container
|
|
85
|
+
var children = container.children;
|
|
86
|
+
var handleIndex = 0;
|
|
87
|
+
for (var i = 0; i < children.length; i++) {
|
|
88
|
+
if (children[i].classList.contains("basefn-resizable__handle")) {
|
|
89
|
+
if (children[i] === handle) {
|
|
90
|
+
entry(handleIndex, e);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
handleIndex++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
document.addEventListener("mousedown", handler);
|
|
99
|
+
document.addEventListener("touchstart", handler, { passive: false });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
window.__basefn_resizable[id] = cb;
|
|
103
|
+
}`)
|
|
104
|
+
|
|
105
|
+
// Lazily attach mousedown/touchstart directly to a handle element.
|
|
106
|
+
// Called from onMouseEnter so the element is guaranteed to exist.
|
|
107
|
+
let initHandle: (Dom.element, int, (int, Dom.event) => unit) => unit = %raw(`function(el, idx, cb) {
|
|
108
|
+
if (el._basefnInit) return;
|
|
109
|
+
el._basefnInit = true;
|
|
110
|
+
el.addEventListener("mousedown", function(e) { cb(idx, e); });
|
|
111
|
+
el.addEventListener("touchstart", function(e) { cb(idx, e); }, { passive: false });
|
|
112
|
+
}`)
|
|
113
|
+
|
|
114
|
+
let getCurrentTarget: Dom.event => Dom.element = %raw(`function(e) {
|
|
115
|
+
return e.currentTarget;
|
|
116
|
+
}`)
|
|
117
|
+
|
|
118
|
+
type dragInfo = {
|
|
119
|
+
handleIndex: int,
|
|
120
|
+
startPos: float,
|
|
121
|
+
startSizes: array<float>,
|
|
122
|
+
container: Dom.element,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@jsx.component
|
|
126
|
+
let make = (
|
|
127
|
+
~panels: array<panel>,
|
|
128
|
+
~direction: direction=Horizontal,
|
|
129
|
+
~withHandle: bool=true,
|
|
130
|
+
~onResize: option<array<float> => unit>=?,
|
|
131
|
+
~class: string="",
|
|
132
|
+
) => {
|
|
133
|
+
let containerId = genId()
|
|
134
|
+
let sizes = Signal.make(panels->Array.map(p => p.defaultSize))
|
|
135
|
+
let isDragging = Signal.make(false)
|
|
136
|
+
let dragRef: ref<option<dragInfo>> = ref(None)
|
|
137
|
+
|
|
138
|
+
let getPos = (evt: Dom.event) =>
|
|
139
|
+
switch direction {
|
|
140
|
+
| Horizontal => getClientX(evt)
|
|
141
|
+
| Vertical => getClientY(evt)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let getContainerDimension = (container: Dom.element) => {
|
|
145
|
+
let rect = getElementRect(container)
|
|
146
|
+
switch direction {
|
|
147
|
+
| Horizontal => Obj.magic(rect)["width"]
|
|
148
|
+
| Vertical => Obj.magic(rect)["height"]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let clampSizes = (leftIdx: int, rightIdx: int, newLeft: float, newRight: float) => {
|
|
153
|
+
let lp = panels->Array.getUnsafe(leftIdx)
|
|
154
|
+
let rp = panels->Array.getUnsafe(rightIdx)
|
|
155
|
+
let lMin = lp.minSize->Option.getOr(0.0)
|
|
156
|
+
let lMax = lp.maxSize->Option.getOr(100.0)
|
|
157
|
+
let rMin = rp.minSize->Option.getOr(0.0)
|
|
158
|
+
let rMax = rp.maxSize->Option.getOr(100.0)
|
|
159
|
+
let total = newLeft +. newRight
|
|
160
|
+
|
|
161
|
+
if newLeft < lMin {
|
|
162
|
+
(lMin, total -. lMin)
|
|
163
|
+
} else if newLeft > lMax {
|
|
164
|
+
(lMax, total -. lMax)
|
|
165
|
+
} else if newRight < rMin {
|
|
166
|
+
(total -. rMin, rMin)
|
|
167
|
+
} else if newRight > rMax {
|
|
168
|
+
(total -. rMax, rMax)
|
|
169
|
+
} else {
|
|
170
|
+
(newLeft, newRight)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let applySizes = (newSizes: array<float>) => {
|
|
175
|
+
Signal.set(sizes, newSizes)
|
|
176
|
+
switch onResize {
|
|
177
|
+
| Some(cb) => cb(newSizes)
|
|
178
|
+
| None => ()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let onMouseMove = (evt: Dom.event) => {
|
|
183
|
+
switch dragRef.contents {
|
|
184
|
+
| None => ()
|
|
185
|
+
| Some(info) =>
|
|
186
|
+
let containerSize = getContainerDimension(info.container)
|
|
187
|
+
if containerSize > 0.0 {
|
|
188
|
+
let delta = getPos(evt) -. info.startPos
|
|
189
|
+
let deltaPercent = delta /. containerSize *. 100.0
|
|
190
|
+
let li = info.handleIndex
|
|
191
|
+
let ri = info.handleIndex + 1
|
|
192
|
+
let origLeft = info.startSizes->Array.getUnsafe(li)
|
|
193
|
+
let origRight = info.startSizes->Array.getUnsafe(ri)
|
|
194
|
+
let (nl, nr) = clampSizes(li, ri, origLeft +. deltaPercent, origRight -. deltaPercent)
|
|
195
|
+
let newSizes = Signal.get(sizes)->Array.copy
|
|
196
|
+
newSizes->Array.setUnsafe(li, nl)
|
|
197
|
+
newSizes->Array.setUnsafe(ri, nr)
|
|
198
|
+
applySizes(newSizes)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let rec onMouseUp = (_: Dom.event) => {
|
|
204
|
+
dragRef := None
|
|
205
|
+
Signal.set(isDragging, false)
|
|
206
|
+
enableSelect()
|
|
207
|
+
removeDocListener("mousemove", onMouseMove)
|
|
208
|
+
removeDocListener("mouseup", onMouseUp)
|
|
209
|
+
removeDocListener("touchmove", onMouseMove)
|
|
210
|
+
removeDocListener("touchend", onMouseUp)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let startDrag = (handleIndex: int, evt: Dom.event) => {
|
|
214
|
+
preventDefault(evt)
|
|
215
|
+
let target = getEventTarget(evt)
|
|
216
|
+
let maybeContainer = findClosest(target, ".basefn-resizable")
|
|
217
|
+
switch Nullable.toOption(maybeContainer) {
|
|
218
|
+
| None => ()
|
|
219
|
+
| Some(container) =>
|
|
220
|
+
dragRef := Some({
|
|
221
|
+
handleIndex,
|
|
222
|
+
startPos: getPos(evt),
|
|
223
|
+
startSizes: Signal.get(sizes)->Array.copy,
|
|
224
|
+
container,
|
|
225
|
+
})
|
|
226
|
+
Signal.set(isDragging, true)
|
|
227
|
+
disableSelect()
|
|
228
|
+
addDocListener("mousemove", onMouseMove)
|
|
229
|
+
addDocListener("mouseup", onMouseUp)
|
|
230
|
+
addDocListener("touchmove", onMouseMove)
|
|
231
|
+
addDocListener("touchend", onMouseUp)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let handleKeyDown = (handleIndex: int, evt: Dom.event) => {
|
|
236
|
+
let key = getKey(evt)
|
|
237
|
+
let step = 1.0
|
|
238
|
+
let li = handleIndex
|
|
239
|
+
let ri = handleIndex + 1
|
|
240
|
+
let currentSizes = Signal.get(sizes)
|
|
241
|
+
let left = currentSizes->Array.getUnsafe(li)
|
|
242
|
+
let right = currentSizes->Array.getUnsafe(ri)
|
|
243
|
+
|
|
244
|
+
let delta = switch (direction, key) {
|
|
245
|
+
| (Horizontal, "ArrowLeft") | (Vertical, "ArrowUp") => Some(-.step)
|
|
246
|
+
| (Horizontal, "ArrowRight") | (Vertical, "ArrowDown") => Some(step)
|
|
247
|
+
| _ => None
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
switch delta {
|
|
251
|
+
| Some(d) =>
|
|
252
|
+
preventDefault(evt)
|
|
253
|
+
let (nl, nr) = clampSizes(li, ri, left +. d, right -. d)
|
|
254
|
+
let newSizes = currentSizes->Array.copy
|
|
255
|
+
newSizes->Array.setUnsafe(li, nl)
|
|
256
|
+
newSizes->Array.setUnsafe(ri, nr)
|
|
257
|
+
applySizes(newSizes)
|
|
258
|
+
| None => ()
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Register this instance for global event delegation
|
|
263
|
+
registerInstance(containerId, startDrag)
|
|
264
|
+
|
|
265
|
+
let containerClass =
|
|
266
|
+
"basefn-resizable basefn-resizable--" ++
|
|
267
|
+
directionToString(direction) ++
|
|
268
|
+
(class !== "" ? " " ++ class : "")
|
|
269
|
+
|
|
270
|
+
let elements: array<Node.node> = []
|
|
271
|
+
|
|
272
|
+
panels->Array.forEachWithIndex((panel, index) => {
|
|
273
|
+
let panelStyle = Computed.make(() => {
|
|
274
|
+
let size = Signal.get(sizes)->Array.getUnsafe(index)
|
|
275
|
+
"flex-basis:" ++ Float.toString(size) ++ "%;flex-grow:0;flex-shrink:0"
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
let panelClass = Computed.make(() => {
|
|
279
|
+
let base = "basefn-resizable__panel"
|
|
280
|
+
if Signal.get(isDragging) {
|
|
281
|
+
base ++ " basefn-resizable__panel--dragging"
|
|
282
|
+
} else {
|
|
283
|
+
base
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
elements->Array.push(
|
|
288
|
+
<div key={"panel-" ++ Int.toString(index)} class={panelClass} style={panelStyle}>
|
|
289
|
+
{panel.content}
|
|
290
|
+
</div>,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
if index < Array.length(panels) - 1 {
|
|
294
|
+
let handleClass =
|
|
295
|
+
"basefn-resizable__handle basefn-resizable__handle--" ++
|
|
296
|
+
directionToString(direction) ++
|
|
297
|
+
(withHandle ? " basefn-resizable__handle--with-grip" : "")
|
|
298
|
+
|
|
299
|
+
elements->Array.push(
|
|
300
|
+
<div
|
|
301
|
+
key={"handle-" ++ Int.toString(index)}
|
|
302
|
+
class={handleClass}
|
|
303
|
+
onMouseEnter={evt => {
|
|
304
|
+
let el = getCurrentTarget(evt)
|
|
305
|
+
initHandle(el, index, startDrag)
|
|
306
|
+
}}
|
|
307
|
+
onKeyDown={evt => handleKeyDown(index, evt)}
|
|
308
|
+
role="separator"
|
|
309
|
+
tabIndex={0}
|
|
310
|
+
>
|
|
311
|
+
{withHandle ? <div class="basefn-resizable__handle-grip" /> : <> </>}
|
|
312
|
+
</div>,
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
<div id={containerId} class={containerClass}> {elements->Node.fragment} </div>
|
|
318
|
+
}
|