keyboard nav in tree view.
This commit is contained in:
parent
5c76f9f3a2
commit
a8a51718fc
2 changed files with 73 additions and 26 deletions
|
|
@ -1,6 +1,3 @@
|
||||||
treeNodeHeight = value path expanded \
|
|
||||||
lineH = 20;
|
|
||||||
|
|
||||||
valueLabel = v \ v
|
valueLabel = v \ v
|
||||||
| NumberValue n \ Some n
|
| NumberValue n \ Some n
|
||||||
| StringValue n \ Some n
|
| StringValue n \ Some n
|
||||||
|
|
@ -8,6 +5,9 @@ treeNodeHeight = value path expanded \
|
||||||
| FunctionValue _ \ Some "<fn>"
|
| FunctionValue _ \ Some "<fn>"
|
||||||
| _ \ None;
|
| _ \ None;
|
||||||
|
|
||||||
|
treeNodeHeight = value path expanded \
|
||||||
|
lineH = 20;
|
||||||
|
|
||||||
value
|
value
|
||||||
| RecordValue entries \ (
|
| RecordValue entries \ (
|
||||||
(contains path expanded)
|
(contains path expanded)
|
||||||
|
|
@ -27,13 +27,35 @@ treeNodeHeight = value path expanded \
|
||||||
| False \ lineH)
|
| False \ lineH)
|
||||||
| _ \ lineH;
|
| _ \ lineH;
|
||||||
|
|
||||||
|
visiblePaths = value path expanded \ value
|
||||||
|
| RecordValue entries \ (
|
||||||
|
[path, ...(contains path expanded)
|
||||||
|
| True \ flatten (map (entry \
|
||||||
|
entryPath = path & "." & entry.key;
|
||||||
|
(valueLabel entry.value)
|
||||||
|
| Some _ \ [entryPath]
|
||||||
|
| None \ visiblePaths entry.value entryPath expanded
|
||||||
|
) entries)
|
||||||
|
| False \ []])
|
||||||
|
| ListValue items \ (
|
||||||
|
[path, ...(contains path expanded)
|
||||||
|
| True \ flatten (mapWithIndex (item i \
|
||||||
|
itemPath = path & "." & (show i);
|
||||||
|
(valueLabel item)
|
||||||
|
| Some _ \ [itemPath]
|
||||||
|
| None \ visiblePaths item itemPath expanded
|
||||||
|
) items)
|
||||||
|
| False \ []])
|
||||||
|
| _ \ [path];
|
||||||
|
|
||||||
treeNode = config \
|
treeNode = config \
|
||||||
depth = config.depth;
|
depth = config.depth;
|
||||||
indent = depth * 20;
|
indent = depth * 20;
|
||||||
|
|
||||||
simple = content color \
|
simple = path content color \
|
||||||
|
selected = (config.selectedPath | Some p \ p == path | None \ False);
|
||||||
ui.positioned { x = indent, y = 0,
|
ui.positioned { x = indent, y = 0,
|
||||||
child = ui.text { content = content, color = color }
|
child = ui.text { content = content, color = (selected | True \ "white" | False \ color) }
|
||||||
};
|
};
|
||||||
|
|
||||||
valueLabel = value \ value
|
valueLabel = value \ value
|
||||||
|
|
@ -46,16 +68,16 @@ treeNode = config \
|
||||||
header = isExp label color \
|
header = isExp label color \
|
||||||
ui.clickable {
|
ui.clickable {
|
||||||
onClick = _ \ config.onToggle config.path,
|
onClick = _ \ config.onToggle config.path,
|
||||||
child = simple ((isExp | True \ "▼ " | False \ "▶ ") & label) color
|
child = simple config.path (config.prefix & (isExp | True \ "▼ " | False \ "▶ ") & label) color
|
||||||
};
|
};
|
||||||
|
|
||||||
isExp = contains config.path config.expanded;
|
isExp = contains config.path config.expanded;
|
||||||
|
|
||||||
config.value
|
config.value
|
||||||
| NumberValue n \ simple (show n) "#6cf"
|
| NumberValue n \ simple config.path (config.prefix & (show n)) "#6cf"
|
||||||
| StringValue n \ simple ("\"" & n & "\"") "#f6a"
|
| StringValue n \ simple config.path (config.prefix & "\"" & n & "\"") "#f6a"
|
||||||
| ConstructorValue { tag = tag } \ simple tag "#fc6"
|
| ConstructorValue { tag = tag } \ simple config.path (config.prefix & tag) "#fc6"
|
||||||
| FunctionValue _ \ simple "<fn>" "#888"
|
| FunctionValue _ \ simple config.path (config.prefix & "<fn>") "#888"
|
||||||
| RecordValue entries \
|
| RecordValue entries \
|
||||||
ui.column { gap = 0, children = [
|
ui.column { gap = 0, children = [
|
||||||
header isExp ((show (len entries)) & " fields") "#888",
|
header isExp ((show (len entries)) & " fields") "#888",
|
||||||
|
|
@ -63,12 +85,8 @@ treeNode = config \
|
||||||
| True \ [ui.clickable {
|
| True \ [ui.clickable {
|
||||||
onClick = _ \ noOp,
|
onClick = _ \ noOp,
|
||||||
child = ui.column { gap = 0, children = map (entry \
|
child = ui.column { gap = 0, children = map (entry \
|
||||||
(valueLabel entry.value)
|
pfx = (valueLabel entry.value) | Some _ \ " = " | None \ " ";
|
||||||
| Some label \ simple (entry.key & " = " & label) "#aaa"
|
treeNode { value = entry.value, depth = depth + 1, path = config.path & "." & entry.key, expanded = config.expanded, onToggle = config.onToggle, selectedPath = config.selectedPath, prefix = entry.key & pfx }
|
||||||
| None \ ui.column { gap = 0, children = [
|
|
||||||
simple entry.key "#aaa",
|
|
||||||
treeNode { value = entry.value, depth = depth + 1, path = config.path & "." & entry.key, expanded = config.expanded, onToggle = config.onToggle }
|
|
||||||
]}
|
|
||||||
) entries }
|
) entries }
|
||||||
}]
|
}]
|
||||||
| False \ [])
|
| False \ [])
|
||||||
|
|
@ -80,12 +98,7 @@ treeNode = config \
|
||||||
| True \ [ui.clickable {
|
| True \ [ui.clickable {
|
||||||
onClick = _ \ noOp,
|
onClick = _ \ noOp,
|
||||||
child = ui.column { gap = 0, children = mapWithIndex (item i \
|
child = ui.column { gap = 0, children = mapWithIndex (item i \
|
||||||
(valueLabel item)
|
treeNode { value = item, depth = depth + 1, path = config.path & "." & (show i), expanded = config.expanded, onToggle = config.onToggle, selectedPath = config.selectedPath, prefix = (show i) & ": " }
|
||||||
| Some label \ simple ((show i) & " : " & label) "#aaa"
|
|
||||||
| None \ ui.column { gap = 0, children = [
|
|
||||||
simple (show i) "#aaa",
|
|
||||||
treeNode { value = item, depth = depth + 1, path = config.path & "." & (show i), expanded = config.expanded, onToggle = config.onToggle }
|
|
||||||
]}
|
|
||||||
) items }
|
) items }
|
||||||
}]
|
}]
|
||||||
| False \ [])
|
| False \ [])
|
||||||
|
|
@ -94,8 +107,11 @@ treeNode = config \
|
||||||
|
|
||||||
tree = config \
|
tree = config \
|
||||||
ui.stateful {
|
ui.stateful {
|
||||||
|
focusable = True,
|
||||||
|
autoFocus = True,
|
||||||
|
|
||||||
key = "tree-" & config.path,
|
key = "tree-" & config.path,
|
||||||
init = { expanded = [], scrollY = 0 },
|
init = { expanded = [], scrollY = 0, selectedIndex = 0 },
|
||||||
|
|
||||||
update = state event \ event
|
update = state event \ event
|
||||||
| Toggle path \ ((contains path state.expanded)
|
| Toggle path \ ((contains path state.expanded)
|
||||||
|
|
@ -107,11 +123,35 @@ tree = config \
|
||||||
newY = max 0 (min (totalH - config.h) (state.scrollY + delta.deltaY));
|
newY = max 0 (min (totalH - config.h) (state.scrollY + delta.deltaY));
|
||||||
{ state = state.{ scrollY = newY }, emit = [] })
|
{ state = state.{ scrollY = newY }, emit = [] })
|
||||||
|
|
||||||
|
| Key { key = "ArrowDown" } \ (
|
||||||
|
paths = visiblePaths config.value config.path state.expanded;
|
||||||
|
newIndex = min (len paths - 1) (state.selectedIndex + 1);
|
||||||
|
_ = debug "newIndex" newIndex;
|
||||||
|
{ state = state.{ selectedIndex = newIndex }, emit = [] })
|
||||||
|
|
||||||
|
| Key { key = "ArrowUp" } \ (
|
||||||
|
# paths = visiblePaths config.value config.path state.expanded;
|
||||||
|
newIndex = max 0 (state.selectedIndex - 1);
|
||||||
|
_ = debug "newIndex" newIndex;
|
||||||
|
{ state = state.{ selectedIndex = newIndex }, emit = [] })
|
||||||
|
|
||||||
|
| Key { key = "Enter" } \ (
|
||||||
|
paths = visiblePaths config.value config.path state.expanded;
|
||||||
|
selected = nth state.selectedIndex paths;
|
||||||
|
selected
|
||||||
|
| Some path \ ((contains path state.expanded)
|
||||||
|
| True \ { state = state.{ expanded = filter (p \ p != path) state.expanded }, emit = [] }
|
||||||
|
| False \ { state = state.{ expanded = [path, ...state.expanded] }, emit = [] }
|
||||||
|
)
|
||||||
|
| None \ { state = state, emit = [] })
|
||||||
|
|
||||||
| _ \ { state = state, emit = [] },
|
| _ \ { state = state, emit = [] },
|
||||||
|
|
||||||
view = state emit \
|
view = state emit \
|
||||||
onToggle = path \ emit (Toggle path);
|
onToggle = path \ emit (Toggle path);
|
||||||
totalH = treeNodeHeight config.value config.path state.expanded;
|
totalH = treeNodeHeight config.value config.path state.expanded;
|
||||||
|
paths = visiblePaths config.value config.path state.expanded;
|
||||||
|
selectedPath = nth state.selectedIndex paths;
|
||||||
|
|
||||||
scrollable {
|
scrollable {
|
||||||
w = config.w,
|
w = config.w,
|
||||||
|
|
@ -121,6 +161,6 @@ tree = config \
|
||||||
scrollX = 0,
|
scrollX = 0,
|
||||||
scrollY = state.scrollY,
|
scrollY = state.scrollY,
|
||||||
onScroll = delta \ emit (Scrolled delta),
|
onScroll = delta \ emit (Scrolled delta),
|
||||||
child = treeNode { value = config.value, path = config.path, depth = 0, expanded = state.expanded, onToggle = onToggle }
|
child = treeNode { value = config.value, path = config.path, depth = 0, expanded = state.expanded, onToggle = onToggle, selectedPath = selectedPath, prefix = "" }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ type ComponentInstance = {
|
||||||
state: any;
|
state: any;
|
||||||
update: (state: any) => (event: any) => any;
|
update: (state: any) => (event: any) => any;
|
||||||
view: (state: any) => any;
|
view: (state: any) => any;
|
||||||
|
focusable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function runAppCompiled(canvas: HTMLCanvasElement, store: any) {
|
export function runAppCompiled(canvas: HTMLCanvasElement, store: any) {
|
||||||
|
|
@ -79,13 +80,15 @@ export function runAppCompiled(canvas: HTMLCanvasElement, store: any) {
|
||||||
instance = {
|
instance = {
|
||||||
state: ui.init,
|
state: ui.init,
|
||||||
update: ui.update,
|
update: ui.update,
|
||||||
view: ui.view
|
view: ui.view,
|
||||||
|
focusable: ui.focusable?._tag === 'True'
|
||||||
};
|
};
|
||||||
componentInstances.set(fullKey, instance);
|
componentInstances.set(fullKey, instance);
|
||||||
} else {
|
} else {
|
||||||
// refresh closures, pick up new values
|
// refresh closures, pick up new values
|
||||||
instance.update = ui.update;
|
instance.update = ui.update;
|
||||||
instance.view = ui.view;
|
instance.view = ui.view;
|
||||||
|
instance.focusable = ui.focusable?._tag === 'True'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ui.autoFocus?._tag === 'True' && isNew) {
|
if (ui.autoFocus?._tag === 'True' && isNew) {
|
||||||
|
|
@ -209,6 +212,10 @@ export function runAppCompiled(canvas: HTMLCanvasElement, store: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event._tag === 'ComponentEvent') {
|
if (event._tag === 'ComponentEvent') {
|
||||||
|
const instance = componentInstances.get(event._0);
|
||||||
|
if (instance?.focusable) {
|
||||||
|
setFocus(event._0);
|
||||||
|
}
|
||||||
handleComponentEvent(event._0, event._1);
|
handleComponentEvent(event._0, event._1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue