@liberation-data/desk 0.1.0
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/LICENSE +201 -0
- package/NOTICE +4 -0
- package/README.md +436 -0
- package/dist/core/commands.d.ts +40 -0
- package/dist/core/commands.d.ts.map +1 -0
- package/dist/core/commands.js +92 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/desk.d.ts +46 -0
- package/dist/core/desk.d.ts.map +1 -0
- package/dist/core/desk.js +178 -0
- package/dist/core/desk.js.map +1 -0
- package/dist/core/events.d.ts +42 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +54 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/location.d.ts +30 -0
- package/dist/core/location.d.ts.map +1 -0
- package/dist/core/location.js +99 -0
- package/dist/core/location.js.map +1 -0
- package/dist/core/shortcuts.d.ts +36 -0
- package/dist/core/shortcuts.d.ts.map +1 -0
- package/dist/core/shortcuts.js +73 -0
- package/dist/core/shortcuts.js.map +1 -0
- package/dist/core/titles.d.ts +3 -0
- package/dist/core/titles.d.ts.map +1 -0
- package/dist/core/titles.js +26 -0
- package/dist/core/titles.js.map +1 -0
- package/dist/core/types.d.ts +51 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/desk.css +2550 -0
- package/dist/react/Desktop.d.ts +24 -0
- package/dist/react/Desktop.d.ts.map +1 -0
- package/dist/react/Desktop.js +212 -0
- package/dist/react/Desktop.js.map +1 -0
- package/dist/react/Dock.d.ts +42 -0
- package/dist/react/Dock.d.ts.map +1 -0
- package/dist/react/Dock.js +103 -0
- package/dist/react/Dock.js.map +1 -0
- package/dist/react/MenuBar.d.ts +64 -0
- package/dist/react/MenuBar.d.ts.map +1 -0
- package/dist/react/MenuBar.js +245 -0
- package/dist/react/MenuBar.js.map +1 -0
- package/dist/react/appFrame.d.ts +37 -0
- package/dist/react/appFrame.d.ts.map +1 -0
- package/dist/react/appFrame.js +119 -0
- package/dist/react/appFrame.js.map +1 -0
- package/dist/react/arrange.d.ts +24 -0
- package/dist/react/arrange.d.ts.map +1 -0
- package/dist/react/arrange.js +61 -0
- package/dist/react/arrange.js.map +1 -0
- package/dist/react/commands.d.ts +23 -0
- package/dist/react/commands.d.ts.map +1 -0
- package/dist/react/commands.js +41 -0
- package/dist/react/commands.js.map +1 -0
- package/dist/react/context.d.ts +25 -0
- package/dist/react/context.d.ts.map +1 -0
- package/dist/react/context.js +28 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/controls.d.ts +111 -0
- package/dist/react/controls.d.ts.map +1 -0
- package/dist/react/controls.js +83 -0
- package/dist/react/controls.js.map +1 -0
- package/dist/react/conversation.d.ts +41 -0
- package/dist/react/conversation.d.ts.map +1 -0
- package/dist/react/conversation.js +73 -0
- package/dist/react/conversation.js.map +1 -0
- package/dist/react/dnd.d.ts +51 -0
- package/dist/react/dnd.d.ts.map +1 -0
- package/dist/react/dnd.js +138 -0
- package/dist/react/dnd.js.map +1 -0
- package/dist/react/dragContext.d.ts +32 -0
- package/dist/react/dragContext.d.ts.map +1 -0
- package/dist/react/dragContext.js +14 -0
- package/dist/react/dragContext.js.map +1 -0
- package/dist/react/events.d.ts +41 -0
- package/dist/react/events.d.ts.map +1 -0
- package/dist/react/events.js +91 -0
- package/dist/react/events.js.map +1 -0
- package/dist/react/index.d.ts +43 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +22 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/layers.d.ts +5 -0
- package/dist/react/layers.d.ts.map +1 -0
- package/dist/react/layers.js +65 -0
- package/dist/react/layers.js.map +1 -0
- package/dist/react/overlays.d.ts +67 -0
- package/dist/react/overlays.d.ts.map +1 -0
- package/dist/react/overlays.js +98 -0
- package/dist/react/overlays.js.map +1 -0
- package/dist/react/popup.d.ts +21 -0
- package/dist/react/popup.d.ts.map +1 -0
- package/dist/react/popup.js +94 -0
- package/dist/react/popup.js.map +1 -0
- package/dist/react/search.d.ts +33 -0
- package/dist/react/search.d.ts.map +1 -0
- package/dist/react/search.js +82 -0
- package/dist/react/search.js.map +1 -0
- package/dist/react/setupProgress.d.ts +35 -0
- package/dist/react/setupProgress.d.ts.map +1 -0
- package/dist/react/setupProgress.js +68 -0
- package/dist/react/setupProgress.js.map +1 -0
- package/dist/react/shell.d.ts +17 -0
- package/dist/react/shell.d.ts.map +1 -0
- package/dist/react/shell.js +13 -0
- package/dist/react/shell.js.map +1 -0
- package/dist/react/sidebar.d.ts +23 -0
- package/dist/react/sidebar.d.ts.map +1 -0
- package/dist/react/sidebar.js +26 -0
- package/dist/react/sidebar.js.map +1 -0
- package/dist/react/table.d.ts +39 -0
- package/dist/react/table.d.ts.map +1 -0
- package/dist/react/table.js +86 -0
- package/dist/react/table.js.map +1 -0
- package/dist/react/tasks.d.ts +41 -0
- package/dist/react/tasks.d.ts.map +1 -0
- package/dist/react/tasks.js +79 -0
- package/dist/react/tasks.js.map +1 -0
- package/dist/react/tour.d.ts +42 -0
- package/dist/react/tour.d.ts.map +1 -0
- package/dist/react/tour.js +68 -0
- package/dist/react/tour.js.map +1 -0
- package/dist/react/windowBoundary.d.ts +22 -0
- package/dist/react/windowBoundary.d.ts.map +1 -0
- package/dist/react/windowBoundary.js +50 -0
- package/dist/react/windowBoundary.js.map +1 -0
- package/dist/react/wizard.d.ts +41 -0
- package/dist/react/wizard.d.ts.map +1 -0
- package/dist/react/wizard.js +60 -0
- package/dist/react/wizard.js.map +1 -0
- package/llms.txt +410 -0
- package/package.json +64 -0
- package/src/core/commands.ts +142 -0
- package/src/core/desk.ts +229 -0
- package/src/core/events.ts +99 -0
- package/src/core/index.ts +43 -0
- package/src/core/location.ts +127 -0
- package/src/core/shortcuts.ts +119 -0
- package/src/core/titles.ts +28 -0
- package/src/core/types.ts +49 -0
- package/src/desk.css +2550 -0
- package/src/react/Desktop.tsx +349 -0
- package/src/react/Dock.tsx +258 -0
- package/src/react/MenuBar.tsx +350 -0
- package/src/react/appFrame.tsx +183 -0
- package/src/react/arrange.ts +82 -0
- package/src/react/commands.ts +60 -0
- package/src/react/context.tsx +55 -0
- package/src/react/controls.tsx +364 -0
- package/src/react/conversation.tsx +208 -0
- package/src/react/dnd.tsx +189 -0
- package/src/react/dragContext.tsx +60 -0
- package/src/react/events.tsx +149 -0
- package/src/react/index.ts +64 -0
- package/src/react/layers.ts +66 -0
- package/src/react/overlays.tsx +254 -0
- package/src/react/popup.tsx +176 -0
- package/src/react/search.tsx +177 -0
- package/src/react/setupProgress.ts +113 -0
- package/src/react/shell.tsx +27 -0
- package/src/react/sidebar.tsx +84 -0
- package/src/react/table.tsx +176 -0
- package/src/react/tasks.tsx +176 -0
- package/src/react/tour.tsx +183 -0
- package/src/react/windowBoundary.tsx +110 -0
- package/src/react/wizard.tsx +150 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/NOTICE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# desk
|
|
2
|
+
|
|
3
|
+
A desktop for the web, in React: windows that fill the desk, layered, and arrange when asked.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @liberation-data/desk
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Window manager defaults
|
|
10
|
+
|
|
11
|
+
Every window opens filling the desk, over the ones already open, and keeps filling it when the browser
|
|
12
|
+
resizes. Nothing is laid out behind anyone's back.
|
|
13
|
+
|
|
14
|
+
**Window → Arrange** (⌥⌘A) lays the open windows out once: two side by side, three one tall and two stacked,
|
|
15
|
+
four a grid. After that they are ordinary windows. Move or resize one and only that one changes.
|
|
16
|
+
|
|
17
|
+
Drag a window by its title bar to free it where it sits. Drop it against the left or right edge and it takes
|
|
18
|
+
that half. Resize from the sides, the bottom or the corner. Double-click the title bar, or press the green
|
|
19
|
+
control, to fill the desk and back.
|
|
20
|
+
|
|
21
|
+
## Use it
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { createDesk } from '@liberation-data/desk'
|
|
25
|
+
import { Desktop, DeskProvider } from '@liberation-data/desk/react'
|
|
26
|
+
import '@liberation-data/desk/desk.css'
|
|
27
|
+
|
|
28
|
+
const desk = createDesk()
|
|
29
|
+
|
|
30
|
+
export function App() {
|
|
31
|
+
return (
|
|
32
|
+
<DeskProvider desk={desk}>
|
|
33
|
+
<button onClick={() => desk.open('notes')}>Notes</button>
|
|
34
|
+
<Desktop
|
|
35
|
+
title={id => TITLES[id]}
|
|
36
|
+
renderWindow={id => SURFACES[id]}
|
|
37
|
+
empty={<p>Nothing open</p>}
|
|
38
|
+
/>
|
|
39
|
+
</DeskProvider>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Two windows onto the same thing are the same kind with different ids: `desk.openInstance('query')` gives
|
|
45
|
+
`query`, then `query#2`. `windowType(id)` says what to render; the whole id says which one it is.
|
|
46
|
+
|
|
47
|
+
A window's code can wait until the window opens, and each window loads and fails on its own:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
const RoutePlanner = lazyWindow(() => import('./apps/RoutePlanner'))
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
While it loads, that window says so and the rest of the desk carries on; if it cannot open, that window says
|
|
54
|
+
what went wrong and offers Reload, which tries the import again rather than remembering the failure.
|
|
55
|
+
`<Desktop loading={…} failed={…}>` replaces the wording.
|
|
56
|
+
|
|
57
|
+
`renderWindow` is called with a window's id. The desk never owns your content, and a window keeps its DOM
|
|
58
|
+
when focus moves, so scroll positions, carets and iframes survive.
|
|
59
|
+
|
|
60
|
+
## Touch: one window at a time
|
|
61
|
+
|
|
62
|
+
`<Desktop layout="auto">` (the default) reads the device. A touch screen that cannot hover gets
|
|
63
|
+
`fullscreen`: the key window fills the stage, the dock stays as the way to switch, and windows are not
|
|
64
|
+
moved or arranged. Windows you are not looking at stay mounted — their drafts, scroll positions and carets survive —
|
|
65
|
+
and are simply offstage and `inert`. A tablet with a trackpad reports a fine pointer, so it gets the
|
|
66
|
+
desktop. Pass `layout="desktop"` or `layout="fullscreen"` to decide for yourself.
|
|
67
|
+
|
|
68
|
+
Links work the same everywhere: a link opens the same windows, shown one at a time on touch.
|
|
69
|
+
|
|
70
|
+
## Dock
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { Dock, dockItem, dockSeparator, dockStack } from '@liberation-data/desk/react'
|
|
74
|
+
|
|
75
|
+
<Dock
|
|
76
|
+
entries={[
|
|
77
|
+
dockItem({ id: 'rides', label: 'Rides', icon: <RideIcon /> }),
|
|
78
|
+
dockItem({ id: 'service', label: 'Service', icon: <WrenchIcon />, badge: 2 }),
|
|
79
|
+
dockSeparator('stacks'),
|
|
80
|
+
dockStack({ id: 'plan', label: 'Plan', items: [routes, weather, calendar] }),
|
|
81
|
+
]}
|
|
82
|
+
/>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
An item opens the window with its id (or `window`), and shows a dot while that window is open. A stack fans
|
|
86
|
+
its items out above the dock and previews the first four icons. `onSelect` replaces opening a window.
|
|
87
|
+
|
|
88
|
+
The dock is one tab stop: arrow keys move along it, Enter opens, Escape folds a stack away and returns focus
|
|
89
|
+
to it. With `placement="overlay"` (the default) it floats over the bottom of its positioned parent; set
|
|
90
|
+
`--desk-inset-bottom` so windows stop above it.
|
|
91
|
+
|
|
92
|
+
## Controls
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { Button, SegmentedControl, TextField, Toggle } from '@liberation-data/desk/react'
|
|
96
|
+
|
|
97
|
+
<Button intent="default" onClick={save}>Save ride</Button>
|
|
98
|
+
<SegmentedControl label="Filter by bike" options={BIKES} value={bike} onChange={setBike} />
|
|
99
|
+
<Toggle checked={metric} onChange={setMetric} label="Kilometres and metres" description="Off shows miles" />
|
|
100
|
+
<TextField label="Wheel size" value={wheel} onChange={e => setWheel(e.target.value)} error={wheelError} />
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Each control carries its own behaviour and accessibility; the look comes only from tokens, and there are few
|
|
104
|
+
style props on purpose. `intent="default"` marks the one action a view leads with. A segmented control is one
|
|
105
|
+
tab stop whose arrow keys move the selection past disabled options. A toggle is a `switch` for settings that
|
|
106
|
+
apply at once — a checkbox is for one that waits for Save. A text field always has a label (`labelHidden`
|
|
107
|
+
keeps it for screen readers), and an `error` marks it invalid and replaces the help text. A choice group
|
|
108
|
+
is the handful of cards a setup step turns on — an icon, a label, a line of why — with native radios
|
|
109
|
+
underneath, so it is one tab stop and the arrow keys move the choice.
|
|
110
|
+
|
|
111
|
+
## Setup assistant
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
const steps: WizardStep[] = [
|
|
115
|
+
{ id: 'welcome', name: 'Welcome', title: 'Welcome to the garage', continueLabel: 'Get started' },
|
|
116
|
+
{ id: 'rider', name: 'Rider', title: 'Who is riding?', complete: name.length > 1, body: <TextField … /> },
|
|
117
|
+
{ id: 'weather', name: 'Weather', title: 'Connect a weather service', complete: key.length > 0,
|
|
118
|
+
continueLabel: 'Connect', busyLabel: 'Checking the key…', onContinue: checkKey,
|
|
119
|
+
skip: { label: 'Set up later', onSkip: skipWeather } },
|
|
120
|
+
{ id: 'finishing', name: 'Finishing', title: 'Setting up', working: true, complete: done, onEnter: install },
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
<Wizard steps={steps} index={index} onIndexChange={setIndex} onFinish={start} />
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
One pane, one question at a time, Back and Continue where the eye already is. `complete` gates Continue, so
|
|
127
|
+
a step cannot be passed until it is answered; `skip` is the quiet way past a step that can wait; `working`
|
|
128
|
+
is a step with nothing to do but wait, which offers no Back. `onContinue` is the work Continue does before
|
|
129
|
+
moving on — create the account, check the key: while it runs Continue shows `busyLabel` and nothing can be
|
|
130
|
+
pressed twice; throw to stay on the step with the message shown in it, or return `false` to stay quietly.
|
|
131
|
+
Focus moves to each step's heading as it arrives, so it is announced — and only when the step changes, so a
|
|
132
|
+
field in the pane keeps what is typed.
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
const setup = useTasks([
|
|
136
|
+
{ id: 'profile', name: 'Save your profile', run: () => saveProfile(profile) },
|
|
137
|
+
{ id: 'rides', name: 'Import your rides', run: async report => { for await (const n of importRides()) report(`${n} of 24`) } },
|
|
138
|
+
])
|
|
139
|
+
|
|
140
|
+
{ id: 'finishing', name: 'Finishing', title: 'Setting up', working: !setup.failed, complete: setup.done,
|
|
141
|
+
onEnter: setup.restart, body: <Checklist items={setup.items} onRetry={setup.start} /> }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The jobs a setup does while someone watches run in order. Each says how it is getting on (`report`), and one
|
|
145
|
+
that throws stops the rest and shows why, with **Try again** picking up from the task that failed rather than
|
|
146
|
+
from the top. `working: !setup.failed` gives the step its Back again once something has gone wrong.
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
const progress = useSetupProgress({ key: 'garage', initial: { rider: '', bikes: [] }, store: serverStore })
|
|
150
|
+
if (!progress.loaded) return null
|
|
151
|
+
if (!progress.finished) return <Wizard index={progress.index} onIndexChange={progress.setIndex} onFinish={progress.finish} … />
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Setup is remembered: closing the tab on step four resumes on step four, and a finished setup is not shown
|
|
155
|
+
again until `reset()` ("Set up again"). The store is the app's to choose — this browser's local storage by
|
|
156
|
+
default, or a `SetupStore` with `load`, `save` and `clear` that talks to a server. Keep answers to what a
|
|
157
|
+
person would mind retyping; never a password or a key.
|
|
158
|
+
|
|
159
|
+
## Tours
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
const TOUR: Tour = {
|
|
163
|
+
id: 'first-look',
|
|
164
|
+
name: 'What needs doing',
|
|
165
|
+
description: 'A minute on where the jobs are and what wore out.',
|
|
166
|
+
steps: [
|
|
167
|
+
{ window: 'service', point: 'service-chain', caption: 'Service lists what the bikes need.' },
|
|
168
|
+
{ window: 'rides', caption: 'Choose a ride in the table.', yourTurn: true, until: 'ride.selected' },
|
|
169
|
+
{ window: 'chat', caption: 'Ask which job to do first.', yourTurn: true },
|
|
170
|
+
],
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
{touring && <TourBar tour={TOUR} offer={firstRun} onFinish={done} onStop={done} />}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
A tour drives the desk: each step opens the window it is about and points at the control it is talking
|
|
177
|
+
about (`point` is a `data-tour` name or any selector), so the person watches the real app rather than
|
|
178
|
+
reading about it. A `yourTurn` step hands over and waits, offering Done and Skip. Give it `until` — an event
|
|
179
|
+
topic, or a topic and a test — and it moves on by itself when the person has done it, with no Done to press.
|
|
180
|
+
`offer` asks first ("Show me" or "Not now") and opens nothing until the person says yes: use it the first time
|
|
181
|
+
someone reaches the desktop, straight after setup, so nobody lands there without a word. The bar holds no
|
|
182
|
+
content: the steps are the app's.
|
|
183
|
+
|
|
184
|
+
## Dragging between windows
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
// In the list — one source, because a hook cannot be called per row
|
|
188
|
+
const rides = useDragSource<Ride>({ type: 'ride' })
|
|
189
|
+
<Table rowProps={row => rides.dragProps(row, row.name)} … />
|
|
190
|
+
|
|
191
|
+
// In the map
|
|
192
|
+
const { dropProps, over, ready } = useDropTarget<Ride>({ accepts: 'ride', onDrop: ({ payload }) => draw(payload) })
|
|
193
|
+
<div {...dropProps}>{ready && <p>Drop a ride here</p>}</div>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Built on Pointer Events, so touch and pen work the same way, the preview is drawn by us, and Escape calls
|
|
197
|
+
the drag off. A drag carries a `type` and a `payload` and says which window it came from; a target says which
|
|
198
|
+
types it takes and lights up while something acceptable is in the air. Dropping brings the window that took
|
|
199
|
+
it forward. `useDraggable` is the single-item form of the same thing.
|
|
200
|
+
|
|
201
|
+
## Generated apps
|
|
202
|
+
|
|
203
|
+
A self-contained page — the kind an assistant writes when asked for an app — can run in a window and take
|
|
204
|
+
part like any other:
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
<AppFrame
|
|
208
|
+
title="Ride Card"
|
|
209
|
+
srcDoc={withAppBridge(generatedHtml)}
|
|
210
|
+
listens={['ride.selected']} // what it may hear
|
|
211
|
+
says={['ride.selected']} // what it may say
|
|
212
|
+
accepts="ride" // what may be dropped on it
|
|
213
|
+
opens={['map']} // windows it may ask for
|
|
214
|
+
/>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Inside the page, the bridge gives it a small `desk` object: `desk.on(topic, …)`, `desk.onDrop(…)`,
|
|
218
|
+
`desk.publish(topic, payload)` and `desk.open(window)`. The page runs sandboxed with no access to the host's
|
|
219
|
+
origin, and talks only through messages. The host grants each app its topics, drop types and windows;
|
|
220
|
+
anything else it sends is dropped, and messages from anywhere but its own frame are ignored.
|
|
221
|
+
|
|
222
|
+
## Search
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
import { SearchPalette, windowResults } from '@liberation-data/desk/react'
|
|
226
|
+
|
|
227
|
+
<SearchPalette open={open} onOpenChange={setOpen} search={query => [
|
|
228
|
+
...windowResults(desk, titleOf),
|
|
229
|
+
...rides.filter(matching(query)).map(asResult),
|
|
230
|
+
]} />
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The app says what the results are; the palette does the typing, grouping, keyboard and choosing. It binds
|
|
234
|
+
`mod+k` and answers the `desk.search` command, so a menu item can open it too (pass `shortcut={null}` when
|
|
235
|
+
the menu binds the key itself). An async `search` is safe: a slower answer to an older query never overtakes
|
|
236
|
+
a newer one.
|
|
237
|
+
|
|
238
|
+
## Typing into the window you are in, and windows telling each other
|
|
239
|
+
|
|
240
|
+
One bar to type into, which reaches whichever window is key:
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
// In a window: take the text while this window is in front.
|
|
244
|
+
useWindowInput(setFilter, { placeholder: 'Filter rides by name…', target: 'Rides' })
|
|
245
|
+
|
|
246
|
+
// In the shell: the bar, and what happens when no window takes the text.
|
|
247
|
+
<InputBar onSubmit={askTheAssistant} fallbackPlaceholder="Ask anything…" fallbackTarget="Chat" />
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The bar is **summoned** (⌘J by default) and opens over the middle of the screen, the way Spotlight does;
|
|
251
|
+
Escape dismisses it, and sending closes it. `mode="inline"` puts it wherever you like instead. Either way it
|
|
252
|
+
asks the responder chain how to present itself, so its placeholder and the *goes to* line follow the key
|
|
253
|
+
window, and a window that registers nothing lets the fallback have the text.
|
|
254
|
+
|
|
255
|
+
Windows tell each other what happened rather than calling each other:
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
const publish = usePublish() // stamped with the window it came from
|
|
259
|
+
publish('ride.selected', ride)
|
|
260
|
+
|
|
261
|
+
useDeskEvent<Ride>('ride.selected', event => show(event.payload), { replay: true })
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Topics are dotted names; a subscriber can take a branch with `ride.*` or everything with `*`. A desk
|
|
265
|
+
carries its own bus, so this needs no extra provider. `replay`
|
|
266
|
+
delivers the last event on the topic straight away — the window that the event concerns is often opened by
|
|
267
|
+
that very event, and would otherwise miss it by a frame.
|
|
268
|
+
|
|
269
|
+
## Conversation
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
import { Composer, Thread } from '@liberation-data/desk/react'
|
|
273
|
+
|
|
274
|
+
<Thread messages={messages} me="me" typing={thinking} onRetry={resend} />
|
|
275
|
+
<Composer value={draft} onChange={setDraft} onSubmit={send} busy={thinking} onStop={cancel} />
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Presentational only: no transport, and no opinion about who is answering. The thread groups a run of
|
|
279
|
+
messages from one author, marks yours apart, shows `sending` and `failed` states with Retry, and follows the
|
|
280
|
+
conversation only while you are at the end of it — scroll back and new messages become a *2 new messages*
|
|
281
|
+
button instead of yanking you away. The composer grows with the text, sends on Enter, keeps Shift+Enter for
|
|
282
|
+
a new line, never sends mid-composition in an input method, and turns Send into Stop while an answer is
|
|
283
|
+
arriving.
|
|
284
|
+
|
|
285
|
+
## Lists and choices
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
<Table label="Rides" rows={rides} columns={COLUMNS} rowId={r => r.id}
|
|
289
|
+
selected={selected} onSelect={choose} onActivate={open}
|
|
290
|
+
sort={sort} onSortChange={setSort} empty={<p>No rides yet</p>} />
|
|
291
|
+
|
|
292
|
+
<Sidebar label="Bikes" sections={SECTIONS} value={bike} onChange={setBike} />
|
|
293
|
+
|
|
294
|
+
<PopUpButton label="Distance" options={UNITS} value={units} onChange={setUnits} />
|
|
295
|
+
<Checkbox checked={commutes} onChange={setCommutes} label="Count commutes towards wear" />
|
|
296
|
+
<Slider label="Warn at" value={90} onChange={setThreshold} format={v => `${v}% worn`} />
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
A **table** renders what it is given — no paging, no fetching — and is one tab stop: arrow keys move the
|
|
300
|
+
selection, Enter or a double click opens a row, and a sortable heading says which way it is sorted. A
|
|
301
|
+
**sidebar** is navigation, so where you are is `aria-current` and stays put. A **pop-up button** is what a
|
|
302
|
+
segmented control becomes when there are too many options to show at once; unlike a menu it takes focus, and
|
|
303
|
+
typing jumps to an option. A **checkbox** is for a setting that waits for Save (a Toggle applies at once),
|
|
304
|
+
and can stand for a mixed set. A **slider** reads its value in words for people who cannot see it.
|
|
305
|
+
|
|
306
|
+
## Overlays
|
|
307
|
+
|
|
308
|
+
Least interrupting to most (see [HIG.md](HIG.md) §7):
|
|
309
|
+
|
|
310
|
+
```tsx
|
|
311
|
+
import { Alert, Popover, Sheet, ToastProvider, useToast } from '@liberation-data/desk/react'
|
|
312
|
+
|
|
313
|
+
<Popover open={open} onOpenChange={setOpen} label="More filters"
|
|
314
|
+
trigger={props => <Button {...props}>Filters</Button>}>…</Popover>
|
|
315
|
+
|
|
316
|
+
<Sheet open={exporting} onDismiss={cancel} title="Export rides" actions={…}>…</Sheet>
|
|
317
|
+
|
|
318
|
+
<Alert open={confirming} title="Clear these notes?" confirmLabel="Clear notes" destructive
|
|
319
|
+
onConfirm={clear} onCancel={stop} />
|
|
320
|
+
|
|
321
|
+
const toast = useToast()
|
|
322
|
+
toast.show({ message: 'Ride deleted', action: { label: 'Undo', onSelect: restore } })
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
A **popover** belongs to one control and closes when you press outside. A **sheet** belongs to one window
|
|
326
|
+
and blocks only that window — it renders inside it, so the rest of the desk stays usable. An **alert** stops
|
|
327
|
+
the app and is for what cannot be undone; Cancel comes before the action, and a destructive action is never
|
|
328
|
+
the default button. A **toast** interrupts nothing, carries Undo, and waits while the pointer or focus is on
|
|
329
|
+
it. All three overlays trap focus, give it back on close, and answer Escape topmost-first.
|
|
330
|
+
|
|
331
|
+
## Menu bar
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
import { MenuBar, menuAction, menuCommand, menuSeparator, windowMenuItems } from '@liberation-data/desk/react'
|
|
335
|
+
|
|
336
|
+
<MenuBar
|
|
337
|
+
leading={<AppMark />}
|
|
338
|
+
menus={[
|
|
339
|
+
{ id: 'app', label: 'Garage', items: [menuAction('About Garage', showAbout), menuSeparator(),
|
|
340
|
+
menuCommand('Settings…', 'app.settings', { shortcut: 'mod+comma' })] },
|
|
341
|
+
{ id: 'window', label: 'Window', items: () => [ // a function: re-read each time it opens
|
|
342
|
+
menuCommand('Arrange', DeskCommands.arrange),
|
|
343
|
+
menuSeparator(),
|
|
344
|
+
...windowMenuItems(desk.getState(), desk.focus, titleOf),
|
|
345
|
+
] },
|
|
346
|
+
]}
|
|
347
|
+
status={[{ id: 'service', label: 'Service, 2 due', title: <WrenchWithBadge />, items: dueItems }]}
|
|
348
|
+
trailing={<Clock />}
|
|
349
|
+
/>
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
A `menuCommand` is enabled when something in the responder chain can perform it right now, and chooses the
|
|
353
|
+
same responder a shortcut would: opening a menu never takes focus from the window you were working in. Menus
|
|
354
|
+
own their shortcuts, as in AppKit — a command item's `shortcut` is shown and bound. Keyboard: Tab reaches
|
|
355
|
+
the bar, arrow keys move between menus and items (skipping separators and disabled items), a letter jumps
|
|
356
|
+
to an item, Enter chooses, Escape closes.
|
|
357
|
+
|
|
358
|
+
## Commands and the responder chain
|
|
359
|
+
|
|
360
|
+
Some commands name no target — Copy, Close, Find. They go to whoever is responsible, the way AppKit's
|
|
361
|
+
responder chain works: the focused element, then its window, then the stage, then the app.
|
|
362
|
+
|
|
363
|
+
```tsx
|
|
364
|
+
function RideList() {
|
|
365
|
+
const [selection, setSelection] = useState<Ride[]>([])
|
|
366
|
+
// Inside a window this answers when that window is key.
|
|
367
|
+
useCommand('edit.copy', () => copyRides(selection), { enabled: selection.length > 0 })
|
|
368
|
+
…
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const perform = usePerform() // perform('edit.copy') → true if something handled it
|
|
372
|
+
const canPerform = useCanPerform() // what a menu asks before drawing the item enabled
|
|
373
|
+
useShortcuts(KEYMAP) // { 'mod+c': 'edit.copy', 'mod+shift+z': 'edit.redo' }
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
`isInstalledApp()` says whether the app is running installed rather than in a tab. Only then may it take
|
|
377
|
+
shortcuts the browser owns: ⌘W for close window and ⌘\` for next window, and never ⌘T or ⌘N at all.
|
|
378
|
+
|
|
379
|
+
The chain is the DOM: commands travel as events that bubble from the focused element, so there is no second
|
|
380
|
+
tree to keep in step. The first responder that implements a command decides — if it is disabled, the
|
|
381
|
+
command stops there. `mod` means ⌘ on Apple devices and Ctrl elsewhere; a shortcut nothing handles leaves
|
|
382
|
+
the browser's default alone. The desk answers `DeskCommands` (close, zoom, arrange, next and previous
|
|
383
|
+
window) at the stage, and any window can override them.
|
|
384
|
+
|
|
385
|
+
## The core has no React in it
|
|
386
|
+
|
|
387
|
+
`createDesk()` returns a plain object. Anything can drive it — a router, a tour, a keyboard shortcut, a
|
|
388
|
+
test:
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
const { open, focus, close, float, fill, toggleMode, closeAll, subscribe, getState } = createDesk()
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
State is immutable. `windows` is the order windows were opened, which is the order Arrange lays them out,
|
|
395
|
+
so focusing never reshuffles them. `stack` is back to front; `focusedId(state)` is its last entry.
|
|
396
|
+
|
|
397
|
+
## Links and Back
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
import { syncWithLocation } from '@liberation-data/desk'
|
|
401
|
+
|
|
402
|
+
const stop = syncWithLocation(desk, () => stageSize(), { isKnown: id => id in SURFACES })
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
The hash carries which windows are open, which float, and which has focus — `#w=notes,clock~&f=clock` —
|
|
406
|
+
but never positions: a shared link opens the same things, not someone else's layout on a different
|
|
407
|
+
screen. Opening or closing a window pushes a history entry, so Back closes it; focus and mode changes
|
|
408
|
+
replace the entry. Other hash parameters are left alone.
|
|
409
|
+
|
|
410
|
+
## Theming
|
|
411
|
+
|
|
412
|
+
Every value in `desk.css` reads a `--desk-*` custom property. Redefine the tokens; don't override the
|
|
413
|
+
selectors. Light and dark follow `prefers-color-scheme`, and `data-theme="light|dark"` on the root wins.
|
|
414
|
+
|
|
415
|
+
## Docs
|
|
416
|
+
|
|
417
|
+
- [docs/guide.md](docs/guide.md) — building an app with it, and what catches people out
|
|
418
|
+
- [llms.txt](llms.txt) — the whole public surface in one file, for a coding agent
|
|
419
|
+
- [HIG.md](HIG.md) — how an app built with it should look and behave
|
|
420
|
+
|
|
421
|
+
## Develop
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
npm install
|
|
425
|
+
npm test # vitest
|
|
426
|
+
npm run typecheck
|
|
427
|
+
npm run example # the Garage sample in examples/garage
|
|
428
|
+
npm run size # the gzipped budget: core 10 KiB, react 46, css 10
|
|
429
|
+
npm run check # everything CI runs
|
|
430
|
+
npm run check:browser # the generated-app bridge in real Chrome, against a running sample
|
|
431
|
+
npm run build # dist/
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Licence
|
|
435
|
+
|
|
436
|
+
Apache-2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
|