basefn 1.9.0 → 1.10.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 +2 -2
- package/src/Basefn.res +5 -0
- package/src/Basefn.res.mjs +4 -0
- package/src/components/Basefn__Resizable.css +156 -0
- package/src/components/Basefn__Resizable.res +289 -0
- package/src/components/Basefn__Resizable.res.mjs +313 -0
- package/src/components/Basefn__Spotlight.res +16 -10
- package/src/components/Basefn__Spotlight.res.mjs +20 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "basefn",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/brnrdog/basefn.git"
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"@semantic-release/github": "^12.0.2",
|
|
81
81
|
"@semantic-release/npm": "^13.1.3",
|
|
82
82
|
"rescript": "^12.0.1",
|
|
83
|
-
"vite": "^
|
|
83
|
+
"vite": "^8.0.0",
|
|
84
84
|
"xote": "^4.12.0"
|
|
85
85
|
}
|
|
86
86
|
}
|
package/src/Basefn.res
CHANGED
|
@@ -72,6 +72,8 @@ type gridJustifyContent = Basefn__Grid.justifyContent
|
|
|
72
72
|
type gridAlignContent = Basefn__Grid.alignContent
|
|
73
73
|
type gridItemColumnSpan = Basefn__Grid.Item.columnSpan
|
|
74
74
|
type gridItemRowSpan = Basefn__Grid.Item.rowSpan
|
|
75
|
+
type resizableDirection = Basefn__Resizable.direction
|
|
76
|
+
type resizablePanel = Basefn__Resizable.panel
|
|
75
77
|
type breakpoint = Basefn__Responsive.breakpoint
|
|
76
78
|
type currentBreakpoint = Basefn__Responsive.currentBreakpoint
|
|
77
79
|
type responsiveValue<'a> = Basefn__Responsive.responsiveValue<'a>
|
|
@@ -231,6 +233,9 @@ module ContextMenu = {
|
|
|
231
233
|
module Spotlight = {
|
|
232
234
|
include Basefn__Spotlight
|
|
233
235
|
}
|
|
236
|
+
module Resizable = {
|
|
237
|
+
include Basefn__Resizable
|
|
238
|
+
}
|
|
234
239
|
|
|
235
240
|
// Responsive Utilities
|
|
236
241
|
module Responsive = {
|
package/src/Basefn.res.mjs
CHANGED
|
@@ -35,6 +35,7 @@ import * as Basefn__Timeline from "./components/Basefn__Timeline.res.mjs";
|
|
|
35
35
|
import * as Basefn__Accordion from "./components/Basefn__Accordion.res.mjs";
|
|
36
36
|
import * as Basefn__AppLayout from "./components/Basefn__AppLayout.res.mjs";
|
|
37
37
|
import * as Basefn__HoverCard from "./components/Basefn__HoverCard.res.mjs";
|
|
38
|
+
import * as Basefn__Resizable from "./components/Basefn__Resizable.res.mjs";
|
|
38
39
|
import * as Basefn__Separator from "./components/Basefn__Separator.res.mjs";
|
|
39
40
|
import * as Basefn__Spotlight from "./components/Basefn__Spotlight.res.mjs";
|
|
40
41
|
import * as Basefn__Breadcrumb from "./components/Basefn__Breadcrumb.res.mjs";
|
|
@@ -143,6 +144,8 @@ let ContextMenu = Basefn__ContextMenu;
|
|
|
143
144
|
|
|
144
145
|
let Spotlight = Basefn__Spotlight;
|
|
145
146
|
|
|
147
|
+
let Resizable = Basefn__Resizable;
|
|
148
|
+
|
|
146
149
|
let Responsive = Basefn__Responsive;
|
|
147
150
|
|
|
148
151
|
export {
|
|
@@ -192,6 +195,7 @@ export {
|
|
|
192
195
|
AlertDialog,
|
|
193
196
|
ContextMenu,
|
|
194
197
|
Spotlight,
|
|
198
|
+
Resizable,
|
|
195
199
|
Responsive,
|
|
196
200
|
}
|
|
197
201
|
/* Not a pure module */
|
|
@@ -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,289 @@
|
|
|
1
|
+
%%raw(`import './Basefn__Resizable.css'`)
|
|
2
|
+
|
|
3
|
+
open Xote
|
|
4
|
+
|
|
5
|
+
type direction = Horizontal | Vertical
|
|
6
|
+
|
|
7
|
+
type panel = {
|
|
8
|
+
content: Component.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
|
+
// Set up mousedown/touchstart delegation on a container element by ID
|
|
70
|
+
let setupDragListeners: (string, (int, Dom.event) => unit) => unit = %raw(`function(containerId, onStart) {
|
|
71
|
+
requestAnimationFrame(function() {
|
|
72
|
+
var container = document.getElementById(containerId);
|
|
73
|
+
if (!container) return;
|
|
74
|
+
|
|
75
|
+
function handler(e) {
|
|
76
|
+
var target = e.target;
|
|
77
|
+
var handle = target.closest ? target.closest(".basefn-resizable__handle") : null;
|
|
78
|
+
if (!handle || !container.contains(handle)) return;
|
|
79
|
+
var handles = container.querySelectorAll(".basefn-resizable__handle");
|
|
80
|
+
for (var i = 0; i < handles.length; i++) {
|
|
81
|
+
if (handles[i] === handle) {
|
|
82
|
+
onStart(i, e);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
container.addEventListener("mousedown", handler);
|
|
89
|
+
container.addEventListener("touchstart", handler, { passive: false });
|
|
90
|
+
});
|
|
91
|
+
}`)
|
|
92
|
+
|
|
93
|
+
type dragInfo = {
|
|
94
|
+
handleIndex: int,
|
|
95
|
+
startPos: float,
|
|
96
|
+
startSizes: array<float>,
|
|
97
|
+
container: Dom.element,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@jsx.component
|
|
101
|
+
let make = (
|
|
102
|
+
~panels: array<panel>,
|
|
103
|
+
~direction: direction=Horizontal,
|
|
104
|
+
~withHandle: bool=true,
|
|
105
|
+
~onResize: option<array<float> => unit>=?,
|
|
106
|
+
~class: string="",
|
|
107
|
+
) => {
|
|
108
|
+
let containerId = genId()
|
|
109
|
+
let sizes = Signal.make(panels->Array.map(p => p.defaultSize))
|
|
110
|
+
let isDragging = Signal.make(false)
|
|
111
|
+
let dragRef: ref<option<dragInfo>> = ref(None)
|
|
112
|
+
|
|
113
|
+
let getPos = (evt: Dom.event) =>
|
|
114
|
+
switch direction {
|
|
115
|
+
| Horizontal => getClientX(evt)
|
|
116
|
+
| Vertical => getClientY(evt)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let getContainerDimension = (container: Dom.element) => {
|
|
120
|
+
let rect = getElementRect(container)
|
|
121
|
+
switch direction {
|
|
122
|
+
| Horizontal => Obj.magic(rect)["width"]
|
|
123
|
+
| Vertical => Obj.magic(rect)["height"]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let clampSizes = (leftIdx: int, rightIdx: int, newLeft: float, newRight: float) => {
|
|
128
|
+
let lp = panels->Array.getUnsafe(leftIdx)
|
|
129
|
+
let rp = panels->Array.getUnsafe(rightIdx)
|
|
130
|
+
let lMin = lp.minSize->Option.getOr(0.0)
|
|
131
|
+
let lMax = lp.maxSize->Option.getOr(100.0)
|
|
132
|
+
let rMin = rp.minSize->Option.getOr(0.0)
|
|
133
|
+
let rMax = rp.maxSize->Option.getOr(100.0)
|
|
134
|
+
let total = newLeft +. newRight
|
|
135
|
+
|
|
136
|
+
if newLeft < lMin {
|
|
137
|
+
(lMin, total -. lMin)
|
|
138
|
+
} else if newLeft > lMax {
|
|
139
|
+
(lMax, total -. lMax)
|
|
140
|
+
} else if newRight < rMin {
|
|
141
|
+
(total -. rMin, rMin)
|
|
142
|
+
} else if newRight > rMax {
|
|
143
|
+
(total -. rMax, rMax)
|
|
144
|
+
} else {
|
|
145
|
+
(newLeft, newRight)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let applySizes = (newSizes: array<float>) => {
|
|
150
|
+
Signal.set(sizes, newSizes)
|
|
151
|
+
switch onResize {
|
|
152
|
+
| Some(cb) => cb(newSizes)
|
|
153
|
+
| None => ()
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let onMouseMove = (evt: Dom.event) => {
|
|
158
|
+
switch dragRef.contents {
|
|
159
|
+
| None => ()
|
|
160
|
+
| Some(info) =>
|
|
161
|
+
let containerSize = getContainerDimension(info.container)
|
|
162
|
+
if containerSize > 0.0 {
|
|
163
|
+
let delta = getPos(evt) -. info.startPos
|
|
164
|
+
let deltaPercent = delta /. containerSize *. 100.0
|
|
165
|
+
let li = info.handleIndex
|
|
166
|
+
let ri = info.handleIndex + 1
|
|
167
|
+
let origLeft = info.startSizes->Array.getUnsafe(li)
|
|
168
|
+
let origRight = info.startSizes->Array.getUnsafe(ri)
|
|
169
|
+
let (nl, nr) = clampSizes(li, ri, origLeft +. deltaPercent, origRight -. deltaPercent)
|
|
170
|
+
let newSizes = Signal.get(sizes)->Array.copy
|
|
171
|
+
newSizes->Array.setUnsafe(li, nl)
|
|
172
|
+
newSizes->Array.setUnsafe(ri, nr)
|
|
173
|
+
applySizes(newSizes)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let rec onMouseUp = (_: Dom.event) => {
|
|
179
|
+
dragRef := None
|
|
180
|
+
Signal.set(isDragging, false)
|
|
181
|
+
enableSelect()
|
|
182
|
+
removeDocListener("mousemove", onMouseMove)
|
|
183
|
+
removeDocListener("mouseup", onMouseUp)
|
|
184
|
+
removeDocListener("touchmove", onMouseMove)
|
|
185
|
+
removeDocListener("touchend", onMouseUp)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let startDrag = (handleIndex: int, evt: Dom.event) => {
|
|
189
|
+
preventDefault(evt)
|
|
190
|
+
let target = getEventTarget(evt)
|
|
191
|
+
let maybeContainer = findClosest(target, ".basefn-resizable")
|
|
192
|
+
switch Nullable.toOption(maybeContainer) {
|
|
193
|
+
| None => ()
|
|
194
|
+
| Some(container) =>
|
|
195
|
+
dragRef := Some({
|
|
196
|
+
handleIndex,
|
|
197
|
+
startPos: getPos(evt),
|
|
198
|
+
startSizes: Signal.get(sizes)->Array.copy,
|
|
199
|
+
container,
|
|
200
|
+
})
|
|
201
|
+
Signal.set(isDragging, true)
|
|
202
|
+
disableSelect()
|
|
203
|
+
addDocListener("mousemove", onMouseMove)
|
|
204
|
+
addDocListener("mouseup", onMouseUp)
|
|
205
|
+
addDocListener("touchmove", onMouseMove)
|
|
206
|
+
addDocListener("touchend", onMouseUp)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let handleKeyDown = (handleIndex: int, evt: Dom.event) => {
|
|
211
|
+
let key = getKey(evt)
|
|
212
|
+
let step = 1.0
|
|
213
|
+
let li = handleIndex
|
|
214
|
+
let ri = handleIndex + 1
|
|
215
|
+
let currentSizes = Signal.get(sizes)
|
|
216
|
+
let left = currentSizes->Array.getUnsafe(li)
|
|
217
|
+
let right = currentSizes->Array.getUnsafe(ri)
|
|
218
|
+
|
|
219
|
+
let delta = switch (direction, key) {
|
|
220
|
+
| (Horizontal, "ArrowLeft") | (Vertical, "ArrowUp") => Some(-.step)
|
|
221
|
+
| (Horizontal, "ArrowRight") | (Vertical, "ArrowDown") => Some(step)
|
|
222
|
+
| _ => None
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
switch delta {
|
|
226
|
+
| Some(d) =>
|
|
227
|
+
preventDefault(evt)
|
|
228
|
+
let (nl, nr) = clampSizes(li, ri, left +. d, right -. d)
|
|
229
|
+
let newSizes = currentSizes->Array.copy
|
|
230
|
+
newSizes->Array.setUnsafe(li, nl)
|
|
231
|
+
newSizes->Array.setUnsafe(ri, nr)
|
|
232
|
+
applySizes(newSizes)
|
|
233
|
+
| None => ()
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Set up mousedown/touchstart via event delegation
|
|
238
|
+
setupDragListeners(containerId, startDrag)
|
|
239
|
+
|
|
240
|
+
let containerClass =
|
|
241
|
+
"basefn-resizable basefn-resizable--" ++
|
|
242
|
+
directionToString(direction) ++
|
|
243
|
+
(class !== "" ? " " ++ class : "")
|
|
244
|
+
|
|
245
|
+
let elements: array<Component.node> = []
|
|
246
|
+
|
|
247
|
+
panels->Array.forEachWithIndex((panel, index) => {
|
|
248
|
+
let panelStyle = Computed.make(() => {
|
|
249
|
+
let size = Signal.get(sizes)->Array.getUnsafe(index)
|
|
250
|
+
"flex-basis:" ++ Float.toString(size) ++ "%;flex-grow:0;flex-shrink:0"
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
let panelClass = Computed.make(() => {
|
|
254
|
+
let base = "basefn-resizable__panel"
|
|
255
|
+
if Signal.get(isDragging) {
|
|
256
|
+
base ++ " basefn-resizable__panel--dragging"
|
|
257
|
+
} else {
|
|
258
|
+
base
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
elements->Array.push(
|
|
263
|
+
<div key={"panel-" ++ Int.toString(index)} class={panelClass} style={panelStyle}>
|
|
264
|
+
{panel.content}
|
|
265
|
+
</div>,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if index < Array.length(panels) - 1 {
|
|
269
|
+
let handleClass =
|
|
270
|
+
"basefn-resizable__handle basefn-resizable__handle--" ++
|
|
271
|
+
directionToString(direction) ++
|
|
272
|
+
(withHandle ? " basefn-resizable__handle--with-grip" : "")
|
|
273
|
+
|
|
274
|
+
elements->Array.push(
|
|
275
|
+
<div
|
|
276
|
+
key={"handle-" ++ Int.toString(index)}
|
|
277
|
+
class={handleClass}
|
|
278
|
+
onKeyDown={evt => handleKeyDown(index, evt)}
|
|
279
|
+
role="separator"
|
|
280
|
+
tabIndex={0}
|
|
281
|
+
>
|
|
282
|
+
{withHandle ? <div class="basefn-resizable__handle-grip" /> : <> </>}
|
|
283
|
+
</div>,
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
<div id={containerId} class={containerClass}> {elements->Component.fragment} </div>
|
|
289
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Xote from "xote/src/Xote.res.mjs";
|
|
4
|
+
import * as Xote__JSX from "xote/src/Xote__JSX.res.mjs";
|
|
5
|
+
import * as Core__Option from "@rescript/core/src/Core__Option.res.mjs";
|
|
6
|
+
|
|
7
|
+
import './Basefn__Resizable.css'
|
|
8
|
+
;
|
|
9
|
+
|
|
10
|
+
function directionToString(d) {
|
|
11
|
+
if (d === "Horizontal") {
|
|
12
|
+
return "horizontal";
|
|
13
|
+
} else {
|
|
14
|
+
return "vertical";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let getClientX = (function(e) {
|
|
19
|
+
var t = e.touches ? e.touches[0] : e;
|
|
20
|
+
return t.clientX;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
let getClientY = (function(e) {
|
|
24
|
+
var t = e.touches ? e.touches[0] : e;
|
|
25
|
+
return t.clientY;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
let addDocListener = (function(ev, fn) {
|
|
29
|
+
document.addEventListener(ev, fn);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
let removeDocListener = (function(ev, fn) {
|
|
33
|
+
document.removeEventListener(ev, fn);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
let disableSelect = (function() {
|
|
37
|
+
document.body.style.userSelect = "none";
|
|
38
|
+
document.body.style.webkitUserSelect = "none";
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
let enableSelect = (function() {
|
|
42
|
+
document.body.style.userSelect = "";
|
|
43
|
+
document.body.style.webkitUserSelect = "";
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
let findClosest = (function(el, sel) {
|
|
47
|
+
return el.closest(sel);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let getElementRect = (function(el) {
|
|
51
|
+
return el.getBoundingClientRect();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
let getKey = (function(e) { return e.key || "" });
|
|
55
|
+
|
|
56
|
+
let getEventTarget = (function(e) {
|
|
57
|
+
return e.target;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
let preventDefault = (function(e) { e.preventDefault() });
|
|
61
|
+
|
|
62
|
+
let genId = (function() {
|
|
63
|
+
return "basefn-resizable-" + Math.random().toString(36).substr(2, 9);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
let setupDragListeners = (function(containerId, onStart) {
|
|
67
|
+
requestAnimationFrame(function() {
|
|
68
|
+
var container = document.getElementById(containerId);
|
|
69
|
+
if (!container) return;
|
|
70
|
+
|
|
71
|
+
function handler(e) {
|
|
72
|
+
var target = e.target;
|
|
73
|
+
var handle = target.closest ? target.closest(".basefn-resizable__handle") : null;
|
|
74
|
+
if (!handle || !container.contains(handle)) return;
|
|
75
|
+
var handles = container.querySelectorAll(".basefn-resizable__handle");
|
|
76
|
+
for (var i = 0; i < handles.length; i++) {
|
|
77
|
+
if (handles[i] === handle) {
|
|
78
|
+
onStart(i, e);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
container.addEventListener("mousedown", handler);
|
|
85
|
+
container.addEventListener("touchstart", handler, { passive: false });
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
function Basefn__Resizable(props) {
|
|
90
|
+
let __class = props.class;
|
|
91
|
+
let onResize = props.onResize;
|
|
92
|
+
let __withHandle = props.withHandle;
|
|
93
|
+
let __direction = props.direction;
|
|
94
|
+
let panels = props.panels;
|
|
95
|
+
let direction = __direction !== undefined ? __direction : "Horizontal";
|
|
96
|
+
let withHandle = __withHandle !== undefined ? __withHandle : true;
|
|
97
|
+
let $$class = __class !== undefined ? __class : "";
|
|
98
|
+
let containerId = genId();
|
|
99
|
+
let sizes = Xote.Signal.make(panels.map(p => p.defaultSize), undefined, undefined);
|
|
100
|
+
let isDragging = Xote.Signal.make(false, undefined, undefined);
|
|
101
|
+
let dragRef = {
|
|
102
|
+
contents: undefined
|
|
103
|
+
};
|
|
104
|
+
let getPos = evt => {
|
|
105
|
+
if (direction === "Horizontal") {
|
|
106
|
+
return getClientX(evt);
|
|
107
|
+
} else {
|
|
108
|
+
return getClientY(evt);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
let getContainerDimension = container => {
|
|
112
|
+
let rect = getElementRect(container);
|
|
113
|
+
if (direction === "Horizontal") {
|
|
114
|
+
return rect.width;
|
|
115
|
+
} else {
|
|
116
|
+
return rect.height;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
let clampSizes = (leftIdx, rightIdx, newLeft, newRight) => {
|
|
120
|
+
let lp = panels[leftIdx];
|
|
121
|
+
let rp = panels[rightIdx];
|
|
122
|
+
let lMin = Core__Option.getOr(lp.minSize, 0.0);
|
|
123
|
+
let lMax = Core__Option.getOr(lp.maxSize, 100.0);
|
|
124
|
+
let rMin = Core__Option.getOr(rp.minSize, 0.0);
|
|
125
|
+
let rMax = Core__Option.getOr(rp.maxSize, 100.0);
|
|
126
|
+
let total = newLeft + newRight;
|
|
127
|
+
if (newLeft < lMin) {
|
|
128
|
+
return [
|
|
129
|
+
lMin,
|
|
130
|
+
total - lMin
|
|
131
|
+
];
|
|
132
|
+
} else if (newLeft > lMax) {
|
|
133
|
+
return [
|
|
134
|
+
lMax,
|
|
135
|
+
total - lMax
|
|
136
|
+
];
|
|
137
|
+
} else if (newRight < rMin) {
|
|
138
|
+
return [
|
|
139
|
+
total - rMin,
|
|
140
|
+
rMin
|
|
141
|
+
];
|
|
142
|
+
} else if (newRight > rMax) {
|
|
143
|
+
return [
|
|
144
|
+
total - rMax,
|
|
145
|
+
rMax
|
|
146
|
+
];
|
|
147
|
+
} else {
|
|
148
|
+
return [
|
|
149
|
+
newLeft,
|
|
150
|
+
newRight
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
let applySizes = newSizes => {
|
|
155
|
+
Xote.Signal.set(sizes, newSizes);
|
|
156
|
+
if (onResize !== undefined) {
|
|
157
|
+
return onResize(newSizes);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
let onMouseMove = evt => {
|
|
161
|
+
let info = dragRef.contents;
|
|
162
|
+
if (info === undefined) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
let containerSize = getContainerDimension(info.container);
|
|
166
|
+
if (containerSize <= 0.0) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
let delta = getPos(evt) - info.startPos;
|
|
170
|
+
let deltaPercent = delta / containerSize * 100.0;
|
|
171
|
+
let li = info.handleIndex;
|
|
172
|
+
let ri = info.handleIndex + 1 | 0;
|
|
173
|
+
let origLeft = info.startSizes[li];
|
|
174
|
+
let origRight = info.startSizes[ri];
|
|
175
|
+
let match = clampSizes(li, ri, origLeft + deltaPercent, origRight - deltaPercent);
|
|
176
|
+
let newSizes = Xote.Signal.get(sizes).slice();
|
|
177
|
+
newSizes[li] = match[0];
|
|
178
|
+
newSizes[ri] = match[1];
|
|
179
|
+
applySizes(newSizes);
|
|
180
|
+
};
|
|
181
|
+
let onMouseUp = param => {
|
|
182
|
+
dragRef.contents = undefined;
|
|
183
|
+
Xote.Signal.set(isDragging, false);
|
|
184
|
+
enableSelect();
|
|
185
|
+
removeDocListener("mousemove", onMouseMove);
|
|
186
|
+
removeDocListener("mouseup", onMouseUp);
|
|
187
|
+
removeDocListener("touchmove", onMouseMove);
|
|
188
|
+
removeDocListener("touchend", onMouseUp);
|
|
189
|
+
};
|
|
190
|
+
let startDrag = (handleIndex, evt) => {
|
|
191
|
+
preventDefault(evt);
|
|
192
|
+
let target = getEventTarget(evt);
|
|
193
|
+
let maybeContainer = findClosest(target, ".basefn-resizable");
|
|
194
|
+
if (!(maybeContainer == null)) {
|
|
195
|
+
dragRef.contents = {
|
|
196
|
+
handleIndex: handleIndex,
|
|
197
|
+
startPos: getPos(evt),
|
|
198
|
+
startSizes: Xote.Signal.get(sizes).slice(),
|
|
199
|
+
container: maybeContainer
|
|
200
|
+
};
|
|
201
|
+
Xote.Signal.set(isDragging, true);
|
|
202
|
+
disableSelect();
|
|
203
|
+
addDocListener("mousemove", onMouseMove);
|
|
204
|
+
addDocListener("mouseup", onMouseUp);
|
|
205
|
+
addDocListener("touchmove", onMouseMove);
|
|
206
|
+
return addDocListener("touchend", onMouseUp);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
setupDragListeners(containerId, startDrag);
|
|
210
|
+
let containerClass = "basefn-resizable basefn-resizable--" + directionToString(direction) + (
|
|
211
|
+
$$class !== "" ? " " + $$class : ""
|
|
212
|
+
);
|
|
213
|
+
let elements = [];
|
|
214
|
+
panels.forEach((panel, index) => {
|
|
215
|
+
let panelStyle = Xote.Computed.make(() => {
|
|
216
|
+
let size = Xote.Signal.get(sizes)[index];
|
|
217
|
+
return "flex-basis:" + size.toString() + "%;flex-grow:0;flex-shrink:0";
|
|
218
|
+
}, undefined);
|
|
219
|
+
let panelClass = Xote.Computed.make(() => {
|
|
220
|
+
let base = "basefn-resizable__panel";
|
|
221
|
+
if (Xote.Signal.get(isDragging)) {
|
|
222
|
+
return base + " basefn-resizable__panel--dragging";
|
|
223
|
+
} else {
|
|
224
|
+
return base;
|
|
225
|
+
}
|
|
226
|
+
}, undefined);
|
|
227
|
+
elements.push(Xote__JSX.Elements.jsxKeyed("div", {
|
|
228
|
+
class: panelClass,
|
|
229
|
+
style: panelStyle,
|
|
230
|
+
children: panel.content
|
|
231
|
+
}, "panel-" + index.toString(), undefined));
|
|
232
|
+
if (index >= (panels.length - 1 | 0)) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
let handleClass = "basefn-resizable__handle basefn-resizable__handle--" + directionToString(direction) + (
|
|
236
|
+
withHandle ? " basefn-resizable__handle--with-grip" : ""
|
|
237
|
+
);
|
|
238
|
+
elements.push(Xote__JSX.Elements.jsxKeyed("div", {
|
|
239
|
+
class: handleClass,
|
|
240
|
+
role: "separator",
|
|
241
|
+
tabIndex: 0,
|
|
242
|
+
onKeyDown: evt => {
|
|
243
|
+
let key = getKey(evt);
|
|
244
|
+
let ri = index + 1 | 0;
|
|
245
|
+
let currentSizes = Xote.Signal.get(sizes);
|
|
246
|
+
let left = currentSizes[index];
|
|
247
|
+
let right = currentSizes[ri];
|
|
248
|
+
let delta;
|
|
249
|
+
if (direction === "Horizontal") {
|
|
250
|
+
switch (key) {
|
|
251
|
+
case "ArrowLeft" :
|
|
252
|
+
delta = - 1.0;
|
|
253
|
+
break;
|
|
254
|
+
case "ArrowRight" :
|
|
255
|
+
delta = 1.0;
|
|
256
|
+
break;
|
|
257
|
+
default:
|
|
258
|
+
delta = undefined;
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
switch (key) {
|
|
262
|
+
case "ArrowDown" :
|
|
263
|
+
delta = 1.0;
|
|
264
|
+
break;
|
|
265
|
+
case "ArrowUp" :
|
|
266
|
+
delta = - 1.0;
|
|
267
|
+
break;
|
|
268
|
+
default:
|
|
269
|
+
delta = undefined;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (delta === undefined) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
preventDefault(evt);
|
|
276
|
+
let match = clampSizes(index, ri, left + delta, right - delta);
|
|
277
|
+
let newSizes = currentSizes.slice();
|
|
278
|
+
newSizes[index] = match[0];
|
|
279
|
+
newSizes[ri] = match[1];
|
|
280
|
+
applySizes(newSizes);
|
|
281
|
+
},
|
|
282
|
+
children: withHandle ? Xote__JSX.Elements.jsx("div", {
|
|
283
|
+
class: "basefn-resizable__handle-grip"
|
|
284
|
+
}) : Xote__JSX.jsx(Xote__JSX.jsxFragment, {})
|
|
285
|
+
}, "handle-" + index.toString(), undefined));
|
|
286
|
+
});
|
|
287
|
+
return Xote__JSX.Elements.jsx("div", {
|
|
288
|
+
id: containerId,
|
|
289
|
+
class: containerClass,
|
|
290
|
+
children: Xote.Component.fragment(elements)
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let make = Basefn__Resizable;
|
|
295
|
+
|
|
296
|
+
export {
|
|
297
|
+
directionToString,
|
|
298
|
+
getClientX,
|
|
299
|
+
getClientY,
|
|
300
|
+
addDocListener,
|
|
301
|
+
removeDocListener,
|
|
302
|
+
disableSelect,
|
|
303
|
+
enableSelect,
|
|
304
|
+
findClosest,
|
|
305
|
+
getElementRect,
|
|
306
|
+
getKey,
|
|
307
|
+
getEventTarget,
|
|
308
|
+
preventDefault,
|
|
309
|
+
genId,
|
|
310
|
+
setupDragListeners,
|
|
311
|
+
make,
|
|
312
|
+
}
|
|
313
|
+
/* Not a pure module */
|
|
@@ -65,10 +65,18 @@ let make = (
|
|
|
65
65
|
| "ArrowDown" => {
|
|
66
66
|
let _ = Basefn__Dom.preventDefault(evt)
|
|
67
67
|
Signal.update(activeIndex, i => mod(i + 1, max(len, 1)))
|
|
68
|
+
let _ = %raw(`requestAnimationFrame(() => {
|
|
69
|
+
const el = document.querySelector(".basefn-spotlight__item--active");
|
|
70
|
+
if (el) el.scrollIntoView({ block: "nearest" });
|
|
71
|
+
})`)
|
|
68
72
|
}
|
|
69
73
|
| "ArrowUp" => {
|
|
70
74
|
let _ = Basefn__Dom.preventDefault(evt)
|
|
71
75
|
Signal.update(activeIndex, i => mod(i - 1 + max(len, 1), max(len, 1)))
|
|
76
|
+
let _ = %raw(`requestAnimationFrame(() => {
|
|
77
|
+
const el = document.querySelector(".basefn-spotlight__item--active");
|
|
78
|
+
if (el) el.scrollIntoView({ block: "nearest" });
|
|
79
|
+
})`)
|
|
72
80
|
}
|
|
73
81
|
| "Enter" =>
|
|
74
82
|
if len > 0 {
|
|
@@ -103,16 +111,15 @@ let make = (
|
|
|
103
111
|
}
|
|
104
112
|
}
|
|
105
113
|
|
|
106
|
-
// Auto-focus input when opened
|
|
114
|
+
// Auto-focus input when opened using requestAnimationFrame for reliable timing
|
|
107
115
|
let _ = Effect.run(() => {
|
|
108
116
|
if Signal.get(isOpen) {
|
|
109
|
-
let _ =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}, 16)
|
|
117
|
+
let _ = %raw(`requestAnimationFrame(() => {
|
|
118
|
+
requestAnimationFrame(() => {
|
|
119
|
+
const el = document.querySelector(".basefn-spotlight__input");
|
|
120
|
+
if (el) el.focus();
|
|
121
|
+
})
|
|
122
|
+
})`)
|
|
116
123
|
}
|
|
117
124
|
None
|
|
118
125
|
})
|
|
@@ -165,7 +172,7 @@ let make = (
|
|
|
165
172
|
if Signal.get(isOpen) {
|
|
166
173
|
[
|
|
167
174
|
<div class="basefn-spotlight-backdrop" onClick={handleBackdropClick}>
|
|
168
|
-
<div class="basefn-spotlight">
|
|
175
|
+
<div class="basefn-spotlight" onKeyDown={handleKeyDown}>
|
|
169
176
|
<div class="basefn-spotlight__input-wrapper">
|
|
170
177
|
<Basefn__Icon name={Basefn__Icon.Search} size={Basefn__Icon.Sm} />
|
|
171
178
|
<input
|
|
@@ -174,7 +181,6 @@ let make = (
|
|
|
174
181
|
placeholder
|
|
175
182
|
value={ReactiveProp.reactive(query)}
|
|
176
183
|
onInput={handleInput}
|
|
177
|
-
onKeyDown={handleKeyDown}
|
|
178
184
|
/>
|
|
179
185
|
</div>
|
|
180
186
|
<div class="basefn-spotlight__results"> {renderResults()} </div>
|
|
@@ -54,10 +54,20 @@ function Basefn__Spotlight(props) {
|
|
|
54
54
|
switch (k) {
|
|
55
55
|
case "ArrowDown" :
|
|
56
56
|
Basefn__Dom.preventDefault(evt);
|
|
57
|
-
|
|
57
|
+
Xote.Signal.update(activeIndex, i => Primitive_int.mod_(i + 1 | 0, Primitive_int.max(len, 1)));
|
|
58
|
+
((requestAnimationFrame(() => {
|
|
59
|
+
const el = document.querySelector(".basefn-spotlight__item--active");
|
|
60
|
+
if (el) el.scrollIntoView({ block: "nearest" });
|
|
61
|
+
})));
|
|
62
|
+
return;
|
|
58
63
|
case "ArrowUp" :
|
|
59
64
|
Basefn__Dom.preventDefault(evt);
|
|
60
|
-
|
|
65
|
+
Xote.Signal.update(activeIndex, i => Primitive_int.mod_((i - 1 | 0) + Primitive_int.max(len, 1) | 0, Primitive_int.max(len, 1)));
|
|
66
|
+
((requestAnimationFrame(() => {
|
|
67
|
+
const el = document.querySelector(".basefn-spotlight__item--active");
|
|
68
|
+
if (el) el.scrollIntoView({ block: "nearest" });
|
|
69
|
+
})));
|
|
70
|
+
return;
|
|
61
71
|
case "Enter" :
|
|
62
72
|
if (len <= 0) {
|
|
63
73
|
return;
|
|
@@ -93,14 +103,12 @@ function Basefn__Spotlight(props) {
|
|
|
93
103
|
};
|
|
94
104
|
Xote.Effect.run(() => {
|
|
95
105
|
if (Xote.Signal.get(isOpen)) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
el.focus();
|
|
103
|
-
}, 16);
|
|
106
|
+
((requestAnimationFrame(() => {
|
|
107
|
+
requestAnimationFrame(() => {
|
|
108
|
+
const el = document.querySelector(".basefn-spotlight__input");
|
|
109
|
+
if (el) el.focus();
|
|
110
|
+
})
|
|
111
|
+
})));
|
|
104
112
|
}
|
|
105
113
|
}, undefined);
|
|
106
114
|
let renderResults = () => {
|
|
@@ -155,6 +163,7 @@ function Basefn__Spotlight(props) {
|
|
|
155
163
|
onClick: handleBackdropClick,
|
|
156
164
|
children: Xote__JSX.Elements.jsxs("div", {
|
|
157
165
|
class: "basefn-spotlight",
|
|
166
|
+
onKeyDown: handleKeyDown,
|
|
158
167
|
children: Xote__JSX.array([
|
|
159
168
|
Xote__JSX.Elements.jsxs("div", {
|
|
160
169
|
class: "basefn-spotlight__input-wrapper",
|
|
@@ -168,8 +177,7 @@ function Basefn__Spotlight(props) {
|
|
|
168
177
|
type: "text",
|
|
169
178
|
value: Xote.ReactiveProp.reactive(query),
|
|
170
179
|
placeholder: placeholder,
|
|
171
|
-
onInput: handleInput
|
|
172
|
-
onKeyDown: handleKeyDown
|
|
180
|
+
onInput: handleInput
|
|
173
181
|
})
|
|
174
182
|
])
|
|
175
183
|
}),
|