diff --git a/README.md b/README.md
index e20b971..765d4f6 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,9 @@ JSWindowで囲むだけで、そこが仮想ウインドウ化します
## 3.links
+- WebSite
+[https://ttis.croud.jp/?uuid=b292d429-dbad-49b5-8fed-6d268f4feaf0](https://ttis.croud.jp/?uuid=b292d429-dbad-49b5-8fed-6d268f4feaf0)
+
- Source code
[https://github.com/JavaScript-WindowFramework/jswf-react](https://github.com/JavaScript-WindowFramework/jswf-react)
@@ -113,13 +116,12 @@ ReactDOM.render(, document.getElementById("root") as HTMLElement);
- 重ね合わせ
- 親子ウインドウ
- 画面分割
+- リストビュー
## 6.コンポーネント
### 6.1 **JSWindow**
-
-
#### Propsパラメータ
| Name | Type | Info |
@@ -152,12 +154,8 @@ WindowState.MAX
WindowState.MIN
WindowState.HIDE
-
-
### 6.2 **SplitView**
-
-
#### Propsパラメータ
| Name | Type | Info |
@@ -169,8 +167,6 @@ WindowState.HIDE
| bold | number | Bar thickness |
| style | React.CSSProperties | CSS |
-
-
## 7.ライセンス
MIT
diff --git a/images/file.png b/images/file.png
new file mode 100644
index 0000000..1325bf3
Binary files /dev/null and b/images/file.png differ
diff --git a/images/talone.svg b/images/talone.svg
new file mode 100644
index 0000000..cc99ea6
--- /dev/null
+++ b/images/talone.svg
@@ -0,0 +1,15 @@
+
+
+
diff --git a/images/tclose.svg b/images/tclose.svg
new file mode 100644
index 0000000..7ca11e8
--- /dev/null
+++ b/images/tclose.svg
@@ -0,0 +1,16 @@
+
+
+
diff --git a/images/topen.svg b/images/topen.svg
new file mode 100644
index 0000000..db7eeff
--- /dev/null
+++ b/images/topen.svg
@@ -0,0 +1,15 @@
+
+
+
diff --git a/package.json b/package.json
index f8dbaeb..33bc717 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@jswf/react",
- "version": "0.1.1",
+ "version": "0.2.1",
"description": "Virtual Window for React",
"main": "dist/index.js",
"scripts": {
diff --git a/src/JSWindow/index.tsx b/src/JSWindow/index.tsx
index f90f540..f8f16ab 100644
--- a/src/JSWindow/index.tsx
+++ b/src/JSWindow/index.tsx
@@ -29,7 +29,7 @@ export interface WindowProps {
windowStyle?: number;
windowState?: WindowState;
onUpdate?: ((status: WindowInfo) => void) | null;
- clientStyle?:React.CSSProperties;
+ clientStyle?: React.CSSProperties;
}
type NonNullableType = {
[P in K]-?: T[P];
@@ -93,7 +93,7 @@ export class JSWindow extends Component {
overlapped: true,
windowStyle: 0xff,
windowState: WindowState.NORMAL,
- clientStyle:{},
+ clientStyle: {},
onUpdate: null
};
@@ -119,10 +119,9 @@ export class JSWindow extends Component {
state = {
active: props.active!,
overlapped: props.overlapped!,
- titlePrmisson:props.windowStyle!,
- titleSize:(props.windowStyle! & WindowStyle.TITLE) === 0
- ? 0
- : props.titleSize!,
+ titlePrmisson: props.windowStyle!,
+ titleSize:
+ (props.windowStyle! & WindowStyle.TITLE) === 0 ? 0 : props.titleSize!,
borderSize: props.borderSize!,
x: props.x!,
y: props.y!,
@@ -154,7 +153,7 @@ export class JSWindow extends Component {
onUpdate: props.onUpdate!,
clientWidth: 0,
clientHeight: 0,
- clientStyle:props.clientStyle!,
+ clientStyle: props.clientStyle!,
realX: 0,
realY: 0,
realWidth: 0,
@@ -411,6 +410,7 @@ export class JSWindow extends Component {
/>
))}
{
) {
if (Manager.moveNode == null) {
this.foreground();
- Manager.moveNode = this.rootRef.current;
- let p = Manager.getPos((e as unknown) as MouseEvent | TouchEvent);
- Manager.baseX = p.x;
- Manager.baseY = p.y;
- Manager.nodeX = this.windowInfo.realX;
- Manager.nodeY = this.windowInfo.realY;
- Manager.nodeWidth = this.windowInfo.realWidth;
- Manager.nodeHeight = this.windowInfo.realHeight;
- e.stopPropagation();
+ if (this.props.moveable || Manager.frame) {
+ Manager.moveNode = this.rootRef.current;
+ let p = Manager.getPos((e as unknown) as MouseEvent | TouchEvent);
+ Manager.baseX = p.x;
+ Manager.baseY = p.y;
+ Manager.nodeX = this.windowInfo.realX;
+ Manager.nodeY = this.windowInfo.realY;
+ Manager.nodeWidth = this.windowInfo.realWidth;
+ Manager.nodeHeight = this.windowInfo.realHeight;
+ e.stopPropagation();
+ }
} else {
- e.preventDefault();
+ // e.preventDefault();
}
}
//フレームクリックイベントの処理
@@ -626,8 +628,8 @@ export class JSWindow extends Component {
.call(parent.childNodes, 0)
.filter(node => {
return (
- (node as typeof node & { _symbol?: JSWindow })
- ._symbol instanceof JSWindow
+ (node as typeof node & { _symbol?: JSWindow })._symbol instanceof
+ JSWindow
);
})
.sort((a, b) => {
diff --git a/src/ListView/index.tsx b/src/ListView/index.tsx
index 7450751..7bc3086 100644
--- a/src/ListView/index.tsx
+++ b/src/ListView/index.tsx
@@ -1,3 +1,4 @@
+import ResizeObserver from "resize-observer-polyfill";
import React, { Component, ReactNode, ReactElement, createRef } from "react";
import { Root } from "./parts/Root";
import { Headers } from "./parts/Header/Headers";
@@ -7,6 +8,12 @@ interface Props {
children?: ReactNode;
onItemClick?: (row: number, col: number) => void;
onItemDoubleClick?: (row: number, col: number) => void;
+ onItemDragStart?: (e: React.DragEvent, row: number, col: number) => void;
+ onItemDragEnter?: (e: React.DragEvent, row: number, col: number) => void;
+ onItemDragLeave?: (e: React.DragEvent, row: number, col: number) => void;
+ onItemDragOver?: (e: React.DragEvent, row: number, col: number) => void;
+ onItemDrop?: (e: React.DragEvent, row: number, col: number) => void;
+ onDrop?: (e: React.DragEvent) => void;
}
interface State {
xScroll: number;
@@ -15,7 +22,15 @@ interface State {
sortOrder?: boolean;
sortType?: string;
selectItems: Set;
+ clientWidth?: number;
}
+/**
+ *WindowsライクなListView
+ *
+ * @export
+ * @class ListView
+ * @extends {Component}
+ */
export class ListView extends Component {
static defaultProps = { children: [] };
state: State = {
@@ -24,11 +39,12 @@ export class ListView extends Component {
sortIndex: -1,
selectItems: new Set()
};
- rootRef = createRef();
- itemsRef = createRef();
- headersRef = createRef();
+ private resizeObserver?: ResizeObserver;
+ private rootRef = createRef();
+ private itemsRef = createRef();
+ private headersRef = createRef();
- render() {
+ public render(): JSX.Element {
const children = React.Children.toArray(
this.props.children
) as React.ReactElement[];
@@ -41,11 +57,13 @@ export class ListView extends Component {
return (
{
this.setState({ xScroll: this.rootRef.current!.scrollLeft });
}}
>
this.setState({ headerSizes })}
@@ -54,7 +72,9 @@ export class ListView extends Component {
{
headerSizes={this.state.headerSizes}
onClick={this.onItemClick.bind(this)}
onDoubleClick={this.onItemDoubleClick.bind(this)}
+ onItemDragStart={this.props.onItemDragStart}
+ onItemDragEnter={this.props.onItemDragEnter}
+ onItemDragLeave={this.props.onItemDragLeave}
+ onItemDragOver={this.props.onItemDragOver}
+ onItemDrop={this.props.onItemDrop}
>
{items}
);
}
- onHeaderClick(sortIndex: number) {
+ public componentDidMount(): void {
+ this.resizeObserver = new ResizeObserver(() => {
+ this.layout();
+ });
+ this.resizeObserver.observe(this.rootRef.current! as Element);
+ this.layout();
+ }
+ public componentWillUnmount(): void {
+ if (this.resizeObserver) {
+ this.resizeObserver.disconnect();
+ this.resizeObserver = undefined;
+ }
+ }
+ /**
+ *レイアウト処理
+ *
+ * @protected
+ * @memberof SplitView
+ */
+ protected layout(): void {
+ this.setState({ clientWidth: this.rootRef.current!.clientWidth });
+ if (this.rootRef.current) {
+ this.rootRef.current.scrollLeft = 0;
+ }
+ }
+ protected onHeaderClick(sortIndex: number): void {
let sortOrder;
if (this.state.sortIndex === sortIndex) {
sortOrder = !this.state.sortOrder;
@@ -81,7 +131,7 @@ export class ListView extends Component {
.current!.getType();
this.setState({ sortOrder, sortIndex, sortType });
}
- onItemClick(e: React.MouseEvent, row: number, col: number) {
+ protected onItemClick(e: React.MouseEvent, row: number, col: number): void {
const selectItems = this.state.selectItems;
if (e.ctrlKey) {
if (!selectItems.has(row)) selectItems.add(row);
@@ -104,34 +154,95 @@ export class ListView extends Component {
this.props.onItemClick(row, col);
}
}
- onItemDoubleClick(e: React.MouseEvent, row: number, col: number) {
+ protected onItemDoubleClick(
+ e: React.MouseEvent,
+ row: number,
+ col: number
+ ): void {
if (this.props.onItemDoubleClick) {
this.props.onItemDoubleClick(row, col);
}
}
- getSelectItem() {
+ /**
+ *選択中の最初のアイテムを返す
+ *
+ * @returns 0<=:アイテム番号 -1:選択無し
+ * @memberof ListView
+ */
+ public getSelectItem(): number {
const selectItems = this.state.selectItems;
- if (selectItems.size) return selectItems.values().next();
+ if (selectItems.size) return selectItems.values().next().value;
return -1;
}
- getSelectItems() {
+ /**
+ *選択中のアイテムを配列で返す
+ *
+ * @returns 選択中のアイテム番号の配列
+ * @memberof ListView
+ */
+ public getSelectItems(): number[] {
return Array.from(this.state.selectItems.values());
}
- getItem(row: number, col: number): React.ReactNode | undefined {
+ /**
+ *アイテムの内容を返す
+ *
+ * @param {number} row
+ * @param {number} col
+ * @returns {(React.ReactNode | undefined)} 内容
+ * @memberof ListView
+ */
+ public getItem(row: number, col: number): React.ReactNode | undefined {
const itemValues = this.itemsRef.current!.getItemValues();
if (row >= itemValues.length) return undefined;
return itemValues[row][col];
}
- getRows() {
+ /**
+ *アイテムの内容を変更する
+ *
+ * @param {number} row
+ * @param {number} col
+ * @param {ReactNode} value
+ * @memberof ListView
+ */
+ public setItem(row: number, col: number, value: ReactNode): void {
+ const itemValues = this.itemsRef.current!.getItemValues();
+ if (row < itemValues.length) itemValues[row][col] = value;
+ this.forceUpdate();
+ }
+ /**
+ *アイテム数を返す
+ *
+ * @returns
+ * @memberof ListView
+ */
+ public getRows(): number {
return this.itemsRef.current!.getItemValues().length;
}
- getCols() {
+ /**
+ *アイテムのカラム数を返す
+ *
+ * @returns
+ * @memberof ListView
+ */
+ public getCols(): number {
return this.state.headerSizes.length;
}
- addItem(item: ReactNode[]) {
+ /**
+ *アイテムの追加
+ *
+ * @param {ReactNode[]} item 追加するアイテム
+ * @memberof ListView
+ */
+ public addItem(item: ReactNode[]): void {
this.itemsRef.current!.addItem(item);
}
- removeItem(row: number) {
+ /**
+ *アイテムの削除
+ *
+ * @param {number} row 削除するレコード番号
+ * @memberof ListView
+ */
+ public removeItem(row: number): void {
this.itemsRef.current!.removeItem(row);
this.state.selectItems.clear();
}
diff --git a/src/ListView/parts/Header/Header.tsx b/src/ListView/parts/Header/Header.tsx
index 0b1d6b3..d945f42 100644
--- a/src/ListView/parts/Header/Header.tsx
+++ b/src/ListView/parts/Header/Header.tsx
@@ -6,24 +6,32 @@ interface HeaderProps {
onSize: () => void;
onClick: () => void;
}
-interface HeaderStatus {
+interface HeaderState {
width: number;
+ tempWidth: number;
}
-export class Header extends Component {
+/**
+ *ListViewヘッダークラス
+ *
+ * @export
+ * @class Header
+ * @extends {Component}
+ */
+export class Header extends Component {
static defaultProps = {
minWidth: 60
};
- state = { width: -1 };
- type: string = "string";
- labelRef = createRef();
- sliderRef = createRef();
- render() {
+ state: HeaderState = { width: -1,tempWidth:0 };
+ private type: string = "string";
+ private labelRef = createRef();
+ private sliderRef = createRef();
+ public render() {
const child = this.props.children as ReactElement;
const label = child.props.children;
return (
@@ -63,15 +71,15 @@ export class Header extends Component
{
this.props.onSize();
});
}
- public componentDitUnmount() {
+ public componentWillUnmount() {
const node = this.sliderRef.current!;
node.removeEventListener("move", this.onMove.bind(this));
}
public getWidth() {
- return this.state.width;
+ return Math.max(this.state.width,this.state.tempWidth);
}
- public setWidth(width:number) {
- return this.setState({width});
+ public getTempWidth() {
+ return this.state.tempWidth;
}
public getType() {
return this.type;
diff --git a/src/ListView/parts/Header/Headers.tsx b/src/ListView/parts/Header/Headers.tsx
index 791fe26..0e78179 100644
--- a/src/ListView/parts/Header/Headers.tsx
+++ b/src/ListView/parts/Header/Headers.tsx
@@ -6,67 +6,80 @@ interface HeadersProps {
onSize: (headers: number[]) => void;
onClick: (index: number) => void;
children: ReactNode;
+ clientWidth?: number;
}
+/**
+ *ListViewヘッダー管理クラス
+ *
+ * @export
+ * @class Headers
+ * @extends {Component}
+ */
export class Headers extends Component {
- headers: RefObject[] = [];
- values:ReactNode[] = [];
- componentDidMount() {
+ private headers: RefObject[] = [];
+ private values: ReactNode[] = [];
+ private rootRef = createRef();
+ public componentDidMount() {
this.values = React.Children.toArray(this.props.children);
this.onSize();
}
- componentDidUpdate(){
- const headerWidth = this.rootRef.current!.offsetWidth;
- const width = this.headers.reduce((a,b)=>{
- return a-b.current!.getWidth();
- },this.rootRef.current!.offsetWidth);
- console.log(headerWidth);
- const index = this.headers.length -1;
- if(index >= 0 && width){
- const header = this.headers[index].current!;
- header.setState({width:header.getWidth()+width},()=>{ this.onSize();});
+ public componentDidUpdate() {
+ const clientWidth = this.props.clientWidth;
+ if (this.props.clientWidth !== undefined) {
+ const width = this.headers.reduce((a, b) => {
+ const w = b.current!.getWidth();
+ if (w < 0) a = -9999999;
+ return a - b.current!.getWidth();
+ }, clientWidth!);
+ const index = this.headers.length - 1;
+ if (index >= 0) {
+ const header = this.headers[index].current!;
+ let tempWidth = header.getTempWidth() + width;
+ if (tempWidth < 0) tempWidth = 0;
+ if (header.state.tempWidth !== tempWidth) {
+ header.setState({ tempWidth });
+ }
+ }
}
}
- rootRef = createRef();
- render() {
+ public render() {
this.headers = [];
return (
- {this.values.map(
- (element: ReactNode, index) => {
- const refHeader = createRef();
- this.headers.push(refHeader);
- return (
- {
- this.onClick(index);
- }}
- onSize={() => this.onSize()}
- >
- {element}
-
- );
- }
- )}
+ {this.values.map((element: ReactNode, index) => {
+ const refHeader = createRef();
+ this.headers.push(refHeader);
+ return (
+ {
+ this.onClick(index);
+ }}
+ onSize={() => this.onSize()}
+ >
+ {element}
+
+ );
+ })}
);
}
- onSize() {
+ protected onSize() {
const headerSizes = this.headers.map(headerRef => {
if (headerRef.current) return headerRef.current.getWidth();
return -1;
});
if (headerSizes.indexOf(-1) === -1) this.props.onSize(headerSizes);
}
- onClick(index: number) {
+ protected onClick(index: number) {
this.props.onClick(index);
}
- getHeader(index: number) {
+ public getHeader(index: number) {
return this.headers[index];
}
- getTypes() {
+ public getTypes() {
return this.headers.map(header => {
return header.current!.getType();
});
diff --git a/src/ListView/parts/Header/Root.ts b/src/ListView/parts/Header/Root.ts
index 1a407e6..2af583b 100644
--- a/src/ListView/parts/Header/Root.ts
+++ b/src/ListView/parts/Header/Root.ts
@@ -13,28 +13,28 @@ export const Root = styled.div.attrs(p => ({
min-width: 100%;
background-image: linear-gradient(
180deg,
- rgba(144, 197, 240, 0.9) 0%,
- rgba(63, 164, 201, 0.9) 50%,
- rgba(100, 122, 221, 0.9) 100%
+ rgb(144, 197, 240) 0%,
+ rgb(63, 164, 201) 50%,
+ rgb(100, 122, 221) 100%
);
> div {
#back {
- display:flex;
+ display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: linear-gradient(
180deg,
- rgba(144, 197, 240, 0.9) 0%,
- rgba(63, 164, 201, 0.9) 50%,
- rgba(100, 122, 221, 0.9) 100%
+ rgb(144, 197, 240) 0%,
+ rgb(63, 164, 201) 50%,
+ rgb(100, 122, 221) 100%
);
&:hover {
background-image: linear-gradient(
0deg,
- rgba(144, 197, 240, 0.9) 0%,
- rgba(63, 164, 201, 0.9) 50%,
- rgba(100, 122, 221, 0.9) 100%
+ rgb(144, 197, 240) 0%,
+ rgb(63, 164, 201) 50%,
+ rgb(100, 122, 221) 100%
);
}
}
@@ -53,10 +53,16 @@ export const Root = styled.div.attrs(p => ({
cursor: ew-resize;
position: absolute;
top: 0px;
- right: -12px;
+ right: -16px;
width: 32px;
height: 100%;
// background-color: rgba(255, 255, 255, 0.4);
}
+ &:last-child {
+ > #slider {
+ right: 0;
+ width: 16px;
+ }
+ }
}
`;
diff --git a/src/ListView/parts/Item/Items.tsx b/src/ListView/parts/Item/Items.tsx
index 26cf320..7e60552 100644
--- a/src/ListView/parts/Item/Items.tsx
+++ b/src/ListView/parts/Item/Items.tsx
@@ -8,6 +8,7 @@ import React, {
} from "react";
import { Root } from "./Root";
import { Item } from "./Item";
+import imgFile from "../../../../images/file.png";
interface ItemColumnProps {
width: number;
@@ -27,9 +28,14 @@ interface ItemsProps {
sortOrder?: boolean;
sortIndex?: number;
sortType?: string;
- selectItems: Set;
+ selectItems: Set;
onClick: (e: React.MouseEvent, row: number, col: number) => void;
onDoubleClick: (e: React.MouseEvent, row: number, col: number) => void;
+ onItemDragStart?: (e: React.DragEvent, row: number, col: number) => void;
+ onItemDragEnter?: (e: React.DragEvent, row: number, col: number) => void;
+ onItemDragLeave?: (e: React.DragEvent, row: number, col: number) => void;
+ onItemDragOver?: (e: React.DragEvent, row: number, col: number) => void;
+ onItemDrop?: (e: React.DragEvent, row: number, col: number) => void;
}
interface State {
values: ReactNode[][];
@@ -37,15 +43,34 @@ interface State {
export class Items extends Component {
state: State = { values: [] };
- items: RefObject[][] = [];
- sortOrder?: boolean;
- sortIndex?: number;
- values: ReactNode[][] = [];
+ private rootRef: RefObject = createRef();
+ private columnsRef: RefObject[] = [];
+ private itemsRef: RefObject[][] = [];
+ private sortOrder?: boolean;
+ private sortIndex?: number;
+ private values: ReactNode[][] = [];
+ private fileImage?: HTMLImageElement;
- componentDidUpdate() {
- this.items.forEach((row, index) => {
+ public componentDidUpdate() {
+ //カラムのスクロールバー幅修正
+ if (this.columnsRef.length) {
+ const rootWidth = this.rootRef.current!.clientWidth + this.props.xScroll;
+ const columnWidth = this.columnsRef.reduce((a, b) => {
+ return a - b.current!.offsetWidth;
+ }, rootWidth);
+ if (columnWidth) {
+ const node = this.columnsRef[this.columnsRef.length - 1].current!;
+ node.style.width = node.offsetWidth + columnWidth + "px";
+ }
+ }
+
+ //アイテム選択と高さ設定
+ this.itemsRef.forEach((row, index) => {
let height = row.reduce((a, b) => {
- return Math.max(a, b.current!.offsetHeight);
+ return Math.max(
+ a,
+ (b.current!.childNodes[0] as HTMLDivElement).offsetHeight
+ );
}, 0);
const select = this.props.selectItems.has(index);
row.forEach(item => {
@@ -55,7 +80,11 @@ export class Items extends Component {
});
});
}
- componentDidMount() {
+ public componentDidMount() {
+ this.fileImage = document.createElement("img");
+ this.fileImage.src = imgFile;
+ this.fileImage.style.height = "64px";
+
const values: ReactNode[][] = [];
React.Children.forEach(this.props.children, children => {
const value: ReactNode[] = [];
@@ -73,7 +102,7 @@ export class Items extends Component {
this.sort();
}
- render() {
+ public render() {
if (
this.sortOrder !== this.props.sortOrder ||
this.sortIndex !== this.props.sortIndex
@@ -85,25 +114,28 @@ export class Items extends Component {
});
}
- this.items = [];
+ this.itemsRef = [];
+ this.columnsRef = [];
return (
-
+ e.preventDefault()}>
{this.props.headerSizes.map((size, cols) => {
+ this.columnsRef[cols] = createRef();
return (
-
+
{this.state.values.map((items, rows) => {
const ref = createRef();
- if (this.items[rows]) this.items[rows][cols] = ref;
- else this.items[rows] = [ref];
+ if (this.itemsRef[rows]) this.itemsRef[rows][cols] = ref;
+ else this.itemsRef[rows] = [ref];
let type = "flex-start";
- if(this.props.headerTypes[cols] === "number")
- type="flex-end";
+ if (this.props.headerTypes[cols] === "number")
+ type = "flex-end";
return (
- {
this.onOver(rows, true);
}}
@@ -116,8 +148,23 @@ export class Items extends Component {
onDoubleClick={e => {
this.props.onDoubleClick(e, rows, cols);
}}
+ onDragStart={e => {
+ this.onDragStart(e, rows, cols);
+ }}
+ onDragLeave={e => {
+ this.onDragLeave(e, rows, cols);
+ }}
+ onDragEnter={e => {
+ this.onDragEnter(e, rows, cols);
+ }}
+ onDragOver={e => {
+ this.onDragOver(e, rows, cols);
+ }}
+ onDrop={e => {
+ this.onDrop(e, rows, cols);
+ }}
>
- {items[cols]}
+
{items[cols]}
);
})}
@@ -128,14 +175,14 @@ export class Items extends Component {
);
}
- onOver(index: number, flag: boolean) {
- const rows = this.items[index];
+ protected onOver(index: number, flag: boolean) {
+ const rows = this.itemsRef[index];
for (const item of rows) {
if (flag) item.current!.classList.add("Hover");
else item.current!.classList.remove("Hover");
}
}
- sort() {
+ protected sort() {
const sortIndex = this.props.sortIndex;
if (sortIndex === undefined || sortIndex < 0) return;
const sortOrder = this.props.sortOrder;
@@ -160,14 +207,45 @@ export class Items extends Component {
}
this.setState({ values: this.values });
}
- getItemValues() {
+
+ protected onDragStart(e: React.DragEvent, row: number, col: number) {
+ if (!this.props.selectItems.has(row)) {
+ e.preventDefault();
+ return;
+ }
+ if (this.props.onItemDragStart) this.props.onItemDragStart(e, row, col);
+ e.dataTransfer.effectAllowed = "move";
+ e.dataTransfer.setDragImage(this.fileImage!, 10, 10);
+ const rows = Array.from(this.props.selectItems.values());
+ const items = rows.map((item)=>{
+ return this.values[item];
+ })
+ e.dataTransfer.setData("text/plain",JSON.stringify(items));
+ }
+ protected onDragLeave(e: React.DragEvent, row: number, col: number) {
+ if (this.props.onItemDragLeave) this.props.onItemDragLeave(e, row, col);
+ }
+ protected onDragEnter(e: React.DragEvent, row: number, col: number) {
+ if (this.props.onItemDragEnter) this.props.onItemDragEnter(e, row, col);
+ e.preventDefault();
+ }
+ protected onDragOver(e: React.DragEvent, row: number, col: number) {
+ if (this.props.onItemDragOver) this.props.onItemDragOver(e, row, col);
+ e.preventDefault();
+ }
+ protected onDrop(e: React.DragEvent, row: number, col: number) {
+ if (this.props.onItemDrop) this.props.onItemDrop(e, row, col);
+ e.preventDefault();
+ }
+
+ public getItemValues() {
return this.state.values;
}
- addItem(item: ReactNode[]) {
+ public addItem(item: ReactNode[]) {
this.values.push(item);
this.setState({ values: this.values });
}
- removeItem(row: number) {
+ public removeItem(row: number) {
this.values.splice(row, 1);
this.setState({ values: this.values });
}
diff --git a/src/ListView/parts/Root.tsx b/src/ListView/parts/Root.tsx
index 2054249..fb0052e 100644
--- a/src/ListView/parts/Root.tsx
+++ b/src/ListView/parts/Root.tsx
@@ -1,16 +1,14 @@
import styled from "styled-components";
-interface StyleProps{
-
-}
+interface StyleProps {}
export const Root = styled.div.attrs(p => ({
style: {}
}))`
-position:absolute;
-overflow-x:auto;
-overflow-y:hidden;
-width:100%;
-height:100%;
-display:flex;
-flex-direction:column;
-`;
\ No newline at end of file
+ position: absolute;
+ overflow-x: auto;
+ overflow-y: hidden;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+`;
diff --git a/src/TreeView/Item/TreeItem.style.ts b/src/TreeView/Item/TreeItem.style.ts
new file mode 100644
index 0000000..3461aa6
--- /dev/null
+++ b/src/TreeView/Item/TreeItem.style.ts
@@ -0,0 +1,90 @@
+import styled from "styled-components";
+
+interface StyleProps {}
+
+const lineSize = 1.6;
+
+export const Root = styled.div.attrs(p => ({
+ style: {}
+}))`
+ position: relative;
+
+ #icon {
+ cursor: pointer;
+ box-sizing: border-box;
+ margin: ${lineSize*0.2}em;
+ width: ${lineSize*0.8}em;
+ }
+ #item {
+ border-radius: 4px;
+ cursor: default;
+ flex: 1;
+ display: flex;
+ flex-wrap: nowrap;
+ align-items: center;
+ &:hover {
+ background-color: rgba(200, 230, 250, 0.4);
+ }
+ &.select {
+ background-color: rgba(100, 150, 250, 0.4);
+ &:hover {
+ background-color: rgba(100, 150, 250, 0.5);
+ }
+ }
+ }
+ #label {
+ flex: 1;
+ flex-wrap: nowrap;
+ word-break: break-all;
+ margin: 0.1em;
+ }
+ #child {
+ position: relative;
+ > div {
+ display: flex;
+ }
+ }
+ #children{
+ flex:1;
+ }
+ #line {
+ width:${lineSize/2}em;
+ margin-right:${lineSize/2}em;
+ bottom: 0;
+ flex-grow: 0;
+ flex-shrink: 0;
+ border-right:solid 1px;
+ }
+
+ .close {
+ > div {
+ animation: treeClose 0.5s ease 0s forwards;
+ }
+ }
+ .open {
+ > div {
+ animation: treeOpen 0.1s ease 0s normal;
+ }
+ }
+
+ @keyframes treeOpen {
+ 0% {
+ margin-top: -100%;
+ }
+
+ 100% {
+ margin-top: 0%;
+ }
+ }
+
+ @keyframes treeClose {
+ 0% {
+ height: auto;
+ margin-top: 0;
+ }
+
+ 100% {
+ margin-top: -100%;
+ }
+ }
+`;
diff --git a/src/TreeView/Item/TreeItem.tsx b/src/TreeView/Item/TreeItem.tsx
new file mode 100644
index 0000000..e425e3e
--- /dev/null
+++ b/src/TreeView/Item/TreeItem.tsx
@@ -0,0 +1,226 @@
+import React, {
+ Component,
+ ReactNode,
+ ReactElement,
+ createRef,
+ RefObject
+} from "react";
+import imgAlone from "../../../images/talone.svg";
+import imgClose from "../../../images/tclose.svg";
+import imgOpen from "../../../images/topen.svg";
+import { Root } from "./TreeItem.style";
+import { TreeView } from "..";
+interface Props {
+ children?: ReactNode;
+ label?: ReactNode;
+ expand?: boolean;
+ value?: unknown;
+ treeView?: TreeView;
+ parent?: TreeItem;
+ onItemClick?: (item: TreeItem) => void;
+ onDoubleClick?: (item: TreeItem) => void;
+}
+interface State {
+ expand: boolean;
+ label: ReactNode;
+ value: unknown;
+ select: boolean;
+ checked: boolean;
+ items: TreeItem[];
+}
+
+export class TreeItem extends Component {
+ static defaultProps = { label: "", expand: true };
+
+ childRef: RefObject = createRef();
+ itemsRef: RefObject[] = [];
+ keepProps: Props;
+ constructor(props: Props) {
+ super(props);
+ this.keepProps = props;
+ //子アイテムの抽出
+ const items = React.Children.toArray(this.props.children).filter(item => {
+ return (item as ReactElement).type === TreeItem;
+ }) as TreeItem[];
+
+ this.state = {
+ items: items,
+ select: false,
+ checked: false,
+ expand: this.props.expand!,
+ label: this.props.label,
+ value: this.props.value
+ };
+ }
+ componentDidUpdate() {
+ if (this.keepProps.children !== this.props.children) {
+ const items = React.Children.toArray(this.props.children).filter(item => {
+ return (item as ReactElement).type === TreeItem;
+ }) as TreeItem[];
+ this.setState({items});
+ this.keepProps = this.props;
+ }
+ }
+ render() {
+ this.itemsRef = this.state.items.map(() => {
+ return createRef();
+ });
+ for (const item of this.state.items) console.log(item.props.label);
+ return (
+
+ {
+ this.props.onItemClick && this.props.onItemClick(this);
+ if (this.props.treeView) {
+ this.props.treeView.selectItem(this);
+ this.props.treeView.props.onItemClick &&
+ this.props.treeView.props.onItemClick(this);
+ }
+ }}
+ onDoubleClick={() => {
+ this.props.onDoubleClick && this.props.onDoubleClick(this);
+ this.props.treeView &&
+ this.props.treeView.props.onItemDoubleClick &&
+ this.props.treeView.props.onItemDoubleClick(this);
+ }}
+ >
+
![]()
{
+ const expand = !this.state.expand;
+ this.setState({ expand });
+ e.stopPropagation();
+ //e.preventDefault();
+ if (expand) this.childRef.current!.style.display = "block";
+ }}
+ id="icon"
+ src={
+ React.Children.count(this.props.children) === 0
+ ? imgAlone
+ : this.state.expand
+ ? imgOpen
+ : imgClose
+ }
+ />
+
{
+ e.stopPropagation();
+ }}
+ value=""
+ onChange={() => this.setChecked(!this.state.checked)}
+ />
+
{this.state.label}
+
+ {
+ this.childRef.current!.style.overflow = "hidden";
+ }}
+ onAnimationEnd={() => {
+ this.childRef.current!.style.overflow = "visible";
+ if (!this.state.expand)
+ this.childRef.current!.style.display = "none";
+ }}
+ >
+
+
+
+ {this.state.items.map((item, index) => (
+
+ ))}
+
+
+
+
+ );
+ }
+ getLabel() {
+ return this.state.label;
+ }
+ setLabel(label: ReactNode) {
+ this.setState({ label: label });
+ }
+ findItem(value: unknown): TreeItem | null {
+ if (this.state.value === value) return this;
+ for (const item of this.state.items) {
+ const target = item.findItem(value);
+ if (target) return target;
+ }
+ return null;
+ }
+ findItems(value: unknown): TreeItem[] {
+ const items: TreeItem[] = [];
+ if (this.state.value === value) items.push(this);
+ for (const item of this.state.items) {
+ items.push(...item.findItems(value));
+ }
+ return items;
+ }
+ addItem(props: Props) {
+ this.state.items.push(new TreeItem(props));
+ this.forceUpdate();
+ }
+ delItem(item: TreeItem) {
+ const index = this.itemsRef.findIndex(itemRef => {
+ return itemRef.current === item;
+ });
+ if (index >= 0) {
+ if (
+ this.props.treeView &&
+ this.props.treeView.getSelectItem() === this.itemsRef[index].current
+ )
+ this.props.treeView.selectItem(null);
+ this.itemsRef.splice(index, 1);
+ const items = this.state.items.slice();
+ items.splice(index, 1);
+ this.setState({ items });
+ this.forceUpdate();
+ return true;
+ } else {
+ for (const itemRef of this.itemsRef) {
+ if (itemRef.current!.delItem(item)) return true;
+ }
+ }
+ return false;
+ }
+ remove() {
+ if (this.props.parent) this.props.parent.delItem(this);
+ this.forceUpdate();
+ }
+ clear() {
+ this.setState({ items: [] });
+ this.forceUpdate();
+ }
+ getChildren() {
+ return this.state.items;
+ }
+ onSelect(select: boolean) {
+ this.setState({ select });
+ }
+ setChecked(checked: boolean) {
+ this.setState({ checked });
+ for (const item of this.itemsRef) {
+ item.current!.setChecked(checked);
+ }
+ }
+ getCheckItems() {
+ const checks: TreeItem[] = [];
+ if (this.state.checked) checks.push(this);
+ for (const item of this.itemsRef) {
+ checks.push(...item.current!.getCheckItems());
+ }
+ return checks;
+ }
+}
diff --git a/src/TreeView/Root.ts b/src/TreeView/Root.ts
new file mode 100644
index 0000000..b1ce779
--- /dev/null
+++ b/src/TreeView/Root.ts
@@ -0,0 +1,14 @@
+import styled from "styled-components";
+
+interface StyleProps {}
+
+export const Root = styled.div.attrs(p => ({
+ style: {}
+}))`
+ user-select:none;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ overflow:auto;
+
+`;
diff --git a/src/TreeView/index.tsx b/src/TreeView/index.tsx
new file mode 100644
index 0000000..68c98ca
--- /dev/null
+++ b/src/TreeView/index.tsx
@@ -0,0 +1,63 @@
+import React, { Component, createRef, ReactElement } from "react";
+import { TreeItem } from "./Item/TreeItem";
+import { Root } from "./Root";
+
+interface Props {
+ onItemClick?: (item: TreeItem) => void;
+ onItemDoubleClick?: (item: TreeItem) => void;
+}
+interface State {}
+
+export class TreeView extends Component {
+ rootItemRef = createRef();
+ select: TreeItem | null = null;
+ render() {
+ const rootItem = this.props.children as ReactElement;
+ if (rootItem && rootItem.type === TreeItem) {
+ //TreeItemを再定義
+ return (
+
+
+
+ );
+ } else {
+ //データが存在しなかった場合は、デフォルトでrootアイテムを用意
+ return (
+
+
+
+ );
+ }
+ }
+ getItem(): TreeItem {
+ return this.rootItemRef.current!;
+ }
+ findItem(value: unknown) {
+ return this.rootItemRef.current!.findItem(value);
+ }
+ findItems(value: unknown) {
+ return this.rootItemRef.current!.findItems(value);
+ }
+ delItem(item: TreeItem) {
+ return this.rootItemRef.current!.delItem(item);
+ }
+ getSelectItem(): TreeItem | null {
+ return this.select;
+ }
+ selectItem(item: TreeItem | null) {
+ if (this.select) this.select.onSelect(false);
+ if (item) item.onSelect(true);
+ this.select = item;
+ }
+ getCheckItems(){
+ return this.rootItemRef.current!.getCheckItems();
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index 7f1c12c..f6d03f1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,5 @@
export * from "./JSWindow";
export * from "./SplitView";
-export * from "./ListView";
\ No newline at end of file
+export * from "./ListView";
+export * from "./TreeView";
+export * from "./TreeView/Item/TreeItem";
\ No newline at end of file
diff --git a/src/splitView/index.tsx b/src/splitView/index.tsx
index fb89d46..f47b602 100644
--- a/src/splitView/index.tsx
+++ b/src/splitView/index.tsx
@@ -43,18 +43,17 @@ export class SplitView extends Component {
private resizeObserver?: ResizeObserver;
private rootRef = createRef();
private childRef = [createRef(), createRef()];
- private children: (ReactNode | undefined)[] = [undefined, undefined];
public constructor(props: SplitProps) {
super(props);
- this.state = { pos: props.pos!, activeMode: false, barOpen: true };
+ this.state = {
+ pos: props.pos!,
+ activeMode: false,
+ barOpen: true,
+ };
this.type = props.type!;
- if (props.children) {
- if (props.children instanceof Array) {
- this.children = props.children;
- }
- }
}
public render() {
+ const children = React.Children.toArray(this.props.children);
return (
{
this.closeBar();
}}
>
- {this.children[1]}
+ {children[1]}
(this.activeStop = true)}>
- {this.children[0]}
+ {children[0]}
{
}
}
- onOpen(open: boolean) {
+ protected onOpen(open: boolean) {
const children = [this.childRef[0].current!, this.childRef[1].current!];
if (open) {
children[0].style.animation =
@@ -251,7 +250,7 @@ export class SplitView extends Component {
}
this.setState({ barOpen: open });
}
- onMove(pos: number) {
+ protected onMove(pos: number) {
this.setState({ pos });
this.closeBar();
}
diff --git a/src/svg.d.ts b/src/svg.d.ts
index e1e62b7..d9f3b7c 100644
--- a/src/svg.d.ts
+++ b/src/svg.d.ts
@@ -1,4 +1,8 @@
declare module '*.svg' {
const value: string;
export = value;
+}
+declare module '*.png' {
+ const value: string;
+ export = value;
}
\ No newline at end of file