steel-frame 0.0.1 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +7 -0
- package/.idea/steel-frame.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierrc +14 -0
- package/README.md +143 -6
- package/fake-types/index.d.ts +260 -0
- package/lib/communication.js +1 -0
- package/lib/index.js +48 -0
- package/lib/inspector.js +212 -0
- package/lib/jsx-runtime.js +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +58 -6
- package/src/communication.ts +10 -0
- package/src/index.ts +90 -0
- package/src/inspector.ts +256 -0
- package/src/jsx-runtime.ts +1 -0
- package/tsconfig.json +18 -0
- package/types/communication.d.ts +9 -0
- package/types/index.d.ts +41 -0
- package/types/inspector.d.ts +18 -0
- package/types/jsx-runtime.d.ts +1 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="ProjectModuleManager">
|
|
4
|
+
<modules>
|
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/steel-frame.iml" filepath="$PROJECT_DIR$/.idea/steel-frame.iml" />
|
|
6
|
+
</modules>
|
|
7
|
+
</component>
|
|
8
|
+
</project>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="WEB_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
6
|
+
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
7
|
+
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
8
|
+
</content>
|
|
9
|
+
<orderEntry type="inheritedJdk" />
|
|
10
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
11
|
+
</component>
|
|
12
|
+
</module>
|
package/.idea/vcs.xml
ADDED
package/.prettierrc
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tabWidth": 4,
|
|
3
|
+
"printWidth": 120,
|
|
4
|
+
"useTabs": false,
|
|
5
|
+
"semi": true,
|
|
6
|
+
"singleQuote": false,
|
|
7
|
+
"jsxSingleQuote": false,
|
|
8
|
+
"trailingComma": "all",
|
|
9
|
+
"bracketSpacing": true,
|
|
10
|
+
"bracketSameLine": true,
|
|
11
|
+
"arrowParens": "avoid",
|
|
12
|
+
"parser": "typescript",
|
|
13
|
+
"singleAttributePerLine": false
|
|
14
|
+
}
|
package/README.md
CHANGED
|
@@ -1,7 +1,144 @@
|
|
|
1
|
-
#
|
|
2
|
-
* build fault-tolerant web applications effortlessly,
|
|
3
|
-
* navigate gigabytes of code in seconds,
|
|
4
|
-
* fix bugs at lightspeed,
|
|
5
|
-
* have zero onboarding.
|
|
1
|
+
# Steel Frame
|
|
6
2
|
|
|
7
|
-
[
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
`SteelFrameKit` is a front-end development kit, which is developed to provide bulletproof frontends.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/vasille)
|
|
8
|
+
|
|
9
|
+
## Table of content
|
|
10
|
+
|
|
11
|
+
- [Steel Frame](#steel-frame)
|
|
12
|
+
- [Table of content](#table-of-content)
|
|
13
|
+
- [Installation](#installation)
|
|
14
|
+
- [How to use SteelFramekit](#how-to-use-steelframekit)
|
|
15
|
+
- [Full documentation:](#full-documentation)
|
|
16
|
+
- [Examples](#examples)
|
|
17
|
+
- [How SAFE is SteelFrameKit](#how-safe-is-steelframekit)
|
|
18
|
+
- [How INTUITIVE is SteelFrameKit](#how-intuitive-is-steelframekit)
|
|
19
|
+
- [How POWERFUL is SteelFrameKit](#how-powerful-is-steelframekit)
|
|
20
|
+
- [Road map](#road-map)
|
|
21
|
+
- [Change log](#change-log)
|
|
22
|
+
- [5.0.0](#500)
|
|
23
|
+
- [4.3.0](#430)
|
|
24
|
+
- [4.2.0](#420)
|
|
25
|
+
- [4.1.0](#410)
|
|
26
|
+
- [4.0.0](#400)
|
|
27
|
+
- [Questions](#questions)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
<hr>
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
npm install steel-frame --save
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## How to use SteelFramekit
|
|
39
|
+
|
|
40
|
+
Create an app from a template
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
$ npm create steel-frame
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Full documentation:
|
|
47
|
+
* [Learn `SteelFrameKit` in 5 minutes](https://github.com/vasille-js/vasille-js/blob/v5/doc/V4-API.md)
|
|
48
|
+
* [Router Documentation](https://github.com/vasille-js/vasille-js/blob/v5/doc/Router-API.md)
|
|
49
|
+
* [Compostion functions](https://github.com/vasille-js/vasille-js/blob/v5/doc/Compositions.md)
|
|
50
|
+
* [Dependency injection](https://github.com/vasille-js/vasille-js/blob/v5/doc/Context.md)
|
|
51
|
+
|
|
52
|
+
### Examples
|
|
53
|
+
* [TypeScript Example](https://github.com/vasille-js/example-typescript)
|
|
54
|
+
* [JavaScript Example](https://github.com/vasille-js/example-javascript)
|
|
55
|
+
|
|
56
|
+
<hr>
|
|
57
|
+
|
|
58
|
+
## How SAFE is SteelFrameKit
|
|
59
|
+
|
|
60
|
+
The safe of your application is ensured by
|
|
61
|
+
* `100%` coverage of code by unit tests.
|
|
62
|
+
Each function, each branch is working as designed.
|
|
63
|
+
* OOP, DRY, KISS and SOLID principles are applied.
|
|
64
|
+
* `strong typing` makes your javascript/typescript code safe as C++ code.
|
|
65
|
+
All entities of `SteelFrameKit` core library are strongly typed, including:
|
|
66
|
+
* data fields & properties.
|
|
67
|
+
* computed properties (function parameters and result).
|
|
68
|
+
* methods.
|
|
69
|
+
* events (defined handlers & event emit).
|
|
70
|
+
* DOM events & DOM operation (attributing, styling, etc.).
|
|
71
|
+
* slots of components.
|
|
72
|
+
* references to children.
|
|
73
|
+
* No asynchronous code, when the line of code is executed, the DOM and reactive things are already synced.
|
|
74
|
+
|
|
75
|
+
## How INTUITIVE is SteelFrameKit
|
|
76
|
+
|
|
77
|
+
There is the "Hello World":
|
|
78
|
+
```typescript jsx
|
|
79
|
+
import { compose, mount } from "steel-frame";
|
|
80
|
+
|
|
81
|
+
const App = compose(() => {
|
|
82
|
+
<p>Hello world</p>;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
mount(document.body, App, {});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## How POWERFUL is SteelFrameKit
|
|
89
|
+
|
|
90
|
+
All of these are supported:
|
|
91
|
+
* Components.
|
|
92
|
+
* Reactive values (observables).
|
|
93
|
+
* Inline computed values.
|
|
94
|
+
* Multiline computed values.
|
|
95
|
+
* HTML & SVG tags.
|
|
96
|
+
* Component custom slots.
|
|
97
|
+
* 2-way data binding in components.
|
|
98
|
+
* Logic block (if, else).
|
|
99
|
+
* Loops (array, map, set).
|
|
100
|
+
* Dependency injection.
|
|
101
|
+
|
|
102
|
+
<hr>
|
|
103
|
+
|
|
104
|
+
## Road map
|
|
105
|
+
|
|
106
|
+
* [x] Update the `core` library to version 3.0.
|
|
107
|
+
* [x] `100%` Test Coverage for core Library v3.
|
|
108
|
+
* [x] Develop the `JSX` library.
|
|
109
|
+
* [x] `100%` Test Coverage for the JSX library.
|
|
110
|
+
* [x] Develop the `Babel Plugin`.
|
|
111
|
+
* [x] `100%` Test Coverage fot babel plugin.
|
|
112
|
+
* [x] Add CSS support (define styles in components).
|
|
113
|
+
* [x] Add router.
|
|
114
|
+
* [x] Add SSG (static site generation).
|
|
115
|
+
* [ ] Develop tools extension for debugging (WIP).
|
|
116
|
+
* [ ] Add SSR (server side rendering).
|
|
117
|
+
|
|
118
|
+
## Change log
|
|
119
|
+
|
|
120
|
+
### 5.0.0
|
|
121
|
+
|
|
122
|
+
Add support for context and dependencies injection.
|
|
123
|
+
|
|
124
|
+
### 4.3.0
|
|
125
|
+
|
|
126
|
+
Add new function `safe` which make functions safe, errors are reported automatically.
|
|
127
|
+
|
|
128
|
+
### 4.2.0
|
|
129
|
+
|
|
130
|
+
Add support for inlined conditions in JSX, binary `&&` and ternary `?:` operator.
|
|
131
|
+
|
|
132
|
+
### 4.1.0
|
|
133
|
+
|
|
134
|
+
Added SSG (static site generation) as build option `sf build static`.
|
|
135
|
+
|
|
136
|
+
### 4.0.0
|
|
137
|
+
|
|
138
|
+
Initial version of the framework with file based routing and building scripts (`sf dev` and `sf build spa`).
|
|
139
|
+
|
|
140
|
+
## Questions
|
|
141
|
+
|
|
142
|
+
If you have questions, feel free to contact the maintainer of the project:
|
|
143
|
+
|
|
144
|
+
* [Author's Email](mailto:vas.lixcode@gmail.com)
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { App } from "vasille";
|
|
2
|
+
import type { TagOptions } from "vasille/web-runner";
|
|
3
|
+
import type { StyleSheetProps as StyleProps } from "vasille-web";
|
|
4
|
+
import type { ScreenProps } from "vasille-router";
|
|
5
|
+
import type { Router } from "vasille-router/web-router";
|
|
6
|
+
|
|
7
|
+
export type { StyleProps } from "vasille-web";
|
|
8
|
+
export type ClassItem = string | Record<string, boolean> | false;
|
|
9
|
+
export type { FallbackScreenProps, ErrorScreenProps } from "vasille-router";
|
|
10
|
+
export { safe } from "vasille";
|
|
11
|
+
export type { AppSide, IdeSide } from "../types/communication.d.ts";
|
|
12
|
+
|
|
13
|
+
/** Set a handler for component errors */
|
|
14
|
+
export declare function setErrorHandler(handler: (e: unknown) => void): void;
|
|
15
|
+
|
|
16
|
+
declare interface Params {
|
|
17
|
+
slot(...args: unknown[]): unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare type Composed<In extends object, Out> = (
|
|
21
|
+
$: (Required<In> extends Params ? Omit<In, "slot"> & { slot?: unknown } : In) & {
|
|
22
|
+
callback?(data: Out): void;
|
|
23
|
+
},
|
|
24
|
+
) => void;
|
|
25
|
+
declare type ComposedNoCallback<In extends object, Out> = (
|
|
26
|
+
$: Required<In> extends Params ? Omit<In, "slot"> & { slot?: unknown } : In,
|
|
27
|
+
) => void;
|
|
28
|
+
|
|
29
|
+
/** Composes a component (v3), which can receive external reactive values via props */
|
|
30
|
+
export declare function compose<In extends object, Out extends NonNullable<unknown>>(
|
|
31
|
+
renderer: (input: In) => Out,
|
|
32
|
+
): Composed<In, Out>;
|
|
33
|
+
export declare function compose<In extends object>(renderer: (input: In) => void): ComposedNoCallback<In, void>;
|
|
34
|
+
|
|
35
|
+
/** Composes a component (v4), which can receive external reactive values via props */
|
|
36
|
+
export declare function component<In extends object, Out extends NonNullable<unknown>>(
|
|
37
|
+
renderer: (input: In) => Out,
|
|
38
|
+
): Composed<In, Out>;
|
|
39
|
+
export declare function component<In extends object>(renderer: (input: In) => void): ComposedNoCallback<In, void>;
|
|
40
|
+
|
|
41
|
+
/** Composes a view, which can receive external reactive values via props */
|
|
42
|
+
export declare function view<Out extends NonNullable<unknown>>(
|
|
43
|
+
renderer: () => Out,
|
|
44
|
+
): Composed<NonNullable<unknown>, Out>;
|
|
45
|
+
export declare function view<In extends object, Out extends NonNullable<unknown>>(
|
|
46
|
+
renderer: (input: In) => Out,
|
|
47
|
+
): Composed<In, Out>;
|
|
48
|
+
export declare function view(renderer: () => void): ComposedNoCallback<NonNullable<unknown>, void>;
|
|
49
|
+
export declare function view<In extends object>(renderer: (input: In) => void): ComposedNoCallback<In, void>;
|
|
50
|
+
|
|
51
|
+
/** A Vasille.JS App screen */
|
|
52
|
+
type Screen<Route extends string> = (input: ScreenProps<Route>) => Promise<void>;
|
|
53
|
+
|
|
54
|
+
/** Composes a screen, the router navigates between screens */
|
|
55
|
+
export declare function screen<Route extends string>(renderer: Screen<Route>): Screen<Route>;
|
|
56
|
+
/** Composes a page, the file-based router navigates between pages (used for tests only) */
|
|
57
|
+
export declare function page<Route extends string>(renderer: Screen<Route>): Screen<Route>;
|
|
58
|
+
|
|
59
|
+
/** Returns the raw value of expression */
|
|
60
|
+
export declare function raw<T>(v: T): T;
|
|
61
|
+
/** Returns a reactive value of expression */
|
|
62
|
+
export declare function ref<T>(v: T): T;
|
|
63
|
+
/** Returns a reactive-computed form of expression */
|
|
64
|
+
export declare function bind<T>(v: T): T;
|
|
65
|
+
/** Returns a reactive-computed form of returned value */
|
|
66
|
+
export declare function calculate<T>(fn: () => T): T;
|
|
67
|
+
/** Runs the function each time when a dependency is changed */
|
|
68
|
+
export declare function watch(f: () => void): void;
|
|
69
|
+
/** Returns an array model of the array */
|
|
70
|
+
export declare function arrayModel<T>(v?: T[]): T[];
|
|
71
|
+
/** Returns a set model of array values */
|
|
72
|
+
export declare function setModel<T>(v?: T[]): Set<T>;
|
|
73
|
+
/** Returns a map model of map data */
|
|
74
|
+
export declare function mapModel<K, T>(v?: [K, T][]): Map<K, T>;
|
|
75
|
+
|
|
76
|
+
/** Awaits async data in a sync component */
|
|
77
|
+
export declare function awaited<T>(target: Promise<T>): [unknown, T | undefined, () => void];
|
|
78
|
+
export declare function awaited<T>(target: () => Promise<T>): [unknown, T | undefined, () => void];
|
|
79
|
+
|
|
80
|
+
/** Mounts a slot parameter of the component */
|
|
81
|
+
export declare function Slot(options: { model?: () => void; slot?: () => void }): void;
|
|
82
|
+
export declare function Slot<Props extends object>(
|
|
83
|
+
options: {
|
|
84
|
+
model?: (props: Props) => void;
|
|
85
|
+
slot?: () => void;
|
|
86
|
+
} & Props,
|
|
87
|
+
): void;
|
|
88
|
+
|
|
89
|
+
/** Renders content conditionally */
|
|
90
|
+
export declare function If<T>(props: {
|
|
91
|
+
$condition: T;
|
|
92
|
+
slot: (value: Exclude<T, false | 0 | "" | null | undefined>) => void;
|
|
93
|
+
}): void;
|
|
94
|
+
export declare function If(props: { $condition: unknown; slot?: unknown }): void;
|
|
95
|
+
|
|
96
|
+
/** Renders content conditionally, use strict after `<If/>` */
|
|
97
|
+
export declare function ElseIf<T>(props: {
|
|
98
|
+
$condition: T;
|
|
99
|
+
slot: (value: Exclude<T, false | 0 | "" | null | undefined>) => void;
|
|
100
|
+
}): void;
|
|
101
|
+
export declare function ElseIf(props: { $condition: unknown; slot?: unknown }): void;
|
|
102
|
+
|
|
103
|
+
/** Renders content conditionally, use strict after `<If/>` or `<ElseIf/>` */
|
|
104
|
+
export declare function Else(props: { slot?: unknown }): void;
|
|
105
|
+
|
|
106
|
+
/** Renders content several times using a model (array, map or set) */
|
|
107
|
+
export declare function For<T>(props: { of: ReadonlyArray<T>; slot?: (value: T) => void }): void;
|
|
108
|
+
export declare function For<T>(props: { of: ReadonlySet<T>; slot?: (value: T) => void }): void;
|
|
109
|
+
export declare function For<K, T>(props: { of: ReadonlyMap<K, T>; slot?: (value: T, index: K) => void }): void;
|
|
110
|
+
|
|
111
|
+
/** Refresh the content each time then the reactive model is updated */
|
|
112
|
+
export declare function Watch<T>(props: { $model: T; slot?: (value: T) => void }): void;
|
|
113
|
+
|
|
114
|
+
/** Create a debug comment in DOM */
|
|
115
|
+
export declare function Debug(props: { $model: unknown }): void;
|
|
116
|
+
|
|
117
|
+
/** Render content after a while */
|
|
118
|
+
export declare function Delay(props: { time?: number; slot?: unknown }): void;
|
|
119
|
+
|
|
120
|
+
type ReadonlyState<T> =
|
|
121
|
+
T extends Map<infer K, infer V>
|
|
122
|
+
? ReadonlyMap<K, V>
|
|
123
|
+
: T extends Set<infer V>
|
|
124
|
+
? ReadonlySet<V>
|
|
125
|
+
: T extends Array<infer V>
|
|
126
|
+
? ReadonlyArray<V>
|
|
127
|
+
: T extends object
|
|
128
|
+
? { readonly [K in keyof T]: T[K] }
|
|
129
|
+
: T;
|
|
130
|
+
|
|
131
|
+
/** Stores a singleton state to memory */
|
|
132
|
+
export declare function store<Return extends object>(fn: () => Return): ReadonlyState<Return>;
|
|
133
|
+
|
|
134
|
+
/** Creates a model (state) constructor */
|
|
135
|
+
export declare function model<Return extends object>(fn: () => Return): () => ReadonlyState<Return>;
|
|
136
|
+
export declare function model<Input extends object, Return extends object>(
|
|
137
|
+
fn: (input: Input) => Return,
|
|
138
|
+
): (input: Input) => ReadonlyState<Return>;
|
|
139
|
+
|
|
140
|
+
export { QueryParams, ScreenProps, RouteParameters } from "vasille-router";
|
|
141
|
+
export { type Router, NavigationMode } from "vasille-router/web-router";
|
|
142
|
+
|
|
143
|
+
/** Applies the value to theme `name` */
|
|
144
|
+
export declare function theme<T>(name: string, value: T): T;
|
|
145
|
+
/** Applies the value to dark theme */
|
|
146
|
+
export declare function dark<T>($: T): T;
|
|
147
|
+
/** Applies the value to mobile devices */
|
|
148
|
+
export declare function mobile<T>($: T): T;
|
|
149
|
+
/** Applies the value to tablet devices */
|
|
150
|
+
export declare function tablet<T>($: T): T;
|
|
151
|
+
/** Applies the value to laptop devices */
|
|
152
|
+
export declare function laptop<T>($: T): T;
|
|
153
|
+
/** Applies the value when user prefers dark theme */
|
|
154
|
+
export declare function prefersDark<T>($: T): T;
|
|
155
|
+
/** Applies the value when user prefers light theme */
|
|
156
|
+
export declare function prefersLight<T>($: T): T;
|
|
157
|
+
|
|
158
|
+
export { setMobileMaxWidth, setTabletMaxWidth, setLaptopMaxWidth } from "vasille-css";
|
|
159
|
+
|
|
160
|
+
/** Creates a local style sheet */
|
|
161
|
+
export declare const styleSheet: <
|
|
162
|
+
T extends {
|
|
163
|
+
[className: string]: {
|
|
164
|
+
[media: `@${string}`]: {
|
|
165
|
+
[state: `:${string}`]: StyleProps;
|
|
166
|
+
} & StyleProps;
|
|
167
|
+
[state: `:${string}`]: StyleProps;
|
|
168
|
+
} & StyleProps;
|
|
169
|
+
},
|
|
170
|
+
>(
|
|
171
|
+
input: T,
|
|
172
|
+
) => { [K in keyof T]: string };
|
|
173
|
+
|
|
174
|
+
/** Mounts a Vasille.JS component to page */
|
|
175
|
+
export declare function mount<T>(element: Element, component: ($: T) => void, $: T): App<Node, Element, TagOptions>;
|
|
176
|
+
|
|
177
|
+
interface RouterInitialization<Routes extends string> {
|
|
178
|
+
routes: {
|
|
179
|
+
[K in Routes]: {
|
|
180
|
+
screen: Screen<K>;
|
|
181
|
+
minAccessLevel?: number;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
getAccessLevel?(): Promise<number>;
|
|
185
|
+
fallbackScreen?(arg: { cause: "not-found" | "no-access" }): void;
|
|
186
|
+
errorScreen?(data: { error: unknown }): void;
|
|
187
|
+
loadingScreen?(props: object): void;
|
|
188
|
+
loadingOverlay?(props: object): void;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Starts a route app */
|
|
192
|
+
export declare function routerApp<Routes extends string>(
|
|
193
|
+
init: RouterInitialization<Routes>,
|
|
194
|
+
element?: Element,
|
|
195
|
+
): App<Node, Element, TagOptions>;
|
|
196
|
+
|
|
197
|
+
/** Run a function before component mount */
|
|
198
|
+
export declare function beforeMount(fn: () => void): void;
|
|
199
|
+
export declare function beforeMount(fn: () => Promise<void>): void;
|
|
200
|
+
|
|
201
|
+
/** Run a function after component mount */
|
|
202
|
+
export declare function afterMount(fn: () => void): void;
|
|
203
|
+
export declare function afterMount(fn: () => Promise<void>): void;
|
|
204
|
+
|
|
205
|
+
/** Run a function before component destroy */
|
|
206
|
+
export declare function beforeDestroy(fn: () => void): void;
|
|
207
|
+
|
|
208
|
+
/** Returns the current used router */
|
|
209
|
+
export declare function router(): Router<string> | undefined;
|
|
210
|
+
|
|
211
|
+
/** Composes a modal window (v4+) */
|
|
212
|
+
export declare function modal<T extends object>(modal: (input: T) => void): (input: T) => void;
|
|
213
|
+
|
|
214
|
+
/** Describes properties of a prompt window */
|
|
215
|
+
export interface PromptProps<T> {
|
|
216
|
+
resolve(data: T): void;
|
|
217
|
+
reject(err: unknown): void;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Composes a function which will show a prompt on call */
|
|
221
|
+
export declare function prompt<T, Input extends PromptProps<T> = PromptProps<T>>(
|
|
222
|
+
modal: (input: Input) => void,
|
|
223
|
+
): (input: Omit<Input, keyof PromptProps<unknown>>, timeout?: number) => Promise<T>;
|
|
224
|
+
|
|
225
|
+
/** Describes an injectable context value */
|
|
226
|
+
export declare class SteelContext<Args extends unknown[], Value> {
|
|
227
|
+
private fn: (...args: Args) => Value;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Create an injectable context */
|
|
231
|
+
export declare function context<Value, Args extends unknown[] = never[]>(
|
|
232
|
+
fn: (...args: Args) => Value,
|
|
233
|
+
): SteelContext<Args, Value>;
|
|
234
|
+
|
|
235
|
+
/** Share an context to children components */
|
|
236
|
+
export declare function share<Args extends unknown[], Value>(ctx: SteelContext<Args, Value>, ...args: Args): Value;
|
|
237
|
+
|
|
238
|
+
/** Share a dependency */
|
|
239
|
+
export declare function share<Class>(className: abstract new (...args: unknown[]) => Class, value: Class): Class;
|
|
240
|
+
|
|
241
|
+
/** Share a settings */
|
|
242
|
+
export declare function share(key: string, value: string): string;
|
|
243
|
+
|
|
244
|
+
/** Receive an shared context */
|
|
245
|
+
export declare function receive<Args extends unknown[], Value>(ctx: SteelContext<Args, Value>): Value;
|
|
246
|
+
|
|
247
|
+
/** Receive a dependency */
|
|
248
|
+
export declare function receive<Class>(className: abstract new (...args: unknown[]) => Class): Class;
|
|
249
|
+
|
|
250
|
+
/** Receive a settings */
|
|
251
|
+
export declare function receive(key: string): string;
|
|
252
|
+
|
|
253
|
+
/** Create and share an context when it is missing */
|
|
254
|
+
export declare function impute<Args extends unknown[], Value>(ctx: SteelContext<Args, Value>, ...args: Args): Value;
|
|
255
|
+
|
|
256
|
+
/** Ensure a dependency presence in context */
|
|
257
|
+
export declare function impute<Class>(className: abstract new (...args: unknown[]) => Class, value: () => Class): Class;
|
|
258
|
+
|
|
259
|
+
/** Share a setting value when it is missing */
|
|
260
|
+
export declare function impute(key: string, value: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { devArrayModel, devAwaited, DevDelay, devEnsure, devExpr, DevFor, devMapModel, devMatch, devModel, devRef, devSet, devSetModel, DevSlot, devStore, DevSwitch, devView, DevWatch, } from "vasille-jsx/dev";
|
|
2
|
+
import { devModal, devPrompt, devRouterApp, devMount } from "vasille-web/dev";
|
|
3
|
+
import { devScreen } from "vasille-router/dev";
|
|
4
|
+
import { Inspector } from "./inspector.js";
|
|
5
|
+
export { executionPosition, wrapFn, runFn, registerReference, shareStateById, positionedText } from "vasille/dev";
|
|
6
|
+
export { setErrorHandler, earlyInspector } from "vasille-jsx/dev";
|
|
7
|
+
export { safe, } from "vasille-web";
|
|
8
|
+
export { setLaptopMaxWidth, setTabletMaxWidth, setMobileMaxWidth } from "vasille-css";
|
|
9
|
+
export { devStyleSheet as styleSheet } from "vasille-css/dev";
|
|
10
|
+
export { context, impute, receive, share } from "vasille-context";
|
|
11
|
+
export const view = devView;
|
|
12
|
+
export const component = devView;
|
|
13
|
+
export const compose = devView;
|
|
14
|
+
export const ensure = devEnsure;
|
|
15
|
+
export const ref = devRef;
|
|
16
|
+
export const expr = devExpr;
|
|
17
|
+
export const bind = devExpr;
|
|
18
|
+
export const calculate = devExpr;
|
|
19
|
+
export const watch = devExpr;
|
|
20
|
+
export const set = devSet;
|
|
21
|
+
export const Delay = DevDelay;
|
|
22
|
+
export const For = DevFor;
|
|
23
|
+
export const Slot = DevSlot;
|
|
24
|
+
export const Watch = DevWatch;
|
|
25
|
+
export const awaited = devAwaited;
|
|
26
|
+
export const store = devStore;
|
|
27
|
+
export const model = devModel;
|
|
28
|
+
export const setModel = devSetModel;
|
|
29
|
+
export const mapModel = devMapModel;
|
|
30
|
+
export const arrayModel = devArrayModel;
|
|
31
|
+
export const Switch = DevSwitch;
|
|
32
|
+
export const match = devMatch;
|
|
33
|
+
export const screen = devScreen;
|
|
34
|
+
export const page = devScreen;
|
|
35
|
+
export const modal = devModal;
|
|
36
|
+
export const prompt = devPrompt;
|
|
37
|
+
export function mount(element, component, input) {
|
|
38
|
+
const inspector = new Inspector();
|
|
39
|
+
const app = devMount(element, component, input, inspector);
|
|
40
|
+
inspector.setup(app);
|
|
41
|
+
return app;
|
|
42
|
+
}
|
|
43
|
+
export function routerApp(init, element) {
|
|
44
|
+
const inspector = new Inspector();
|
|
45
|
+
const app = devRouterApp(init, element ?? document.body, inspector);
|
|
46
|
+
inspector.setup(app);
|
|
47
|
+
return app;
|
|
48
|
+
}
|
package/lib/inspector.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { AbstractInspector, earlyInspector } from "vasille-jsx/dev";
|
|
2
|
+
import { Fragment } from "vasille";
|
|
3
|
+
import { expr, ref } from "vasille-jsx";
|
|
4
|
+
import { DevFragment, DevTag, DevTextNode } from "vasille/dev";
|
|
5
|
+
class AppHandler {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.canvas = null;
|
|
8
|
+
this.app = null;
|
|
9
|
+
this.active = ref(false);
|
|
10
|
+
this.$width = ref(window.innerWidth);
|
|
11
|
+
this.$height = ref(window.innerHeight);
|
|
12
|
+
window.onresize = () => {
|
|
13
|
+
this.$width.V = window.innerWidth;
|
|
14
|
+
this.$height.V = window.innerHeight;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
setup(app, bridge) {
|
|
18
|
+
this.app = app;
|
|
19
|
+
app.tag("canvas", {
|
|
20
|
+
a: {
|
|
21
|
+
width: this.$width,
|
|
22
|
+
height: this.$height,
|
|
23
|
+
},
|
|
24
|
+
s: {
|
|
25
|
+
"pointer-events": expr(app, active => (active ? "auto" : "none"), [this.active]),
|
|
26
|
+
position: "absolute",
|
|
27
|
+
inset: "0",
|
|
28
|
+
"z-index": "9999909",
|
|
29
|
+
},
|
|
30
|
+
k: canvas => {
|
|
31
|
+
this.canvas = canvas;
|
|
32
|
+
},
|
|
33
|
+
e: {
|
|
34
|
+
pointermove: (ev) => {
|
|
35
|
+
const canvas = this.canvas;
|
|
36
|
+
const ctx = canvas?.getContext("2d");
|
|
37
|
+
let element;
|
|
38
|
+
if (!ctx || !canvas) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
ev.preventDefault();
|
|
42
|
+
this.active.V = false;
|
|
43
|
+
element = document.elementFromPoint(ev.clientX, ev.clientY);
|
|
44
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
45
|
+
ctx.fillStyle = "#ff000030";
|
|
46
|
+
if (element) {
|
|
47
|
+
const rect = element.getBoundingClientRect();
|
|
48
|
+
ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
49
|
+
}
|
|
50
|
+
this.active.V = true;
|
|
51
|
+
},
|
|
52
|
+
pointerdown: (ev) => {
|
|
53
|
+
const ids = [];
|
|
54
|
+
const key = "vasille";
|
|
55
|
+
let textNode = null;
|
|
56
|
+
let elements;
|
|
57
|
+
ev.preventDefault();
|
|
58
|
+
this.active.V = false;
|
|
59
|
+
elements = document.elementsFromPoint(ev.clientX, ev.clientY);
|
|
60
|
+
if (document.caretPositionFromPoint) {
|
|
61
|
+
textNode = document.caretPositionFromPoint(ev.clientX, ev.clientY)?.offsetNode;
|
|
62
|
+
}
|
|
63
|
+
else if (document.caretRangeFromPoint) {
|
|
64
|
+
textNode = document.caretRangeFromPoint(ev.clientX, ev.clientY)?.startContainer;
|
|
65
|
+
}
|
|
66
|
+
this.active.V = true;
|
|
67
|
+
if (textNode && key in textNode) {
|
|
68
|
+
ids.push(textNode[key]);
|
|
69
|
+
}
|
|
70
|
+
for (const element of elements) {
|
|
71
|
+
if (key in element) {
|
|
72
|
+
ids.push(element[key]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
bridge.listDebugItems(ids);
|
|
76
|
+
this.active.V = false;
|
|
77
|
+
const canvas = this.canvas;
|
|
78
|
+
const ctx = canvas?.getContext("2d");
|
|
79
|
+
if (ctx && canvas) {
|
|
80
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
highlight(ids) {
|
|
87
|
+
if (this.app === null) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const tags = new Set();
|
|
91
|
+
this.iterateFragment(this.app, new Set(ids), tags);
|
|
92
|
+
const canvas = this.canvas;
|
|
93
|
+
const ctx = canvas?.getContext("2d");
|
|
94
|
+
if (ctx && canvas) {
|
|
95
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
96
|
+
ctx.fillStyle = "#ff000030";
|
|
97
|
+
for (const tag of tags) {
|
|
98
|
+
const rect = tag.getNode().getBoundingClientRect();
|
|
99
|
+
ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
setLookup(active) {
|
|
104
|
+
this.active.V = active;
|
|
105
|
+
const canvas = this.canvas;
|
|
106
|
+
const ctx = canvas?.getContext("2d");
|
|
107
|
+
if (ctx && canvas) {
|
|
108
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
109
|
+
if (active) {
|
|
110
|
+
ctx.fillStyle = "#ff000030";
|
|
111
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
iterateFragment(node, ids, set) {
|
|
116
|
+
for (const child of node.children) {
|
|
117
|
+
if ("id" in child && ids.has(child.id)) {
|
|
118
|
+
this.addReactive(child, set);
|
|
119
|
+
}
|
|
120
|
+
else if (child instanceof Fragment) {
|
|
121
|
+
this.iterateFragment(child, ids, set);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
addTag(node, set) {
|
|
126
|
+
set.add(node);
|
|
127
|
+
}
|
|
128
|
+
addText(node, set) {
|
|
129
|
+
let it = node.parent;
|
|
130
|
+
while (it instanceof DevFragment) {
|
|
131
|
+
it = it.parent;
|
|
132
|
+
}
|
|
133
|
+
if (it instanceof DevTag) {
|
|
134
|
+
set.add(it);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
addFragment(node, set) {
|
|
138
|
+
for (const child of node.children) {
|
|
139
|
+
this.addReactive(child, set);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
addReactive(node, set) {
|
|
143
|
+
if (node instanceof DevTag) {
|
|
144
|
+
this.addTag(node, set);
|
|
145
|
+
}
|
|
146
|
+
else if (node instanceof DevTextNode) {
|
|
147
|
+
this.addText(node, set);
|
|
148
|
+
}
|
|
149
|
+
else if (node instanceof DevFragment) {
|
|
150
|
+
this.addFragment(node, set);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export class Inspector extends AbstractInspector {
|
|
155
|
+
constructor() {
|
|
156
|
+
super();
|
|
157
|
+
this.clean = false;
|
|
158
|
+
this.ws = null;
|
|
159
|
+
this.queue = [];
|
|
160
|
+
this.app = new AppHandler();
|
|
161
|
+
this._connect();
|
|
162
|
+
earlyInspector.connect(this);
|
|
163
|
+
}
|
|
164
|
+
setup(app) {
|
|
165
|
+
this.app.setup(app, this);
|
|
166
|
+
}
|
|
167
|
+
callInspector(method, arg) {
|
|
168
|
+
this.sendMessage(this.callInspector.name, [method, arg]);
|
|
169
|
+
}
|
|
170
|
+
listDebugItems(ids) {
|
|
171
|
+
this.sendMessage(this.listDebugItems.name, [ids]);
|
|
172
|
+
}
|
|
173
|
+
clear() {
|
|
174
|
+
this.sendMessage(this.clear.name, []);
|
|
175
|
+
}
|
|
176
|
+
_connect() {
|
|
177
|
+
const ws = (this.ws = new WebSocket("ws://localhost:7374"));
|
|
178
|
+
ws.onopen = () => {
|
|
179
|
+
if (!this.clean) {
|
|
180
|
+
ws.send(JSON.stringify(["clear", []]));
|
|
181
|
+
this.clean = true;
|
|
182
|
+
}
|
|
183
|
+
// Send all queued messages
|
|
184
|
+
while (this.queue.length > 0) {
|
|
185
|
+
ws.send(JSON.stringify(this.queue.shift()));
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
ws.onmessage = ev => {
|
|
189
|
+
const [method, args] = JSON.parse(ev.data);
|
|
190
|
+
this.app[method](...args);
|
|
191
|
+
};
|
|
192
|
+
ws.onclose = () => {
|
|
193
|
+
setTimeout(() => this._connect(), 1000);
|
|
194
|
+
};
|
|
195
|
+
ws.onerror = () => {
|
|
196
|
+
ws.close();
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
send(method, arg) {
|
|
200
|
+
this.callInspector(method, arg);
|
|
201
|
+
}
|
|
202
|
+
sendMessage(method, args) {
|
|
203
|
+
const message = [method, args];
|
|
204
|
+
const ws = this.ws;
|
|
205
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
206
|
+
ws.send(JSON.stringify(message));
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
this.queue.push(message);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../src/communication.ts","../src/index.ts","../src/inspector.ts","../src/jsx-runtime.ts"],"version":"5.9.3"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "steel-frame",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "",
|
|
7
|
-
"type": "module",
|
|
3
|
+
"version": "5.0.1",
|
|
4
|
+
"description": "SteelFrame: Build Fault-Tolerant Web Applications Effortlessly.",
|
|
8
5
|
"main": "index.js",
|
|
6
|
+
"types": "./index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"sf": "./lib/run.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./fake-types/index.d.ts",
|
|
13
|
+
"import": "./lib/index.js",
|
|
14
|
+
"browser": "./lib/index.js",
|
|
15
|
+
"node": "./lib/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./jsx-runtime": {
|
|
18
|
+
"types": "./types/jsx-runtime.d.ts",
|
|
19
|
+
"import": "./lib/jsx-runtime.js",
|
|
20
|
+
"browser": "./lib/jsx-runtime.js",
|
|
21
|
+
"node": "./lib/jsx-runtime.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
9
24
|
"scripts": {
|
|
10
|
-
"
|
|
25
|
+
"prepack": "cp -f ../README.md ./README.md",
|
|
26
|
+
"prettier": "prettier src fake-types --write",
|
|
27
|
+
"build": "tsc --build tsconfig.json",
|
|
28
|
+
"check-types": "tsc --noEmit --lib es2020 --types web --moduleResolution Node16 --module Node16 ./fake-types/index.d.ts"
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/vasille-js/vasille-js.git"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"frontend",
|
|
37
|
+
"framework",
|
|
38
|
+
"fast",
|
|
39
|
+
"simple",
|
|
40
|
+
"safe"
|
|
41
|
+
],
|
|
42
|
+
"author": "lixcode",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/vasille-js/vasille-js/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/vasille-js/vasille-js#readme",
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"csstype": "^3.1.3",
|
|
50
|
+
"vasille": "^5.0.0",
|
|
51
|
+
"vasille-context": "^5.0.0",
|
|
52
|
+
"vasille-css": "^5.0.0",
|
|
53
|
+
"vasille-jsx": "^5.0.0",
|
|
54
|
+
"vasille-router": "^5.0.0",
|
|
55
|
+
"vasille-ssg": "^4.0.0",
|
|
56
|
+
"vasille-web": "^5.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^24.3.0",
|
|
60
|
+
"@types/web": "^0.0.256",
|
|
61
|
+
"prettier": "^3.6.2",
|
|
62
|
+
"typescript": "^5.8.3"
|
|
11
63
|
}
|
|
12
64
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
devArrayModel,
|
|
3
|
+
devAwaited,
|
|
4
|
+
DevDelay,
|
|
5
|
+
devEnsure,
|
|
6
|
+
devExpr,
|
|
7
|
+
DevFor,
|
|
8
|
+
devMapModel,
|
|
9
|
+
devMatch,
|
|
10
|
+
devModel,
|
|
11
|
+
devRef,
|
|
12
|
+
devSet,
|
|
13
|
+
devSetModel,
|
|
14
|
+
DevSlot,
|
|
15
|
+
devStore,
|
|
16
|
+
DevSwitch,
|
|
17
|
+
devView,
|
|
18
|
+
DevWatch,
|
|
19
|
+
} from "vasille-jsx/dev";
|
|
20
|
+
import { WebRouterInitialization } from "vasille-web";
|
|
21
|
+
import { devModal, devPrompt, devRouterApp, devMount } from "vasille-web/dev";
|
|
22
|
+
import { App } from "vasille";
|
|
23
|
+
import { TagOptions } from "vasille/web-runner";
|
|
24
|
+
import { devScreen } from "vasille-router/dev";
|
|
25
|
+
import { Inspector } from "./inspector.js";
|
|
26
|
+
|
|
27
|
+
export { executionPosition, wrapFn, runFn, registerReference, shareStateById, positionedText } from "vasille/dev";
|
|
28
|
+
export { setErrorHandler, earlyInspector } from "vasille-jsx/dev";
|
|
29
|
+
export { type IdeSide, type AppSide } from "./communication.js";
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
type QueryParams,
|
|
33
|
+
type ScreenProps,
|
|
34
|
+
type RouteParameters,
|
|
35
|
+
type FallbackScreenProps,
|
|
36
|
+
type ErrorScreenProps,
|
|
37
|
+
type WebRouterInitialization,
|
|
38
|
+
type NavigationMode,
|
|
39
|
+
type Router,
|
|
40
|
+
safe,
|
|
41
|
+
} from "vasille-web";
|
|
42
|
+
export { setLaptopMaxWidth, setTabletMaxWidth, setMobileMaxWidth } from "vasille-css";
|
|
43
|
+
export { devStyleSheet as styleSheet } from "vasille-css/dev";
|
|
44
|
+
export { context, impute, receive, share } from "vasille-context";
|
|
45
|
+
|
|
46
|
+
export const view = devView;
|
|
47
|
+
export const component = devView;
|
|
48
|
+
export const compose = devView;
|
|
49
|
+
export const ensure = devEnsure;
|
|
50
|
+
export const ref = devRef;
|
|
51
|
+
export const expr = devExpr;
|
|
52
|
+
export const bind = devExpr;
|
|
53
|
+
export const calculate = devExpr;
|
|
54
|
+
export const watch = devExpr;
|
|
55
|
+
export const set = devSet;
|
|
56
|
+
export const Delay = DevDelay;
|
|
57
|
+
export const For = DevFor;
|
|
58
|
+
export const Slot = DevSlot;
|
|
59
|
+
export const Watch = DevWatch;
|
|
60
|
+
export const awaited = devAwaited;
|
|
61
|
+
export const store = devStore;
|
|
62
|
+
export const model = devModel;
|
|
63
|
+
export const setModel = devSetModel;
|
|
64
|
+
export const mapModel = devMapModel;
|
|
65
|
+
export const arrayModel = devArrayModel;
|
|
66
|
+
export const Switch = DevSwitch;
|
|
67
|
+
export const match = devMatch;
|
|
68
|
+
|
|
69
|
+
export const screen = devScreen;
|
|
70
|
+
export const page = devScreen;
|
|
71
|
+
|
|
72
|
+
export const modal = devModal;
|
|
73
|
+
export const prompt = devPrompt;
|
|
74
|
+
|
|
75
|
+
export function mount<T>(element: Element, component: ($: T) => void, input: T): App<Node, Element, TagOptions> {
|
|
76
|
+
const inspector = new Inspector();
|
|
77
|
+
const app = devMount<T>(element, component, input, inspector);
|
|
78
|
+
inspector.setup(app);
|
|
79
|
+
return app;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function routerApp<Routes extends string>(
|
|
83
|
+
init: WebRouterInitialization<Routes>,
|
|
84
|
+
element?: Element,
|
|
85
|
+
): App<Node, Element, TagOptions> {
|
|
86
|
+
const inspector = new Inspector();
|
|
87
|
+
const app = devRouterApp(init, element ?? document.body, inspector);
|
|
88
|
+
inspector.setup(app);
|
|
89
|
+
return app;
|
|
90
|
+
}
|
package/src/inspector.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { AbstractInspector, earlyInspector } from "vasille-jsx/dev";
|
|
2
|
+
import type { AppSide, IdeSide } from "./communication.js";
|
|
3
|
+
import { App, Fragment, Reactive } from "vasille";
|
|
4
|
+
import { TagOptions, TextNode, Runner } from "vasille/web-runner";
|
|
5
|
+
import { expr, ref } from "vasille-jsx";
|
|
6
|
+
import { DevFragment, DevTag, DevTextNode } from "vasille/dev";
|
|
7
|
+
|
|
8
|
+
class AppHandler implements AppSide {
|
|
9
|
+
private canvas: HTMLCanvasElement | null = null;
|
|
10
|
+
private app: App<Node, Element, TagOptions> | null = null;
|
|
11
|
+
private active = ref(false);
|
|
12
|
+
private $width = ref(window.innerWidth);
|
|
13
|
+
private $height = ref(window.innerHeight);
|
|
14
|
+
|
|
15
|
+
public constructor() {
|
|
16
|
+
window.onresize = () => {
|
|
17
|
+
this.$width.V = window.innerWidth;
|
|
18
|
+
this.$height.V = window.innerHeight;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public setup(app: App<Node, Element, TagOptions>, bridge: Inspector): void {
|
|
23
|
+
this.app = app;
|
|
24
|
+
|
|
25
|
+
app.tag("canvas", {
|
|
26
|
+
a: {
|
|
27
|
+
width: this.$width,
|
|
28
|
+
height: this.$height,
|
|
29
|
+
},
|
|
30
|
+
s: {
|
|
31
|
+
"pointer-events": expr(app, active => (active ? "auto" : "none"), [this.active]),
|
|
32
|
+
position: "absolute",
|
|
33
|
+
inset: "0",
|
|
34
|
+
"z-index": "9999909",
|
|
35
|
+
},
|
|
36
|
+
k: canvas => {
|
|
37
|
+
this.canvas = canvas as HTMLCanvasElement;
|
|
38
|
+
},
|
|
39
|
+
e: {
|
|
40
|
+
pointermove: (ev: PointerEvent) => {
|
|
41
|
+
const canvas = this.canvas;
|
|
42
|
+
const ctx = canvas?.getContext("2d");
|
|
43
|
+
let element: Element | null;
|
|
44
|
+
|
|
45
|
+
if (!ctx || !canvas) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
ev.preventDefault();
|
|
49
|
+
this.active.V = false;
|
|
50
|
+
element = document.elementFromPoint(ev.clientX, ev.clientY);
|
|
51
|
+
|
|
52
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
53
|
+
ctx.fillStyle = "#ff000030";
|
|
54
|
+
|
|
55
|
+
if (element) {
|
|
56
|
+
const rect = element.getBoundingClientRect();
|
|
57
|
+
|
|
58
|
+
ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
59
|
+
}
|
|
60
|
+
this.active.V = true;
|
|
61
|
+
},
|
|
62
|
+
pointerdown: (ev: PointerEvent) => {
|
|
63
|
+
const ids: number[] = [];
|
|
64
|
+
const key = "vasille";
|
|
65
|
+
let textNode: Node | null | undefined = null;
|
|
66
|
+
let elements: Element[];
|
|
67
|
+
|
|
68
|
+
ev.preventDefault();
|
|
69
|
+
this.active.V = false;
|
|
70
|
+
elements = document.elementsFromPoint(ev.clientX, ev.clientY);
|
|
71
|
+
|
|
72
|
+
if (document.caretPositionFromPoint) {
|
|
73
|
+
textNode = document.caretPositionFromPoint(ev.clientX, ev.clientY)?.offsetNode;
|
|
74
|
+
} else if (document.caretRangeFromPoint) {
|
|
75
|
+
textNode = document.caretRangeFromPoint(ev.clientX, ev.clientY)?.startContainer;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.active.V = true;
|
|
79
|
+
|
|
80
|
+
if (textNode && key in textNode) {
|
|
81
|
+
ids.push(textNode[key] as number);
|
|
82
|
+
}
|
|
83
|
+
for (const element of elements) {
|
|
84
|
+
if (key in element) {
|
|
85
|
+
ids.push(element[key] as number);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
bridge.listDebugItems(ids);
|
|
89
|
+
this.active.V = false;
|
|
90
|
+
|
|
91
|
+
const canvas = this.canvas;
|
|
92
|
+
const ctx = canvas?.getContext("2d");
|
|
93
|
+
|
|
94
|
+
if (ctx && canvas) {
|
|
95
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public highlight(ids: number[]): void {
|
|
103
|
+
if (this.app === null) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const tags = new Set<DevTag>();
|
|
108
|
+
|
|
109
|
+
this.iterateFragment(this.app as unknown as Fragment<Node, Element, TagOptions>, new Set(ids), tags);
|
|
110
|
+
|
|
111
|
+
const canvas = this.canvas;
|
|
112
|
+
const ctx = canvas?.getContext("2d");
|
|
113
|
+
|
|
114
|
+
if (ctx && canvas) {
|
|
115
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
116
|
+
ctx.fillStyle = "#ff000030";
|
|
117
|
+
|
|
118
|
+
for (const tag of tags) {
|
|
119
|
+
const rect = tag.getNode().getBoundingClientRect();
|
|
120
|
+
|
|
121
|
+
ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
public setLookup(active: boolean): void {
|
|
126
|
+
this.active.V = active;
|
|
127
|
+
|
|
128
|
+
const canvas = this.canvas;
|
|
129
|
+
const ctx = canvas?.getContext("2d");
|
|
130
|
+
|
|
131
|
+
if (ctx && canvas) {
|
|
132
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
133
|
+
if (active) {
|
|
134
|
+
ctx.fillStyle = "#ff000030";
|
|
135
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
protected iterateFragment(node: Fragment<Node, Element, TagOptions>, ids: Set<number>, set: Set<DevTag>) {
|
|
141
|
+
for (const child of node.children) {
|
|
142
|
+
if ("id" in child && ids.has(child.id as number)) {
|
|
143
|
+
this.addReactive(child, set);
|
|
144
|
+
} else if (child instanceof Fragment) {
|
|
145
|
+
this.iterateFragment(child, ids, set);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
protected addTag(node: DevTag, set: Set<DevTag>) {
|
|
151
|
+
set.add(node);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
protected addText(node: DevTextNode, set: Set<DevTag>) {
|
|
155
|
+
let it = node.parent;
|
|
156
|
+
|
|
157
|
+
while (it instanceof DevFragment) {
|
|
158
|
+
it = it.parent;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (it instanceof DevTag) {
|
|
162
|
+
set.add(it);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
protected addFragment(node: DevFragment<Node, Element, TagOptions>, set: Set<DevTag>) {
|
|
167
|
+
for (const child of node.children) {
|
|
168
|
+
this.addReactive(child, set);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
protected addReactive(node: Reactive, set: Set<DevTag>) {
|
|
173
|
+
if (node instanceof DevTag) {
|
|
174
|
+
this.addTag(node, set);
|
|
175
|
+
} else if (node instanceof DevTextNode) {
|
|
176
|
+
this.addText(node, set);
|
|
177
|
+
} else if (node instanceof DevFragment) {
|
|
178
|
+
this.addFragment(node, set);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export class Inspector extends AbstractInspector implements IdeSide {
|
|
184
|
+
private ws: WebSocket | null;
|
|
185
|
+
private queue: (readonly [string, unknown[]])[];
|
|
186
|
+
private app: AppHandler;
|
|
187
|
+
private clean: boolean = false;
|
|
188
|
+
|
|
189
|
+
constructor() {
|
|
190
|
+
super();
|
|
191
|
+
this.ws = null;
|
|
192
|
+
this.queue = [];
|
|
193
|
+
this.app = new AppHandler();
|
|
194
|
+
this._connect();
|
|
195
|
+
earlyInspector.connect(this);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public setup(app: App<Node, Element, TagOptions>) {
|
|
199
|
+
this.app.setup(app, this);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
public callInspector(method: string, arg: object) {
|
|
203
|
+
this.sendMessage(this.callInspector.name, [method, arg]);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public listDebugItems(ids: number[]) {
|
|
207
|
+
this.sendMessage(this.listDebugItems.name, [ids]);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public clear() {
|
|
211
|
+
this.sendMessage(this.clear.name, []);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private _connect() {
|
|
215
|
+
const ws = (this.ws = new WebSocket("ws://localhost:7374"));
|
|
216
|
+
|
|
217
|
+
ws.onopen = () => {
|
|
218
|
+
if (!this.clean) {
|
|
219
|
+
ws.send(JSON.stringify(["clear", []]));
|
|
220
|
+
this.clean = true;
|
|
221
|
+
}
|
|
222
|
+
// Send all queued messages
|
|
223
|
+
while (this.queue.length > 0) {
|
|
224
|
+
ws.send(JSON.stringify(this.queue.shift()));
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
ws.onmessage = ev => {
|
|
229
|
+
const [method, args] = JSON.parse(ev.data) as [string, unknown[]];
|
|
230
|
+
|
|
231
|
+
this.app[method](...args);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
ws.onclose = () => {
|
|
235
|
+
setTimeout(() => this._connect(), 1000);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
ws.onerror = () => {
|
|
239
|
+
ws.close();
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
protected send(method: string, arg: object) {
|
|
244
|
+
this.callInspector(method, arg);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private sendMessage(method: string, args: unknown[]) {
|
|
248
|
+
const message = [method, args] as const;
|
|
249
|
+
const ws = this.ws;
|
|
250
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
251
|
+
ws.send(JSON.stringify(message));
|
|
252
|
+
} else {
|
|
253
|
+
this.queue.push(message);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type JSX } from "vasille-web/jsx-runtime";
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["src/**/*"],
|
|
3
|
+
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "lib",
|
|
8
|
+
"declarationDir": "types",
|
|
9
|
+
"target": "ES2020",
|
|
10
|
+
"module": "Node16",
|
|
11
|
+
"moduleResolution": "node16",
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"strictNullChecks": true,
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"types": ["node"]
|
|
17
|
+
}
|
|
18
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { devArrayModel, devAwaited, DevDelay, devEnsure, devExpr, DevFor, devMapModel, devMatch, devModel, devRef, devSet, devSetModel, DevSlot, devStore, DevSwitch, devView, DevWatch } from "vasille-jsx/dev";
|
|
2
|
+
import { WebRouterInitialization } from "vasille-web";
|
|
3
|
+
import { devModal, devPrompt } from "vasille-web/dev";
|
|
4
|
+
import { App } from "vasille";
|
|
5
|
+
import { TagOptions } from "vasille/web-runner";
|
|
6
|
+
import { devScreen } from "vasille-router/dev";
|
|
7
|
+
export { executionPosition, wrapFn, runFn, registerReference, shareStateById, positionedText } from "vasille/dev";
|
|
8
|
+
export { setErrorHandler, earlyInspector } from "vasille-jsx/dev";
|
|
9
|
+
export { type IdeSide, type AppSide } from "./communication.js";
|
|
10
|
+
export { type QueryParams, type ScreenProps, type RouteParameters, type FallbackScreenProps, type ErrorScreenProps, type WebRouterInitialization, type NavigationMode, type Router, safe, } from "vasille-web";
|
|
11
|
+
export { setLaptopMaxWidth, setTabletMaxWidth, setMobileMaxWidth } from "vasille-css";
|
|
12
|
+
export { devStyleSheet as styleSheet } from "vasille-css/dev";
|
|
13
|
+
export { context, impute, receive, share } from "vasille-context";
|
|
14
|
+
export declare const view: typeof devView;
|
|
15
|
+
export declare const component: typeof devView;
|
|
16
|
+
export declare const compose: typeof devView;
|
|
17
|
+
export declare const ensure: typeof devEnsure;
|
|
18
|
+
export declare const ref: typeof devRef;
|
|
19
|
+
export declare const expr: typeof devExpr;
|
|
20
|
+
export declare const bind: typeof devExpr;
|
|
21
|
+
export declare const calculate: typeof devExpr;
|
|
22
|
+
export declare const watch: typeof devExpr;
|
|
23
|
+
export declare const set: typeof devSet;
|
|
24
|
+
export declare const Delay: typeof DevDelay;
|
|
25
|
+
export declare const For: typeof DevFor;
|
|
26
|
+
export declare const Slot: typeof DevSlot;
|
|
27
|
+
export declare const Watch: typeof DevWatch;
|
|
28
|
+
export declare const awaited: typeof devAwaited;
|
|
29
|
+
export declare const store: typeof devStore;
|
|
30
|
+
export declare const model: typeof devModel;
|
|
31
|
+
export declare const setModel: typeof devSetModel;
|
|
32
|
+
export declare const mapModel: typeof devMapModel;
|
|
33
|
+
export declare const arrayModel: typeof devArrayModel;
|
|
34
|
+
export declare const Switch: typeof DevSwitch;
|
|
35
|
+
export declare const match: typeof devMatch;
|
|
36
|
+
export declare const screen: typeof devScreen;
|
|
37
|
+
export declare const page: typeof devScreen;
|
|
38
|
+
export declare const modal: typeof devModal;
|
|
39
|
+
export declare const prompt: typeof devPrompt;
|
|
40
|
+
export declare function mount<T>(element: Element, component: ($: T) => void, input: T): App<Node, Element, TagOptions>;
|
|
41
|
+
export declare function routerApp<Routes extends string>(init: WebRouterInitialization<Routes>, element?: Element): App<Node, Element, TagOptions>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AbstractInspector } from "vasille-jsx/dev";
|
|
2
|
+
import type { IdeSide } from "./communication.js";
|
|
3
|
+
import { App } from "vasille";
|
|
4
|
+
import { TagOptions } from "vasille/web-runner";
|
|
5
|
+
export declare class Inspector extends AbstractInspector implements IdeSide {
|
|
6
|
+
private ws;
|
|
7
|
+
private queue;
|
|
8
|
+
private app;
|
|
9
|
+
private clean;
|
|
10
|
+
constructor();
|
|
11
|
+
setup(app: App<Node, Element, TagOptions>): void;
|
|
12
|
+
callInspector(method: string, arg: object): void;
|
|
13
|
+
listDebugItems(ids: number[]): void;
|
|
14
|
+
clear(): void;
|
|
15
|
+
private _connect;
|
|
16
|
+
protected send(method: string, arg: object): void;
|
|
17
|
+
private sendMessage;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type JSX } from "vasille-web/jsx-runtime";
|