🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@farjs/ui

Package Overview
Dependencies
Maintainers
1
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@farjs/ui - npm Package Compare versions

Comparing version
0.6.0
to
0.6.1
+13
src/ListBox.d.ts
import { Widgets } from "@farjs/blessed";
export interface ListBoxProps {
readonly left: number;
readonly top: number;
readonly width: number;
readonly height: number;
readonly style: Widgets.Types.TStyle;
readonly items: string[];
readonly selected: number;
onAction(index: number): void;
onSelect?(index: number): void;
}
/**
* @typedef {import("@farjs/blessed").Widgets.Events.IKeyEventArg} IKeyEventArg
* @typedef {import("./ListBox").ListBoxProps} ListBoxProps
*/
import React, { useLayoutEffect, useState } from "react";
import { createListViewport } from "./ListViewport.mjs";
import ListView from "./ListView.mjs";
import ScrollBar from "./ScrollBar.mjs";
const h = React.createElement;
/**
* @param {ListBoxProps} props
*/
const ListBox = (props) => {
const { listViewComp, scrollBarComp } = ListBox;
const [viewport, setViewport] = useState(
createListViewport(props.selected, props.items.length, props.height)
);
const selected = viewport.offset + viewport.focused;
/** @type {(ch: any, key: IKeyEventArg) => void} */
const onKeypress = (_, key) => {
switch (key.full) {
case "return":
props.onAction(selected);
break;
default:
const vp = viewport.onKeypress(key.full);
if (vp) {
setViewport(vp);
}
break;
}
};
useLayoutEffect(() => {
props.onSelect?.call(null, selected);
}, [selected]);
return h(
"button",
{
left: props.left,
top: props.top,
width: props.width,
height: props.height,
onKeypress,
},
h(listViewComp, {
left: 0,
top: 0,
width: props.width,
height: props.height,
items: props.items,
viewport,
setViewport,
style: props.style,
onClick: props.onAction,
}),
viewport.length > viewport.viewLength
? h(scrollBarComp, {
left: props.width,
top: 0,
length: viewport.viewLength,
style: props.style,
value: viewport.offset,
extent: viewport.viewLength,
min: 0,
max: viewport.length - viewport.viewLength,
onChange: (offset) => {
setViewport(viewport.updated(offset));
},
})
: null
);
};
ListBox.displayName = "ListBox";
ListBox.listViewComp = ListView;
ListBox.scrollBarComp = ScrollBar;
export default ListBox;
/// <reference types="node" />
import { Widgets } from "@farjs/blessed";
import { ListViewport } from "./ListViewport";
export interface ListViewProps {
readonly left: number;
readonly top: number;
readonly width: number;
readonly height: number;
readonly items: string[];
readonly viewport: ListViewport;
setViewport(viewport: ListViewport): void;
readonly style: Widgets.Types.TStyle;
onClick(index: number): void;
}
/**
* @typedef {import("@farjs/blessed").Widgets.Types.TStyle} BlessedStyle
* @typedef {import("@farjs/blessed").Widgets.BlessedElement} BlessedElement
* @typedef {import("@farjs/blessed").Widgets.Events.IMouseEventArg} MouseEvent
* @typedef {import("./ListView").ListViewProps} ListViewProps
*/
import React, { useLayoutEffect, useRef } from "react";
import * as UI from "./UI.mjs";
import UiString from "./UiString.mjs";
const h = React.createElement;
/**
* @param {number} selected
* @param {string[]} items
* @param {number} width
* @param {BlessedStyle} theme
* @returns {string[]}
*/
function renderItems(selected, items, width, theme) {
return items.map((item, index) => {
/** @type {BlessedStyle | undefined} */
const style = selected === index ? theme.focus : theme;
const text = UiString(
item.replace("\n", "").replace("\r", "").replace("\t", " ")
);
return UI.renderText2(style, text.ensureWidth(width, " "));
});
}
/**
* @param {ListViewProps} props
*/
const ListView = (props) => {
const elementRef = /** @type {React.MutableRefObject<BlessedElement>} */ (
useRef()
);
const viewport = props.viewport;
const offset = viewport.offset;
const focused = viewport.focused;
const length = viewport.length;
const viewLength = viewport.viewLength;
const itemsContent = renderItems(
focused,
props.items.slice(offset, offset + viewLength),
props.width,
props.style
).join("\n");
useLayoutEffect(() => {
props.setViewport(viewport.resize(props.height));
}, [props.height]);
return h("text", {
ref: elementRef,
clickable: true,
mouse: true,
autoFocus: false,
left: props.left,
top: props.top,
width: props.width,
height: props.height,
style: props.style,
tags: true,
wrap: false,
content: itemsContent,
onWheelup: () => {
props.setViewport(viewport.up());
},
onWheeldown: () => {
props.setViewport(viewport.down());
},
onClick: (/** @type {MouseEvent}*/ data) => {
const curr = elementRef.current;
const y = data.y - /** @type {number} */ (curr.atop);
const index = offset + y;
if (index < length) {
props.onClick(index);
}
},
});
};
ListView.displayName = "ListView";
export default ListView;
export interface ListPopupProps {
readonly title: string;
readonly items: string[];
onAction(index: number): void;
onClose(): void;
readonly selected?: number;
onSelect?(index: number): void;
onKeypress?(keyFull: string): boolean;
readonly footer?: string;
readonly textPaddingLeft?: number;
readonly textPaddingRight?: number;
readonly itemWrapPrefixLen?: number;
}
/**
* @typedef {import("./ModalContent").BlessedPadding} BlessedPadding
* @typedef {import("./ListPopup").ListPopupProps} ListPopupProps
*/
import React from "react";
import Theme from "../theme/Theme.mjs";
import Popup from "./Popup.mjs";
import ModalContent from "./ModalContent.mjs";
import WithSize from "../WithSize.mjs";
import ListBox from "../ListBox.mjs";
import TextLine from "../TextLine.mjs";
const h = React.createElement;
/**
* @param {ListPopupProps} props
*/
const ListPopup = (props) => {
const { popupComp, modalContentComp, withSizeComp, listBoxComp } = ListPopup;
const items = props.items;
const theme = Theme.useTheme().popup.menu;
const textPaddingLeft = props.textPaddingLeft ?? 2;
const textPaddingRight = props.textPaddingRight ?? 1;
const itemWrapPrefixLen = props.itemWrapPrefixLen ?? 3;
const textPaddingLen = textPaddingLeft + textPaddingRight;
const textPaddingLeftStr = " ".repeat(textPaddingLeft);
const textPaddingRightStr = " ".repeat(textPaddingRight);
return h(
popupComp,
{
onClose: props.onClose,
onKeypress: props.onKeypress,
},
h(withSizeComp, {
render: (width, height) => {
const maxContentWidth =
items.length === 0
? 2 * (ListPopup.paddingHorizontal + 1)
: items.reduce((_1, _2) => Math.max(_1, _2.length), 0) +
2 * (ListPopup.paddingHorizontal + 1);
const maxContentHeight =
items.length + 2 * (ListPopup.paddingVertical + 1);
const modalWidth = Math.min(
Math.max(minWidth, maxContentWidth + textPaddingLen),
Math.max(minWidth, width)
);
const modalHeight = Math.min(
Math.max(minHeight, maxContentHeight),
Math.max(minHeight, height - 4)
);
const contentWidth = modalWidth - 2 * (ListPopup.paddingHorizontal + 1); // padding + border
const contentHeight = modalHeight - 2 * (ListPopup.paddingVertical + 1);
return h(
modalContentComp,
{
title: props.title,
width: modalWidth,
height: modalHeight,
style: theme,
padding: ListPopup.padding,
footer: props.footer,
},
h(listBoxComp, {
left: 1,
top: 1,
width: contentWidth,
height: contentHeight,
selected: props.selected ?? 0,
items: items.map((item) => {
return (
textPaddingLeftStr +
TextLine.wrapText(
item,
contentWidth - textPaddingLen,
itemWrapPrefixLen
) +
textPaddingRightStr
);
}),
style: theme,
onAction: (index) => {
if (items.length > 0) {
props.onAction(index);
}
},
onSelect: props.onSelect,
})
);
},
})
);
};
ListPopup.displayName = "ListPopup";
ListPopup.popupComp = Popup;
ListPopup.modalContentComp = ModalContent;
ListPopup.withSizeComp = WithSize;
ListPopup.listBoxComp = ListBox;
ListPopup.paddingHorizontal = 2;
ListPopup.paddingVertical = 1;
const minWidth = 50 + 2 * (ListPopup.paddingHorizontal + 1); // padding + border
const minHeight = 10 + 2 * (ListPopup.paddingVertical + 1);
/** @type {BlessedPadding} */
ListPopup.padding = {
left: ListPopup.paddingHorizontal,
right: ListPopup.paddingHorizontal,
top: ListPopup.paddingVertical,
bottom: ListPopup.paddingVertical,
};
export default ListPopup;
/// <reference types="node" />
import { Widgets } from "@farjs/blessed";
export interface ScrollBarProps {
readonly left: number;
readonly top: number;
readonly length: number;
readonly style: Widgets.Types.TStyle;
readonly value: number;
readonly extent: number;
readonly min: number;
readonly max: number;
onChange(value: number): void;
}
/**
* @typedef {import("./ScrollBar").ScrollBarProps} ScrollBarProps
*/
import React from "react";
const h = React.createElement;
/**
* @param {ScrollBarProps} props
*/
const ScrollBar = (props) => {
const unitIncrement = 1;
const blockIncrement = Math.max(props.extent, 1);
const barLength = Math.max(props.length, 2) - 2;
const min = Math.max(props.min, 0);
const max = Math.max(props.max, 0);
const value = Math.min(Math.max(props.value, min), max);
const markerLength = 1;
const upLength =
value === min
? 0
: value === max
? barLength - markerLength
: Math.max(
Math.min(
Math.trunc((value * barLength) / Math.max(max - min, 1)),
barLength - markerLength - 1
),
1
);
const downLength = barLength - upLength - markerLength;
return h(
React.Fragment,
null,
h("text", {
width: 1,
height: 1,
left: props.left,
top: props.top,
clickable: true,
mouse: true,
autoFocus: false,
style: props.style,
onClick: () => {
props.onChange(Math.max(props.value - unitIncrement, min));
},
content: ScrollBar.upArrowCh,
}),
h("text", {
width: 1,
height: upLength,
left: props.left,
top: props.top + 1,
clickable: true,
mouse: true,
autoFocus: false,
style: props.style,
onClick: () => {
props.onChange(Math.max(props.value - blockIncrement, min));
},
content: ScrollBar.scrollCh.repeat(upLength),
}),
h("text", {
width: 1,
height: markerLength,
left: props.left,
top: props.top + 1 + upLength,
autoFocus: false,
style: props.style,
content: ScrollBar.markerCh,
}),
h("text", {
width: 1,
height: downLength,
left: props.left,
top: props.top + 1 + upLength + markerLength,
clickable: true,
mouse: true,
autoFocus: false,
style: props.style,
onClick: () => {
props.onChange(Math.min(props.value + blockIncrement, max));
},
content: ScrollBar.scrollCh.repeat(downLength),
}),
h("text", {
width: 1,
height: 1,
left: props.left,
top: props.top + 1 + upLength + markerLength + downLength,
clickable: true,
mouse: true,
autoFocus: false,
style: props.style,
onClick: () => {
props.onChange(Math.min(props.value + unitIncrement, max));
},
content: ScrollBar.downArrowCh,
})
);
};
ScrollBar.displayName = "ScrollBar";
ScrollBar.markerCh = "\u2588"; // █
ScrollBar.scrollCh = "\u2591"; // ░
ScrollBar.upArrowCh = "\u25b2"; // ▲
ScrollBar.downArrowCh = "\u25bc"; // ▼
export default ScrollBar;
+1
-1
{
"name": "@farjs/ui",
"author": "viktor-podzigun",
"version": "0.6.0",
"version": "0.6.1",
"license": "MIT",

@@ -6,0 +6,0 @@ "description": "Terminal UI React.js components library",

/// <reference types="node" />
import { Widgets } from "blessed";
import { Widgets } from "@farjs/blessed";

@@ -5,0 +5,0 @@ export interface ButtonProps {

@@ -1,2 +0,2 @@

import { Widgets } from "blessed";
import { Widgets } from "@farjs/blessed";

@@ -3,0 +3,0 @@ export interface ButtonsPanelAction {