mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-29 13:24:48 +00:00
Improve source structure in the node api (#6164)
* Update api/node/typescript/models.ts Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev> * Code review feedback --------- Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev>
This commit is contained in:
parent
a2ded25914
commit
25ae55b5dd
30 changed files with 528 additions and 450 deletions
|
@ -100,7 +100,6 @@ All notable changes to this project are documented in this file.
|
||||||
- Skia renderer: Improve rendering quality of layers
|
- Skia renderer: Improve rendering quality of layers
|
||||||
- GridLayout: Fixed panic when rowspan or colspan is 0 (#6181)
|
- GridLayout: Fixed panic when rowspan or colspan is 0 (#6181)
|
||||||
|
|
||||||
|
|
||||||
## [1.7.2] - 2024-08-14
|
## [1.7.2] - 2024-08-14
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
|
1
api/node/.gitignore
vendored
1
api/node/.gitignore
vendored
|
@ -3,3 +3,4 @@ rust-module.d.ts
|
||||||
index.js
|
index.js
|
||||||
docs/
|
docs/
|
||||||
npm/
|
npm/
|
||||||
|
dist/
|
||||||
|
|
|
@ -18,6 +18,7 @@ build = "build.rs"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
path = "rust/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["backend-winit", "renderer-femtovg", "renderer-software", "backend-qt", "accessibility"]
|
default = ["backend-winit", "renderer-femtovg", "renderer-software", "backend-qt", "accessibility"]
|
||||||
|
|
|
@ -5,7 +5,7 @@ import test from "ava";
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
import { loadFile, loadSource, CompileError } from "../index.js";
|
import { loadFile, loadSource, CompileError } from "../dist/index.js";
|
||||||
|
|
||||||
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
@ -81,7 +81,6 @@ test("loadFile constructor parameters", (t) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("loadFile component instances and modules are sealed", (t) => {
|
test("loadFile component instances and modules are sealed", (t) => {
|
||||||
"use strict";
|
|
||||||
const demo = loadFile(path.join(dirname, "resources/test.slint")) as any;
|
const demo = loadFile(path.join(dirname, "resources/test.slint")) as any;
|
||||||
|
|
||||||
t.throws(
|
t.throws(
|
||||||
|
@ -182,7 +181,6 @@ test("loadSource constructor parameters", (t) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("loadSource component instances and modules are sealed", (t) => {
|
test("loadSource component instances and modules are sealed", (t) => {
|
||||||
"use strict";
|
|
||||||
const source = `export component Test {
|
const source = `export component Test {
|
||||||
out property <string> check: "Test";
|
out property <string> check: "Test";
|
||||||
}`;
|
}`;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
|
|
||||||
import { private_api } from "../index.js";
|
import { private_api } from "../dist/index.js";
|
||||||
|
|
||||||
test("get/set include paths", (t) => {
|
test("get/set include paths", (t) => {
|
||||||
const compiler = new private_api.ComponentCompiler();
|
const compiler = new private_api.ComponentCompiler();
|
||||||
|
|
|
@ -7,7 +7,7 @@ import test from "ava";
|
||||||
import * as http from "node:http";
|
import * as http from "node:http";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
|
|
||||||
import { runEventLoop, quitEventLoop, private_api } from "../index.js";
|
import { runEventLoop, quitEventLoop, private_api } from "../dist/index.js";
|
||||||
|
|
||||||
test.serial("merged event loops with timer", async (t) => {
|
test.serial("merged event loops with timer", async (t) => {
|
||||||
let invoked = false;
|
let invoked = false;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
|
|
||||||
import { private_api } from "../index.js";
|
import { private_api } from "../dist/index.js";
|
||||||
|
|
||||||
test("get/set global properties", (t) => {
|
test("get/set global properties", (t) => {
|
||||||
const compiler = new private_api.ComponentCompiler();
|
const compiler = new private_api.ComponentCompiler();
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
type ImageData,
|
type ImageData,
|
||||||
ArrayModel,
|
ArrayModel,
|
||||||
type Model,
|
type Model,
|
||||||
} from "../index.js";
|
} from "../dist/index.js";
|
||||||
|
|
||||||
const filename = fileURLToPath(import.meta.url);
|
const filename = fileURLToPath(import.meta.url);
|
||||||
const dirname = path.dirname(filename);
|
const dirname = path.dirname(filename);
|
||||||
|
@ -598,11 +598,12 @@ test("MapModel", (t) => {
|
||||||
|
|
||||||
instance!.setProperty("model", mapModel);
|
instance!.setProperty("model", mapModel);
|
||||||
|
|
||||||
nameModel.setRowData(1, { first: "Simon", last: "Hausmann" });
|
nameModel.setRowData(0, { first: "Simon", last: "Hausmann" });
|
||||||
|
nameModel.setRowData(1, { first: "Olivier", last: "Goffart" });
|
||||||
|
|
||||||
const checkModel = instance!.getProperty("model") as Model<string>;
|
const checkModel = instance!.getProperty("model") as Model<string>;
|
||||||
t.is(checkModel.rowData(0), "Emil, Hans");
|
t.is(checkModel.rowData(0), "Hausmann, Simon");
|
||||||
t.is(checkModel.rowData(1), "Hausmann, Simon");
|
t.is(checkModel.rowData(1), "Goffart, Olivier");
|
||||||
t.is(checkModel.rowData(2), "Tisch, Roman");
|
t.is(checkModel.rowData(2), "Tisch, Roman");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
64
api/node/__test__/models.spec.mts
Normal file
64
api/node/__test__/models.spec.mts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||||
|
|
||||||
|
import test from "ava";
|
||||||
|
import * as path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
import {
|
||||||
|
loadFile,
|
||||||
|
loadSource,
|
||||||
|
CompileError,
|
||||||
|
ArrayModel,
|
||||||
|
private_api,
|
||||||
|
Model,
|
||||||
|
} from "../dist/index.js";
|
||||||
|
|
||||||
|
test("MapModel notify rowChanged", (t) => {
|
||||||
|
const source = `
|
||||||
|
export component App {
|
||||||
|
|
||||||
|
in-out property <[string]> model;
|
||||||
|
in-out property <string> changed-items;
|
||||||
|
|
||||||
|
for item in root.model : Text {
|
||||||
|
text: item;
|
||||||
|
|
||||||
|
changed text => {
|
||||||
|
root.changed-items += self.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const path = "api.spec.ts";
|
||||||
|
|
||||||
|
private_api.initTesting();
|
||||||
|
const demo = loadSource(source, path) as any;
|
||||||
|
const instance = new demo.App();
|
||||||
|
|
||||||
|
interface Name {
|
||||||
|
first: string;
|
||||||
|
last: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameModel: ArrayModel<Name> = new ArrayModel([
|
||||||
|
{ first: "Hans", last: "Emil" },
|
||||||
|
{ first: "Max", last: "Mustermann" },
|
||||||
|
{ first: "Roman", last: "Tisch" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mapModel = new private_api.MapModel(nameModel, (data) => {
|
||||||
|
return data.last + ", " + data.first;
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.model = mapModel;
|
||||||
|
|
||||||
|
private_api.send_mouse_click(instance, 5, 5);
|
||||||
|
|
||||||
|
nameModel.setRowData(0, { first: "Simon", last: "Hausmann" });
|
||||||
|
nameModel.setRowData(1, { first: "Olivier", last: "Goffart" });
|
||||||
|
|
||||||
|
private_api.send_mouse_click(instance, 5, 5);
|
||||||
|
|
||||||
|
t.is(instance.changed_items, "Goffart, OlivierHausmann, Simon");
|
||||||
|
});
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
|
|
||||||
import { private_api, ArrayModel } from "../index.js";
|
import { private_api, ArrayModel } from "../dist/index.js";
|
||||||
|
|
||||||
test("SlintColor from fromRgb", (t) => {
|
test("SlintColor from fromRgb", (t) => {
|
||||||
const color = private_api.SlintRgbaColor.fromRgb(100, 110, 120);
|
const color = private_api.SlintRgbaColor.fromRgb(100, 110, 120);
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
|
|
||||||
import { private_api, Window } from "../index.js";
|
import { private_api, Window } from "../dist/index.js";
|
||||||
|
|
||||||
test("Window constructor", (t) => {
|
test("Window constructor", (t) => {
|
||||||
t.throws(
|
t.throws(
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "slint-ui",
|
"name": "slint-ui",
|
||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"main": "index.js",
|
"main": "dist/index.js",
|
||||||
"types": "index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"homepage": "https://github.com/slint-ui/slint",
|
"homepage": "https://github.com/slint-ui/slint",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -20,8 +20,8 @@
|
||||||
],
|
],
|
||||||
"description": "Slint is a declarative GUI toolkit to build native user interfaces for desktop and embedded applications.",
|
"description": "Slint is a declarative GUI toolkit to build native user interfaces for desktop and embedded applications.",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.8.3",
|
|
||||||
"@ava/typescript": "^4.1.0",
|
"@ava/typescript": "^4.1.0",
|
||||||
|
"@biomejs/biome": "1.8.3",
|
||||||
"@types/node": "^20.8.6",
|
"@types/node": "^20.8.6",
|
||||||
"@types/node-fetch": "^2.6.7",
|
"@types/node-fetch": "^2.6.7",
|
||||||
"ava": "^5.3.0",
|
"ava": "^5.3.0",
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
"build:debug": "napi build --platform --js rust-module.cjs --dts rust-module.d.ts -c binaries.json && npm run compile",
|
"build:debug": "napi build --platform --js rust-module.cjs --dts rust-module.d.ts -c binaries.json && npm run compile",
|
||||||
"build:testing": "napi build --platform --js rust-module.cjs --dts rust-module.d.ts -c binaries.json --features testing && npm run compile",
|
"build:testing": "napi build --platform --js rust-module.cjs --dts rust-module.d.ts -c binaries.json --features testing && npm run compile",
|
||||||
"install": "node build-on-demand.mjs",
|
"install": "node build-on-demand.mjs",
|
||||||
"docs": "npm run build && typedoc --hideGenerator --treatWarningsAsErrors --readme cover.md index.ts",
|
"docs": "npm run build && typedoc --hideGenerator --treatWarningsAsErrors --readme cover.md typescript/index.ts",
|
||||||
"check": "biome check",
|
"check": "biome check",
|
||||||
"format": "biome format",
|
"format": "biome format",
|
||||||
"format:fix": "biome format --write",
|
"format:fix": "biome format --write",
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
"module": "CommonJS",
|
"module": "CommonJS",
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
"outDir": "dist"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"index.ts"
|
"typescript/"
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||||
|
|
||||||
import * as napi from "./rust-module.cjs";
|
import * as napi from "../rust-module.cjs";
|
||||||
export {
|
export {
|
||||||
Diagnostic,
|
Diagnostic,
|
||||||
DiagnosticLevel,
|
DiagnosticLevel,
|
||||||
RgbaColor,
|
RgbaColor,
|
||||||
Brush,
|
Brush,
|
||||||
} from "./rust-module";
|
} from "../rust-module.cjs";
|
||||||
|
|
||||||
import { Diagnostic } from "./rust-module.cjs";
|
import { Model } from "./models";
|
||||||
|
export { Model };
|
||||||
|
|
||||||
|
export { ArrayModel } from "./models";
|
||||||
|
|
||||||
|
import { Diagnostic } from "../rust-module.cjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a two-dimensional point.
|
* Represents a two-dimensional point.
|
||||||
|
@ -115,434 +120,6 @@ export interface ImageData {
|
||||||
get height(): number;
|
get height(): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModelIterator<T> implements Iterator<T> {
|
|
||||||
private row: number;
|
|
||||||
private model: Model<T>;
|
|
||||||
|
|
||||||
constructor(model: Model<T>) {
|
|
||||||
this.model = model;
|
|
||||||
this.row = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public next(): IteratorResult<T> {
|
|
||||||
if (this.row < this.model.rowCount()) {
|
|
||||||
const row = this.row;
|
|
||||||
this.row++;
|
|
||||||
return {
|
|
||||||
done: false,
|
|
||||||
value: this.model.rowData(row),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
done: true,
|
|
||||||
value: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model<T> is the interface for feeding dynamic data into
|
|
||||||
* `.slint` views.
|
|
||||||
*
|
|
||||||
* A model is organized like a table with rows of data. The
|
|
||||||
* fields of the data type T behave like columns.
|
|
||||||
*
|
|
||||||
* @template T the type of the model's items.
|
|
||||||
*
|
|
||||||
* ### Example
|
|
||||||
* As an example let's see the implementation of {@link ArrayModel}
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* export class ArrayModel<T> extends Model<T> {
|
|
||||||
* private a: Array<T>
|
|
||||||
*
|
|
||||||
* constructor(arr: Array<T>) {
|
|
||||||
* super();
|
|
||||||
* this.a = arr;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* rowCount() {
|
|
||||||
* return this.a.length;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* rowData(row: number) {
|
|
||||||
* return this.a[row];
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* setRowData(row: number, data: T) {
|
|
||||||
* this.a[row] = data;
|
|
||||||
* this.notifyRowDataChanged(row);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* push(...values: T[]) {
|
|
||||||
* let size = this.a.length;
|
|
||||||
* Array.prototype.push.apply(this.a, values);
|
|
||||||
* this.notifyRowAdded(size, arguments.length);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* remove(index: number, size: number) {
|
|
||||||
* let r = this.a.splice(index, size);
|
|
||||||
* this.notifyRowRemoved(index, size);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* get length(): number {
|
|
||||||
* return this.a.length;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* values(): IterableIterator<T> {
|
|
||||||
* return this.a.values();
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* entries(): IterableIterator<[number, T]> {
|
|
||||||
* return this.a.entries()
|
|
||||||
* }
|
|
||||||
*}
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export abstract class Model<T> implements Iterable<T> {
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
modelNotify: napi.ExternalObject<napi.SharedModelNotify>;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.modelNotify = napi.jsModelNotifyNew(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Returns a new Model where all elements are mapped by the function `mapFunction`.
|
|
||||||
// * @template T the type of the source model's items.
|
|
||||||
// * @param mapFunction functions that maps
|
|
||||||
// * @returns a new {@link MapModel} that wraps the current model.
|
|
||||||
// */
|
|
||||||
// map<U>(
|
|
||||||
// mapFunction: (data: T) => U
|
|
||||||
// ): MapModel<T, U> {
|
|
||||||
// return new MapModel(this, mapFunction);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementations of this function must return the current number of rows.
|
|
||||||
*/
|
|
||||||
abstract rowCount(): number;
|
|
||||||
/**
|
|
||||||
* Implementations of this function must return the data at the specified row.
|
|
||||||
* @param row index in range 0..(rowCount() - 1).
|
|
||||||
* @returns undefined if row is out of range otherwise the data.
|
|
||||||
*/
|
|
||||||
abstract rowData(row: number): T | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementations of this function must store the provided data parameter
|
|
||||||
* in the model at the specified row.
|
|
||||||
* @param _row index in range 0..(rowCount() - 1).
|
|
||||||
* @param _data new data item to store on the given row index
|
|
||||||
*/
|
|
||||||
setRowData(_row: number, _data: T): void {
|
|
||||||
console.log(
|
|
||||||
"setRowData called on a model which does not re-implement this method. This happens when trying to modify a read-only model",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Symbol.iterator](): Iterator<T> {
|
|
||||||
return new ModelIterator(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the view that the data of the current row is changed.
|
|
||||||
* @param row index of the changed row.
|
|
||||||
*/
|
|
||||||
protected notifyRowDataChanged(row: number): void {
|
|
||||||
napi.jsModelNotifyRowDataChanged(this.modelNotify, row);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the view that multiple rows are added to the model.
|
|
||||||
* @param row index of the first added row.
|
|
||||||
* @param count the number of added items.
|
|
||||||
*/
|
|
||||||
protected notifyRowAdded(row: number, count: number): void {
|
|
||||||
napi.jsModelNotifyRowAdded(this.modelNotify, row, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the view that multiple rows are removed to the model.
|
|
||||||
* @param row index of the first removed row.
|
|
||||||
* @param count the number of removed items.
|
|
||||||
*/
|
|
||||||
protected notifyRowRemoved(row: number, count: number): void {
|
|
||||||
napi.jsModelNotifyRowRemoved(this.modelNotify, row, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the view that the complete data must be reload.
|
|
||||||
*/
|
|
||||||
protected notifyReset(): void {
|
|
||||||
napi.jsModelNotifyReset(this.modelNotify);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ArrayModel wraps a JavaScript array for use in `.slint` views. The underlying
|
|
||||||
* array can be modified with the [[ArrayModel.push]] and [[ArrayModel.remove]] methods.
|
|
||||||
*/
|
|
||||||
export class ArrayModel<T> extends Model<T> {
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
#array: Array<T>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new ArrayModel.
|
|
||||||
*
|
|
||||||
* @param arr
|
|
||||||
*/
|
|
||||||
constructor(arr: Array<T>) {
|
|
||||||
super();
|
|
||||||
this.#array = arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of entries in the array model.
|
|
||||||
*/
|
|
||||||
get length(): number {
|
|
||||||
return this.#array.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of entries in the array model.
|
|
||||||
*/
|
|
||||||
rowCount() {
|
|
||||||
return this.#array.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the data at the specified row.
|
|
||||||
* @param row index in range 0..(rowCount() - 1).
|
|
||||||
* @returns undefined if row is out of range otherwise the data.
|
|
||||||
*/
|
|
||||||
rowData(row: number) {
|
|
||||||
return this.#array[row];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores the given data on the given row index and notifies run-time about the changed row.
|
|
||||||
* @param row index in range 0..(rowCount() - 1).
|
|
||||||
* @param data new data item to store on the given row index
|
|
||||||
*/
|
|
||||||
setRowData(row: number, data: T) {
|
|
||||||
this.#array[row] = data;
|
|
||||||
this.notifyRowDataChanged(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes new values to the array that's backing the model and notifies
|
|
||||||
* the run-time about the added rows.
|
|
||||||
* @param values list of values that will be pushed to the array.
|
|
||||||
*/
|
|
||||||
push(...values: T[]) {
|
|
||||||
const size = this.#array.length;
|
|
||||||
Array.prototype.push.apply(this.#array, values);
|
|
||||||
this.notifyRowAdded(size, arguments.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the last element from the array and returns it.
|
|
||||||
*
|
|
||||||
* @returns The removed element or undefined if the array is empty.
|
|
||||||
*/
|
|
||||||
pop(): T | undefined {
|
|
||||||
const last = this.#array.pop();
|
|
||||||
if (last !== undefined) {
|
|
||||||
this.notifyRowRemoved(this.#array.length, 1);
|
|
||||||
}
|
|
||||||
return last;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: should this be named splice and have the splice api?
|
|
||||||
/**
|
|
||||||
* Removes the specified number of element from the array that's backing
|
|
||||||
* the model, starting at the specified index.
|
|
||||||
* @param index index of first row to remove.
|
|
||||||
* @param size number of rows to remove.
|
|
||||||
*/
|
|
||||||
remove(index: number, size: number) {
|
|
||||||
const r = this.#array.splice(index, size);
|
|
||||||
this.notifyRowRemoved(index, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an iterable of values in the array.
|
|
||||||
*/
|
|
||||||
values(): IterableIterator<T> {
|
|
||||||
return this.#array.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an iterable of key, value pairs for every entry in the array.
|
|
||||||
*/
|
|
||||||
entries(): IterableIterator<[number, T]> {
|
|
||||||
return this.#array.entries();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace private_api {
|
|
||||||
/**
|
|
||||||
* Provides rows that are generated by a map function based on the rows of another Model.
|
|
||||||
*
|
|
||||||
* @template T item type of source model that is mapped to U.
|
|
||||||
* @template U the type of the mapped items
|
|
||||||
*
|
|
||||||
* ## Example
|
|
||||||
*
|
|
||||||
* Here we have a {@link ArrayModel} holding rows of a custom interface `Name` and a {@link MapModel} that maps the name rows
|
|
||||||
* to single string rows.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* import { Model, ArrayModel, MapModel } from "./index";
|
|
||||||
*
|
|
||||||
* interface Name {
|
|
||||||
* first: string;
|
|
||||||
* last: string;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* const model = new ArrayModel<Name>([
|
|
||||||
* {
|
|
||||||
* first: "Hans",
|
|
||||||
* last: "Emil",
|
|
||||||
* },
|
|
||||||
* {
|
|
||||||
* first: "Max",
|
|
||||||
* last: "Mustermann",
|
|
||||||
* },
|
|
||||||
* {
|
|
||||||
* first: "Roman",
|
|
||||||
* last: "Tisch",
|
|
||||||
* },
|
|
||||||
* ]);
|
|
||||||
*
|
|
||||||
* const mappedModel = new MapModel(
|
|
||||||
* model,
|
|
||||||
* (data) => {
|
|
||||||
* return data.last + ", " + data.first;
|
|
||||||
* }
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* // prints "Emil, Hans"
|
|
||||||
* console.log(mappedModel.rowData(0));
|
|
||||||
*
|
|
||||||
* // prints "Mustermann, Max"
|
|
||||||
* console.log(mappedModel.rowData(1));
|
|
||||||
*
|
|
||||||
* // prints "Tisch, Roman"
|
|
||||||
* console.log(mappedModel.rowData(2));
|
|
||||||
*
|
|
||||||
* // Alternatively you can use the shortcut {@link MapModel.map}.
|
|
||||||
*
|
|
||||||
* const model = new ArrayModel<Name>([
|
|
||||||
* {
|
|
||||||
* first: "Hans",
|
|
||||||
* last: "Emil",
|
|
||||||
* },
|
|
||||||
* {
|
|
||||||
* first: "Max",
|
|
||||||
* last: "Mustermann",
|
|
||||||
* },
|
|
||||||
* {
|
|
||||||
* first: "Roman",
|
|
||||||
* last: "Tisch",
|
|
||||||
* },
|
|
||||||
* ]);
|
|
||||||
*
|
|
||||||
* const mappedModel = model.map(
|
|
||||||
* (data) => {
|
|
||||||
* return data.last + ", " + data.first;
|
|
||||||
* }
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* // prints "Emil, Hans"
|
|
||||||
* console.log(mappedModel.rowData(0));
|
|
||||||
*
|
|
||||||
* // prints "Mustermann, Max"
|
|
||||||
* console.log(mappedModel.rowData(1));
|
|
||||||
*
|
|
||||||
* // prints "Tisch, Roman"
|
|
||||||
* console.log(mappedModel.rowData(2));
|
|
||||||
*
|
|
||||||
* // You can modifying the underlying {@link ArrayModel}:
|
|
||||||
*
|
|
||||||
* const model = new ArrayModel<Name>([
|
|
||||||
* {
|
|
||||||
* first: "Hans",
|
|
||||||
* last: "Emil",
|
|
||||||
* },
|
|
||||||
* {
|
|
||||||
* first: "Max",
|
|
||||||
* last: "Mustermann",
|
|
||||||
* },
|
|
||||||
* {
|
|
||||||
* first: "Roman",
|
|
||||||
* last: "Tisch",
|
|
||||||
* },
|
|
||||||
* ]);
|
|
||||||
*
|
|
||||||
* const mappedModel = model.map(
|
|
||||||
* (data) => {
|
|
||||||
* return data.last + ", " + data.first;
|
|
||||||
* }
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* model.setRowData(1, { first: "Minnie", last: "Musterfrau" } );
|
|
||||||
*
|
|
||||||
* // prints "Emil, Hans"
|
|
||||||
* console.log(mappedModel.rowData(0));
|
|
||||||
*
|
|
||||||
* // prints "Musterfrau, Minnie"
|
|
||||||
* console.log(mappedModel.rowData(1));
|
|
||||||
*
|
|
||||||
* // prints "Tisch, Roman"
|
|
||||||
* console.log(mappedModel.rowData(2));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export class MapModel<T, U> extends Model<U> {
|
|
||||||
readonly sourceModel: Model<T>;
|
|
||||||
#mapFunction: (data: T) => U;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs the MapModel with a source model and map functions.
|
|
||||||
* @template T item type of source model that is mapped to U.
|
|
||||||
* @template U the type of the mapped items.
|
|
||||||
* @param sourceModel the wrapped model.
|
|
||||||
* @param mapFunction maps the data from T to U.
|
|
||||||
*/
|
|
||||||
constructor(sourceModel: Model<T>, mapFunction: (data: T) => U) {
|
|
||||||
super();
|
|
||||||
this.sourceModel = sourceModel;
|
|
||||||
this.#mapFunction = mapFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of entries in the model.
|
|
||||||
*/
|
|
||||||
rowCount(): number {
|
|
||||||
return this.sourceModel.rowCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the data at the specified row.
|
|
||||||
* @param row index in range 0..(rowCount() - 1).
|
|
||||||
* @returns undefined if row is out of range otherwise the data.
|
|
||||||
*/
|
|
||||||
rowData(row: number): U | undefined {
|
|
||||||
const data = this.sourceModel.rowData(row);
|
|
||||||
if (data === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return this.#mapFunction(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* This interface describes the public API of a Slint component that is common to all instances. Use this to
|
* This interface describes the public API of a Slint component that is common to all instances. Use this to
|
||||||
* show() the window on the screen, access the window and subsequent window properties, or start the
|
* show() the window on the screen, access the window and subsequent window properties, or start the
|
||||||
|
@ -1160,6 +737,164 @@ export function quitEventLoop() {
|
||||||
globalEventLoop.quit();
|
globalEventLoop.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace private_api {
|
||||||
|
/**
|
||||||
|
* Provides rows that are generated by a map function based on the rows of another Model.
|
||||||
|
*
|
||||||
|
* @template T item type of source model that is mapped to U.
|
||||||
|
* @template U the type of the mapped items
|
||||||
|
*
|
||||||
|
* ## Example
|
||||||
|
*
|
||||||
|
* Here we have a {@link ArrayModel} holding rows of a custom interface `Name` and a {@link MapModel} that maps the name rows
|
||||||
|
* to single string rows.
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* import { Model, ArrayModel, MapModel } from "./index";
|
||||||
|
*
|
||||||
|
* interface Name {
|
||||||
|
* first: string;
|
||||||
|
* last: string;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const model = new ArrayModel<Name>([
|
||||||
|
* {
|
||||||
|
* first: "Hans",
|
||||||
|
* last: "Emil",
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* first: "Max",
|
||||||
|
* last: "Mustermann",
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* first: "Roman",
|
||||||
|
* last: "Tisch",
|
||||||
|
* },
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* const mappedModel = new MapModel(
|
||||||
|
* model,
|
||||||
|
* (data) => {
|
||||||
|
* return data.last + ", " + data.first;
|
||||||
|
* }
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* // prints "Emil, Hans"
|
||||||
|
* console.log(mappedModel.rowData(0));
|
||||||
|
*
|
||||||
|
* // prints "Mustermann, Max"
|
||||||
|
* console.log(mappedModel.rowData(1));
|
||||||
|
*
|
||||||
|
* // prints "Tisch, Roman"
|
||||||
|
* console.log(mappedModel.rowData(2));
|
||||||
|
*
|
||||||
|
* // Alternatively you can use the shortcut {@link MapModel.map}.
|
||||||
|
*
|
||||||
|
* const model = new ArrayModel<Name>([
|
||||||
|
* {
|
||||||
|
* first: "Hans",
|
||||||
|
* last: "Emil",
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* first: "Max",
|
||||||
|
* last: "Mustermann",
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* first: "Roman",
|
||||||
|
* last: "Tisch",
|
||||||
|
* },
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* const mappedModel = model.map(
|
||||||
|
* (data) => {
|
||||||
|
* return data.last + ", " + data.first;
|
||||||
|
* }
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* // prints "Emil, Hans"
|
||||||
|
* console.log(mappedModel.rowData(0));
|
||||||
|
*
|
||||||
|
* // prints "Mustermann, Max"
|
||||||
|
* console.log(mappedModel.rowData(1));
|
||||||
|
*
|
||||||
|
* // prints "Tisch, Roman"
|
||||||
|
* console.log(mappedModel.rowData(2));
|
||||||
|
*
|
||||||
|
* // You can modifying the underlying {@link ArrayModel}:
|
||||||
|
*
|
||||||
|
* const model = new ArrayModel<Name>([
|
||||||
|
* {
|
||||||
|
* first: "Hans",
|
||||||
|
* last: "Emil",
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* first: "Max",
|
||||||
|
* last: "Mustermann",
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* first: "Roman",
|
||||||
|
* last: "Tisch",
|
||||||
|
* },
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* const mappedModel = model.map(
|
||||||
|
* (data) => {
|
||||||
|
* return data.last + ", " + data.first;
|
||||||
|
* }
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* model.setRowData(1, { first: "Minnie", last: "Musterfrau" } );
|
||||||
|
*
|
||||||
|
* // prints "Emil, Hans"
|
||||||
|
* console.log(mappedModel.rowData(0));
|
||||||
|
*
|
||||||
|
* // prints "Musterfrau, Minnie"
|
||||||
|
* console.log(mappedModel.rowData(1));
|
||||||
|
*
|
||||||
|
* // prints "Tisch, Roman"
|
||||||
|
* console.log(mappedModel.rowData(2));
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export class MapModel<T, U> extends Model<U> {
|
||||||
|
readonly sourceModel: Model<T>;
|
||||||
|
#mapFunction: (data: T) => U;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the MapModel with a source model and map functions.
|
||||||
|
* @template T item type of source model that is mapped to U.
|
||||||
|
* @template U the type of the mapped items.
|
||||||
|
* @param sourceModel the wrapped model.
|
||||||
|
* @param mapFunction maps the data from T to U.
|
||||||
|
*/
|
||||||
|
constructor(sourceModel: Model<T>, mapFunction: (data: T) => U) {
|
||||||
|
super(sourceModel.modelNotify);
|
||||||
|
this.sourceModel = sourceModel;
|
||||||
|
this.#mapFunction = mapFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of entries in the model.
|
||||||
|
*/
|
||||||
|
rowCount(): number {
|
||||||
|
return this.sourceModel.rowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data at the specified row.
|
||||||
|
* @param row index in range 0..(rowCount() - 1).
|
||||||
|
* @returns undefined if row is out of range otherwise the data.
|
||||||
|
*/
|
||||||
|
rowData(row: number): U | undefined {
|
||||||
|
const data = this.sourceModel.rowData(row);
|
||||||
|
if (data === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return this.#mapFunction(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
278
api/node/typescript/models.ts
Normal file
278
api/node/typescript/models.ts
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||||
|
|
||||||
|
import * as napi from "../rust-module.cjs";
|
||||||
|
|
||||||
|
class ModelIterator<T> implements Iterator<T> {
|
||||||
|
private row: number;
|
||||||
|
private model: Model<T>;
|
||||||
|
|
||||||
|
constructor(model: Model<T>) {
|
||||||
|
this.model = model;
|
||||||
|
this.row = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public next(): IteratorResult<T> {
|
||||||
|
if (this.row < this.model.rowCount()) {
|
||||||
|
const row = this.row;
|
||||||
|
this.row++;
|
||||||
|
return {
|
||||||
|
done: false,
|
||||||
|
value: this.model.rowData(row),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
done: true,
|
||||||
|
value: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model<T> is the interface for feeding dynamic data into
|
||||||
|
* `.slint` views.
|
||||||
|
*
|
||||||
|
* A model is organized like a table with rows of data. The
|
||||||
|
* fields of the data type T behave like columns.
|
||||||
|
*
|
||||||
|
* @template T the type of the model's items.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
* As an example let's see the implementation of {@link ArrayModel}
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* export class ArrayModel<T> extends Model<T> {
|
||||||
|
* private a: Array<T>
|
||||||
|
*
|
||||||
|
* constructor(arr: Array<T>) {
|
||||||
|
* super();
|
||||||
|
* this.a = arr;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* rowCount() {
|
||||||
|
* return this.a.length;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* rowData(row: number) {
|
||||||
|
* return this.a[row];
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* setRowData(row: number, data: T) {
|
||||||
|
* this.a[row] = data;
|
||||||
|
* this.notifyRowDataChanged(row);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* push(...values: T[]) {
|
||||||
|
* let size = this.a.length;
|
||||||
|
* Array.prototype.push.apply(this.a, values);
|
||||||
|
* this.notifyRowAdded(size, arguments.length);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* remove(index: number, size: number) {
|
||||||
|
* let r = this.a.splice(index, size);
|
||||||
|
* this.notifyRowRemoved(index, size);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* get length(): number {
|
||||||
|
* return this.a.length;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* values(): IterableIterator<T> {
|
||||||
|
* return this.a.values();
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* entries(): IterableIterator<[number, T]> {
|
||||||
|
* return this.a.entries()
|
||||||
|
* }
|
||||||
|
*}
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export abstract class Model<T> implements Iterable<T> {
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
modelNotify: napi.ExternalObject<napi.SharedModelNotify>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
constructor(modelNotify?: napi.ExternalObject<napi.SharedModelNotify>) {
|
||||||
|
this.modelNotify = modelNotify ?? napi.jsModelNotifyNew(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Returns a new Model where all elements are mapped by the function `mapFunction`.
|
||||||
|
// * @template T the type of the source model's items.
|
||||||
|
// * @param mapFunction functions that maps
|
||||||
|
// * @returns a new {@link MapModel} that wraps the current model.
|
||||||
|
// */
|
||||||
|
// map<U>(
|
||||||
|
// mapFunction: (data: T) => U
|
||||||
|
// ): MapModel<T, U> {
|
||||||
|
// return new MapModel(this, mapFunction);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementations of this function must return the current number of rows.
|
||||||
|
*/
|
||||||
|
abstract rowCount(): number;
|
||||||
|
/**
|
||||||
|
* Implementations of this function must return the data at the specified row.
|
||||||
|
* @param row index in range 0..(rowCount() - 1).
|
||||||
|
* @returns undefined if row is out of range otherwise the data.
|
||||||
|
*/
|
||||||
|
abstract rowData(row: number): T | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementations of this function must store the provided data parameter
|
||||||
|
* in the model at the specified row.
|
||||||
|
* @param _row index in range 0..(rowCount() - 1).
|
||||||
|
* @param _data new data item to store on the given row index
|
||||||
|
*/
|
||||||
|
setRowData(_row: number, _data: T): void {
|
||||||
|
console.log(
|
||||||
|
"setRowData called on a model which does not re-implement this method. This happens when trying to modify a read-only model",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.iterator](): Iterator<T> {
|
||||||
|
return new ModelIterator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the view that the data of the current row is changed.
|
||||||
|
* @param row index of the changed row.
|
||||||
|
*/
|
||||||
|
protected notifyRowDataChanged(row: number): void {
|
||||||
|
napi.jsModelNotifyRowDataChanged(this.modelNotify, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the view that multiple rows are added to the model.
|
||||||
|
* @param row index of the first added row.
|
||||||
|
* @param count the number of added items.
|
||||||
|
*/
|
||||||
|
protected notifyRowAdded(row: number, count: number): void {
|
||||||
|
napi.jsModelNotifyRowAdded(this.modelNotify, row, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the view that multiple rows are removed to the model.
|
||||||
|
* @param row index of the first removed row.
|
||||||
|
* @param count the number of removed items.
|
||||||
|
*/
|
||||||
|
protected notifyRowRemoved(row: number, count: number): void {
|
||||||
|
napi.jsModelNotifyRowRemoved(this.modelNotify, row, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the view that the complete data must be reload.
|
||||||
|
*/
|
||||||
|
protected notifyReset(): void {
|
||||||
|
napi.jsModelNotifyReset(this.modelNotify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ArrayModel wraps a JavaScript array for use in `.slint` views. The underlying
|
||||||
|
* array can be modified with the [[ArrayModel.push]] and [[ArrayModel.remove]] methods.
|
||||||
|
*/
|
||||||
|
export class ArrayModel<T> extends Model<T> {
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
#array: Array<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ArrayModel.
|
||||||
|
*
|
||||||
|
* @param arr
|
||||||
|
*/
|
||||||
|
constructor(arr: Array<T>) {
|
||||||
|
super();
|
||||||
|
this.#array = arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of entries in the array model.
|
||||||
|
*/
|
||||||
|
get length(): number {
|
||||||
|
return this.#array.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of entries in the array model.
|
||||||
|
*/
|
||||||
|
rowCount() {
|
||||||
|
return this.#array.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data at the specified row.
|
||||||
|
* @param row index in range 0..(rowCount() - 1).
|
||||||
|
* @returns undefined if row is out of range otherwise the data.
|
||||||
|
*/
|
||||||
|
rowData(row: number) {
|
||||||
|
return this.#array[row];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the given data on the given row index and notifies run-time about the changed row.
|
||||||
|
* @param row index in range 0..(rowCount() - 1).
|
||||||
|
* @param data new data item to store on the given row index
|
||||||
|
*/
|
||||||
|
setRowData(row: number, data: T) {
|
||||||
|
this.#array[row] = data;
|
||||||
|
this.notifyRowDataChanged(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes new values to the array that's backing the model and notifies
|
||||||
|
* the run-time about the added rows.
|
||||||
|
* @param values list of values that will be pushed to the array.
|
||||||
|
*/
|
||||||
|
push(...values: T[]) {
|
||||||
|
const size = this.#array.length;
|
||||||
|
Array.prototype.push.apply(this.#array, values);
|
||||||
|
this.notifyRowAdded(size, arguments.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the last element from the array and returns it.
|
||||||
|
*
|
||||||
|
* @returns The removed element or undefined if the array is empty.
|
||||||
|
*/
|
||||||
|
pop(): T | undefined {
|
||||||
|
const last = this.#array.pop();
|
||||||
|
if (last !== undefined) {
|
||||||
|
this.notifyRowRemoved(this.#array.length, 1);
|
||||||
|
}
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: should this be named splice and have the splice api?
|
||||||
|
/**
|
||||||
|
* Removes the specified number of element from the array that's backing
|
||||||
|
* the model, starting at the specified index.
|
||||||
|
* @param index index of first row to remove.
|
||||||
|
* @param size number of rows to remove.
|
||||||
|
*/
|
||||||
|
remove(index: number, size: number) {
|
||||||
|
const r = this.#array.splice(index, size);
|
||||||
|
this.notifyRowRemoved(index, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterable of values in the array.
|
||||||
|
*/
|
||||||
|
values(): IterableIterator<T> {
|
||||||
|
return this.#array.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterable of key, value pairs for every entry in the array.
|
||||||
|
*/
|
||||||
|
entries(): IterableIterator<[number, T]> {
|
||||||
|
return this.#array.entries();
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,7 +49,7 @@ lazy_static::lazy_static! {
|
||||||
|
|
||||||
check_output(o);
|
check_output(o);
|
||||||
|
|
||||||
node_dir.join("index.js")
|
node_dir.join("dist/index.js")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue