@orieljs/live 0.1.0-alpha.6 → 0.1.0-alpha.7
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/NOTICE +4 -0
- package/README.md +147 -28
- package/TRADEMARKS.md +33 -0
- package/dist/index.d.ts +168 -12
- package/dist/index.js +679 -137
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/NOTICE
CHANGED
|
@@ -10,3 +10,7 @@ The Oriel name and logo are not covered by that license; see TRADEMARKS.md
|
|
|
10
10
|
|
|
11
11
|
@orieljs/support (and @orieljs/framework, which bundles it) includes transliteration data from
|
|
12
12
|
voku/portable-ascii (https://github.com/voku/portable-ascii), Copyright (C) 2019 Lars Moelleken, MIT License.
|
|
13
|
+
|
|
14
|
+
@orieljs/notifications (and @orieljs/framework, which bundles it) includes the HTML and CSS of
|
|
15
|
+
Laravel's default notification mail (laravel/framework, https://github.com/laravel/framework),
|
|
16
|
+
Copyright (c) Taylor Otwell, MIT License.
|
package/README.md
CHANGED
|
@@ -6,8 +6,7 @@ Server-side Live components for Oriel: Livewire-style components written in TSX,
|
|
|
6
6
|
npm install @orieljs/framework
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
```tsx
|
|
10
|
-
// app/Live/Counter.tsx
|
|
9
|
+
```tsx title="app/Live/Counter.tsx"
|
|
11
10
|
import { Component } from '@orieljs/framework'
|
|
12
11
|
|
|
13
12
|
export default class Counter extends Component {
|
|
@@ -23,28 +22,52 @@ export default class Counter extends Component {
|
|
|
23
22
|
}
|
|
24
23
|
```
|
|
25
24
|
|
|
26
|
-
```ts
|
|
27
|
-
//
|
|
25
|
+
```ts title="routes/web.ts"
|
|
26
|
+
// @filename: app/Live/Counter.tsx
|
|
27
|
+
import { Component } from '@orieljs/framework'
|
|
28
|
+
export default class Counter extends Component { render() { return <p /> } }
|
|
29
|
+
// ---cut---
|
|
28
30
|
import { Route } from '@orieljs/framework'
|
|
29
31
|
import Counter from '../app/Live/Counter'
|
|
30
32
|
|
|
31
33
|
Route.get('/counter', Counter)
|
|
32
34
|
```
|
|
33
35
|
|
|
34
|
-
Embed a component in any view with `<Live component={Counter} />`. In the browser, components are driven by `@orieljs/live-client`.
|
|
36
|
+
Embed a component in any view with `<Live component={Counter} />`. In the browser, components are driven by `@orieljs/live-client`. Public fields are a component's state and its public methods are actions. `mount()` and `render()` may be async (`const user = await request().user<User>()`), and `await this.validate(rules)` returns the validated data, typed from the rules.
|
|
35
37
|
|
|
36
38
|
## Typed actions
|
|
37
39
|
|
|
38
40
|
`this.$action(method, ...arguments)` writes an action for an `o:` directive, typed against the method:
|
|
39
41
|
|
|
40
|
-
```tsx
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
```tsx title="app/Live/Drafts.tsx"
|
|
43
|
+
import { Component } from '@orieljs/framework'
|
|
44
|
+
import { Post } from '../Models/Post'
|
|
45
|
+
|
|
46
|
+
export default class Drafts extends Component {
|
|
47
|
+
async publish(id: number) {
|
|
48
|
+
const post = await Post.findOrFail(id)
|
|
49
|
+
post.published_at = new Date()
|
|
50
|
+
await post.save()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async render() {
|
|
54
|
+
const drafts = await Post.whereNull('published_at').get()
|
|
55
|
+
return (
|
|
56
|
+
<ul>
|
|
57
|
+
{drafts.map((post) => (
|
|
58
|
+
<li>
|
|
59
|
+
{post.title}
|
|
60
|
+
<button type="button" o:click={this.$action(this.publish, post.id)}>Publish</button>
|
|
61
|
+
</li>
|
|
62
|
+
))}
|
|
63
|
+
</ul>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
44
67
|
```
|
|
45
68
|
|
|
46
|
-
|
|
47
|
-
`this.$action(this.
|
|
69
|
+
For post 5 this renders `o:click="publish(5)"`, and since `publish(id: number)` takes a number,
|
|
70
|
+
`this.$action(this.publish, String(post.id))` is a type error. The arguments are written as JSON, so pass ids and
|
|
48
71
|
plain values (strings, numbers, booleans, `null`, arrays and plain objects): a model arrives as its attributes and a
|
|
49
72
|
date as its ISO string, so pass `post.id`, not `post`. The action contains double quotes, so use it as a TSX attribute
|
|
50
73
|
value, which escapes them, not inside an HTML string you write by hand. Rendering throws when the method isn't one of
|
|
@@ -56,6 +79,8 @@ request can still send anything, so an action checks what it is given, as a cont
|
|
|
56
79
|
`o:confirm` asks before an action runs, as Livewire's `wire:confirm`:
|
|
57
80
|
|
|
58
81
|
```tsx
|
|
82
|
+
declare const tag: { id: number }
|
|
83
|
+
// ---cut---
|
|
59
84
|
<button type="button" class="btn btn-danger" o:click={`delete(${tag.id})`} o:confirm="Delete this tag?">
|
|
60
85
|
Delete
|
|
61
86
|
</button>
|
|
@@ -65,22 +90,22 @@ The browser's confirm dialog shows the message (an empty `o:confirm` asks "Are y
|
|
|
65
90
|
and the action runs only when it is accepted. `o:confirm-prompt="Type DELETE to confirm|DELETE"` (`o:confirm.prompt`
|
|
66
91
|
in plain HTML) asks with a prompt instead, and runs the action only when the answer is exactly the text after the `|`.
|
|
67
92
|
It works on any element with an `o:` action: `o:click`, `o:submit` on a form, `o:keydown-enter`, and the others. A
|
|
68
|
-
dismissed dialog stops the event, so the actions of the elements around it don't run either.
|
|
69
|
-
actions of the element it sits on: on a form, put it on the
|
|
70
|
-
its own `o:click` needs `type="button"` or `-prevent`
|
|
71
|
-
the dialog is dismissed.
|
|
93
|
+
dismissed dialog stops the event, so the actions of the elements around it don't run either. With `-once`, a dismissed
|
|
94
|
+
dialog doesn't use the action up. `o:confirm` guards the actions of the element it sits on: on a form, put it on the
|
|
95
|
+
`<form>` with the `o:submit`. A button inside a form with its own `o:click` needs `type="button"` or `-prevent`
|
|
96
|
+
(`o:click-prevent`), or the browser still submits the form when the dialog is dismissed.
|
|
72
97
|
|
|
73
98
|
## Pagination
|
|
74
99
|
|
|
75
|
-
```tsx
|
|
100
|
+
```tsx title="app/Live/Posts.tsx"
|
|
76
101
|
import { Component, Pagination, WithPagination } from '@orieljs/framework'
|
|
77
102
|
import { Post } from '../Models/Post'
|
|
78
103
|
|
|
79
104
|
export default class Posts extends WithPagination(Component) {
|
|
80
105
|
search = ''
|
|
81
106
|
|
|
82
|
-
updatedSearch() {
|
|
83
|
-
this.resetPage()
|
|
107
|
+
async updatedSearch() {
|
|
108
|
+
await this.resetPage()
|
|
84
109
|
}
|
|
85
110
|
|
|
86
111
|
async render() {
|
|
@@ -98,8 +123,81 @@ export default class Posts extends WithPagination(Component) {
|
|
|
98
123
|
|
|
99
124
|
`paginate()` in the component (in `render()`, in `mount()`, in actions and the update hooks, or in a `@Computed`
|
|
100
125
|
getter any of them reads) shows the component's `page`, and its links are built on the page's URL. The actions
|
|
101
|
-
`nextPage`, `previousPage`, `gotoPage(n)`, `setPage(n)` and `resetPage` move it
|
|
102
|
-
buttons with the kits' `btn` classes. The page starts at 1 and isn't kept
|
|
126
|
+
`nextPage`, `previousPage`, `gotoPage(n)`, `setPage(n)` and `resetPage` move it (they are async, so await them in your
|
|
127
|
+
own methods); `<Pagination>` renders them as buttons with the kits' `btn` classes. The page starts at 1 and isn't kept
|
|
128
|
+
in the address bar.
|
|
129
|
+
|
|
130
|
+
## File uploads
|
|
131
|
+
|
|
132
|
+
```tsx title="app/Live/ProfilePhoto.tsx"
|
|
133
|
+
import { Component, TemporaryUploadedFile, WithFileUploads } from '@orieljs/framework'
|
|
134
|
+
|
|
135
|
+
export default class ProfilePhoto extends WithFileUploads(Component) {
|
|
136
|
+
photo: TemporaryUploadedFile | null = null
|
|
137
|
+
|
|
138
|
+
async save() {
|
|
139
|
+
const { photo } = await this.validate({ photo: 'required|image|max:1024' })
|
|
140
|
+
await photo.store('photos')
|
|
141
|
+
this.reset('photo')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
render() {
|
|
145
|
+
return (
|
|
146
|
+
<form o:submit="save">
|
|
147
|
+
<input type="file" o:model="photo" />
|
|
148
|
+
{this.photo?.isPreviewable() ? <img src={this.photo.temporaryUrl()} alt="Preview" /> : null}
|
|
149
|
+
<button type="submit">Save</button>
|
|
150
|
+
</form>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
In a `WithFileUploads` component, `o:model` on a file input uploads the picked file at once, to a temporary
|
|
157
|
+
directory, and sets the property to a `TemporaryUploadedFile` (an array of them with `multiple`). Validate it with the
|
|
158
|
+
file rules, preview it with `temporaryUrl()` (a signed link, for images, audio and video), and `store()` it. In tests,
|
|
159
|
+
`Live.test()`'s `set('photo', file)` uploads a file the same way. Every upload must also pass
|
|
160
|
+
`live.temporary_file_upload.rules`, which allow 12 MB (`max:12288`), but a request body larger than
|
|
161
|
+
`http.max_body_size` (8 MB, for every request) gets a 413: raise it in `config/http.ts` to accept files up to the
|
|
162
|
+
rule's size.
|
|
163
|
+
|
|
164
|
+
## Lazy components
|
|
165
|
+
|
|
166
|
+
```tsx title="app/Live/PostCount.tsx"
|
|
167
|
+
import { Component } from '@orieljs/framework'
|
|
168
|
+
import { Post } from '../Models/Post'
|
|
169
|
+
|
|
170
|
+
export default class PostCount extends Component {
|
|
171
|
+
static override lazy = true
|
|
172
|
+
count = 0
|
|
173
|
+
|
|
174
|
+
async mount() {
|
|
175
|
+
this.count = await Post.count()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
placeholder() {
|
|
179
|
+
return <p>Counting posts…</p>
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
render() {
|
|
183
|
+
return <p>{this.count} posts</p>
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
With `static override lazy = true`, each `<Live component={PostCount} />` (and the full page) renders the
|
|
189
|
+
placeholder first and mounts the component once it scrolls into view. `lazy` on one embed makes that embed lazy,
|
|
190
|
+
`lazy="on-load"` mounts it as soon as the page loads, and `lazy={false}` turns `static lazy` off. Without
|
|
191
|
+
`placeholder()`, the placeholder is the `live.lazy_placeholder` view, else an empty `<div>`; it needs the same root
|
|
192
|
+
element as `render()`.
|
|
193
|
+
|
|
194
|
+
## Navigating
|
|
195
|
+
|
|
196
|
+
`<a href="/posts" o:navigate>` shows the next page without a full reload: Live fetches it (from `mousedown`, or on
|
|
197
|
+
hover with `o:navigate-hover`), merges its `<head>`, swaps the `<body>` and starts the new page's components.
|
|
198
|
+
`this.redirect(url, { navigate: true })` does the same from an action. The new page's scripts run as on a full load,
|
|
199
|
+
except a `<head>` script the page already has, and a `<body>` script marked `data-navigate-once` that an earlier page
|
|
200
|
+
already ran.
|
|
103
201
|
|
|
104
202
|
## Middleware on updates
|
|
105
203
|
|
|
@@ -107,7 +205,7 @@ Each update re-runs the `auth`, `verified`, `guest` and `can` middleware of the
|
|
|
107
205
|
(Livewire's persistent middleware), with that route's parameters bound again, so `can:update,post` sees the post. A
|
|
108
206
|
signed-out tab gets a 401 and a user who lost the ability a 403, before the component's actions run. On a page behind
|
|
109
207
|
one of these middleware, a model deleted since the page rendered gets a 404 too; a page with none binds nothing on
|
|
110
|
-
updates. The list is `live.persistent_middleware
|
|
208
|
+
updates. The list is `live.persistent_middleware`, which a `config/live.ts` of your own can set.
|
|
111
209
|
|
|
112
210
|
## Flash data in updates
|
|
113
211
|
|
|
@@ -120,10 +218,29 @@ Screen readers announce a status region (`role="status"`) when its text changes.
|
|
|
120
218
|
message again ("Saved" after a second save), the page doesn't change and nothing is announced. Add `o:announce` to the
|
|
121
219
|
region and Live takes the message out and puts it back a moment later, so it is announced again:
|
|
122
220
|
|
|
123
|
-
```tsx
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
221
|
+
```tsx title="app/Live/Settings.tsx"
|
|
222
|
+
import { Component } from '@orieljs/framework'
|
|
223
|
+
|
|
224
|
+
export default class Settings extends Component {
|
|
225
|
+
name = ''
|
|
226
|
+
#saved = false
|
|
227
|
+
|
|
228
|
+
save() {
|
|
229
|
+
this.#saved = true
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
render() {
|
|
233
|
+
return (
|
|
234
|
+
<form o:submit="save">
|
|
235
|
+
<input o:model="name" aria-label="Name" />
|
|
236
|
+
<button type="submit">Save</button>
|
|
237
|
+
<p role="status" o:announce>
|
|
238
|
+
{this.#saved ? 'Saved' : ''}
|
|
239
|
+
</p>
|
|
240
|
+
</form>
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
127
244
|
```
|
|
128
245
|
|
|
129
246
|
Only actions do this, and an `o:poll` that calls a method counts as one: an update that only syncs `o:model` values,
|
|
@@ -134,13 +251,15 @@ are taken out for 100 ms, so a button or input inside it would lose focus.
|
|
|
134
251
|
|
|
135
252
|
## Known limitations
|
|
136
253
|
|
|
137
|
-
- `o
|
|
138
|
-
-
|
|
254
|
+
- A DOM event whose name contains `-` can't be an `o:<event>` action, because `-` (and `.`) separate the modifiers (for example `o:model-live`).
|
|
255
|
+
- `temporaryUrl()` previews don't answer range requests, which Safari needs to play a `<video>`.
|
|
256
|
+
|
|
257
|
+
Documentation: https://orieljs.vercel.app/docs/live
|
|
139
258
|
|
|
140
259
|
Status: early alpha. APIs will change.
|
|
141
260
|
|
|
142
261
|
## License
|
|
143
262
|
|
|
144
|
-
Oriel is open-source software licensed under the Apache License 2.0 (Apache-2.0; see `LICENSE` and `NOTICE`). The Oriel name and logo are trademarks; see
|
|
263
|
+
Oriel is open-source software licensed under the Apache License 2.0 (Apache-2.0; see `LICENSE` and `NOTICE`). The Oriel name and logo are trademarks; see `TRADEMARKS.md`.
|
|
145
264
|
|
|
146
265
|
Website: https://orieljs.vercel.app
|
package/TRADEMARKS.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Oriel trademark policy
|
|
2
|
+
|
|
3
|
+
The Oriel code is open source under the Apache License 2.0. The **Oriel name and logo** are not: the license covers
|
|
4
|
+
the code only (section 6 of the license says so), and the name and logo stay with the project. This page explains
|
|
5
|
+
what you can and can't do with them. The goal is simple: people should always be able to tell what is the real Oriel
|
|
6
|
+
and what isn't.
|
|
7
|
+
|
|
8
|
+
## You can, without asking
|
|
9
|
+
|
|
10
|
+
- Use, change, ship and sell the code, as the license allows.
|
|
11
|
+
- Say that your app or package is "built with Oriel", "for Oriel" or "compatible with Oriel".
|
|
12
|
+
- Name a package `oriel-<something>` or `<something>-for-oriel` when it works with Oriel, as long as it is clear that
|
|
13
|
+
it is not official (for example, don't publish it under the `@orieljs` npm scope).
|
|
14
|
+
- Use the name and logo to write about Oriel, teach it, or link to it, in articles, talks, videos and courses.
|
|
15
|
+
- Use the unmodified logo to point to the project, as it is (no recolouring, cropping or
|
|
16
|
+
distortion).
|
|
17
|
+
|
|
18
|
+
## You can't, without written permission
|
|
19
|
+
|
|
20
|
+
- Call a fork, a modified copy or a competing product "Oriel", or anything likely to be confused with it.
|
|
21
|
+
- Use the name or logo as the name, logo or domain of your own product, company or service.
|
|
22
|
+
- Suggest that the Oriel project made, endorses or supports your work when it doesn't.
|
|
23
|
+
- Sell merchandise with the name or logo.
|
|
24
|
+
|
|
25
|
+
## Forks
|
|
26
|
+
|
|
27
|
+
Forks are welcome. Please give yours a different name and logo, keep the `LICENSE` and `NOTICE` files, and say that
|
|
28
|
+
it is based on Oriel.
|
|
29
|
+
|
|
30
|
+
## Questions
|
|
31
|
+
|
|
32
|
+
Open an issue at https://github.com/orieljs/framework or email meeeet.dev@gmail.com. If you're unsure whether a use
|
|
33
|
+
is fine, ask; the answer is usually yes.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _orieljs_contracts from '@orieljs/contracts';
|
|
2
|
-
import { Class, Rules, MessageBag, LiveEffects, Htmlable, LiveSnapshot, LiveManager, Application, HttpRequest, LiveUpdateRequest, LiveUpdateResponse, OrielError, ErrorKind, MiddlewareClass, Router } from '@orieljs/contracts';
|
|
2
|
+
import { Class, RouteName, RouteArguments, Rules, InferValidatedType, MessageBag, LiveEffects, Htmlable, LiveSnapshot, LiveManager, Application, HttpRequest, LiveUpdateRequest, LiveUpdateResponse, OrielError, ErrorKind, UploadedFile, MiddlewareClass, Router } from '@orieljs/contracts';
|
|
3
3
|
import { ServiceProvider } from '@orieljs/support';
|
|
4
4
|
|
|
5
5
|
/** Marks Live component classes (Symbol.for, so two copies of the package agree). */
|
|
@@ -13,8 +13,11 @@ interface QueuedDispatch {
|
|
|
13
13
|
}
|
|
14
14
|
/** The validation errors of a component (Laravel's MessageBag), for render(): `this.errors.first('title')`. */
|
|
15
15
|
declare class ErrorBag implements MessageBag {
|
|
16
|
+
#private;
|
|
16
17
|
private readonly messages;
|
|
17
18
|
constructor(messages: Record<string, string[]>);
|
|
19
|
+
/** Adds to the component's errors, as addError() does. */
|
|
20
|
+
add(key: string, message: string): void;
|
|
18
21
|
has(key: string): boolean;
|
|
19
22
|
first(key?: string): string | undefined;
|
|
20
23
|
get(key: string): string[];
|
|
@@ -45,6 +48,13 @@ declare abstract class Component {
|
|
|
45
48
|
static layout?: string;
|
|
46
49
|
/** The page title passed to the layout as `title`. */
|
|
47
50
|
static title?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Livewire's #[Lazy]: every embed (and the full page) renders the placeholder first and mounts once the browser shows
|
|
53
|
+
* it. `<Live component={…} lazy={false} />` mounts one at once. The placeholder is the component's
|
|
54
|
+
* `placeholder(params)` method (TSX), else the `live.lazy_placeholder` view, else an empty `<div>`: give it the root
|
|
55
|
+
* element the component renders.
|
|
56
|
+
*/
|
|
57
|
+
static lazy?: boolean;
|
|
48
58
|
/** Static fallbacks for runtimes without decorators: { event: 'method' }, locked names, computed getter names. */
|
|
49
59
|
static listeners?: Record<string, string>;
|
|
50
60
|
static locked?: string[];
|
|
@@ -58,14 +68,19 @@ declare abstract class Component {
|
|
|
58
68
|
redirect(url: string, options?: {
|
|
59
69
|
navigate?: boolean;
|
|
60
70
|
}): void;
|
|
61
|
-
|
|
71
|
+
/**
|
|
72
|
+
* redirect() to a named route: `this.redirectRoute('posts.show', post, { navigate: true })`. Once `oriel
|
|
73
|
+
* route:types` has run, the name and the route's parameters are checked, as route()'s are.
|
|
74
|
+
*/
|
|
75
|
+
redirectRoute<N extends RouteName>(name: N, ...args: RouteArguments<N, [options?: {
|
|
62
76
|
navigate?: boolean;
|
|
63
|
-
}): void;
|
|
77
|
+
}]>): void;
|
|
64
78
|
/**
|
|
65
|
-
* Validates the public state. On success clears the error bag and returns the validated data
|
|
66
|
-
*
|
|
79
|
+
* Validates the public state. On success clears the error bag and returns the validated data, typed from the rules
|
|
80
|
+
* (`await this.validate({ title: 'required|string' })` is `{ title: string }`); on failure fills the error bag and
|
|
81
|
+
* throws (the rest of the action is skipped and the component re-renders with `errors`).
|
|
67
82
|
*/
|
|
68
|
-
validate<
|
|
83
|
+
validate<const R extends Rules>(rules: R, messages?: Record<string, string>, attributes?: Record<string, string>): Promise<InferValidatedType<R>>;
|
|
69
84
|
addError(key: string, message: string): void;
|
|
70
85
|
/** Clears every error, or only those of `keys`. */
|
|
71
86
|
resetErrorBag(...keys: string[]): void;
|
|
@@ -109,7 +124,10 @@ declare class Testable<C extends Component = Component> {
|
|
|
109
124
|
private readonly manager;
|
|
110
125
|
private readonly scope;
|
|
111
126
|
private constructor();
|
|
112
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Mounts `component` with `params`, as `<Live component={…} {...params} />` would. A lazy component mounts at once
|
|
129
|
+
* (Livewire's withoutLazyLoading()); `{ lazy: true }` renders its placeholder, and `call('__lazyLoad', …)` loads it.
|
|
130
|
+
*/
|
|
113
131
|
static create<C extends Component>(component: Class<C> | string, params?: Record<string, unknown>): Promise<Testable<C>>;
|
|
114
132
|
/** The component instance after the last request. */
|
|
115
133
|
instance(): C;
|
|
@@ -119,7 +137,14 @@ declare class Testable<C extends Component = Component> {
|
|
|
119
137
|
html(): string;
|
|
120
138
|
/** The effects of the last request. */
|
|
121
139
|
effects(): LiveEffects;
|
|
140
|
+
/**
|
|
141
|
+
* Sets public properties through an update. A file (a FakeFile) or an array of files is uploaded as `o:model` on a
|
|
142
|
+
* file input does it: the upload rules, the temporary disk, then a TemporaryUploadedFile (or an array) in the
|
|
143
|
+
* property, or the upload's errors on it (Livewire's).
|
|
144
|
+
*/
|
|
122
145
|
set(path: string | Record<string, unknown>, value?: unknown): Promise<this>;
|
|
146
|
+
/** Uploads `files` into the property `name` (an array when `multiple`), as the browser's upload calls do. */
|
|
147
|
+
upload(name: string, files: readonly Blob[], multiple?: boolean): Promise<this>;
|
|
123
148
|
call(method: string, ...params: unknown[]): Promise<this>;
|
|
124
149
|
toggle(path: string): Promise<this>;
|
|
125
150
|
/** Dispatches an event to this component, as the browser does for its listeners. */
|
|
@@ -152,6 +177,11 @@ interface LiveProps {
|
|
|
152
177
|
component: Class<Component> | string;
|
|
153
178
|
/** Identifies a child inside its parent across re-renders (Livewire's :key / wire:key). */
|
|
154
179
|
'o:key'?: string | number;
|
|
180
|
+
/**
|
|
181
|
+
* Renders the placeholder and mounts the component once it scrolls into view (`'on-load'`: once the page starts);
|
|
182
|
+
* `false` mounts a `static lazy` component at once.
|
|
183
|
+
*/
|
|
184
|
+
lazy?: boolean | 'on-load';
|
|
155
185
|
[param: string]: unknown;
|
|
156
186
|
}
|
|
157
187
|
/** Blade's @livewire('counter', ['count' => 3], key('c1')): the component's first render, as trusted HTML. */
|
|
@@ -194,10 +224,25 @@ declare class LiveComponentManager implements LiveManager {
|
|
|
194
224
|
isLiveRequest(request: HttpRequest): boolean;
|
|
195
225
|
/** The first render: HTML with o:id, o:snapshot and o:effects on the root element. */
|
|
196
226
|
mount(component: string | Class<unknown>, params?: Record<string, unknown>, key?: string): Promise<string>;
|
|
197
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Mounts a component: boot(), the params into its properties, mount(params), then its first render. A lazy one
|
|
229
|
+
* (`lazy` in the params, or `static lazy`) renders its placeholder instead and mounts when the browser loads it.
|
|
230
|
+
*/
|
|
231
|
+
mountComponent(component: string | Class<unknown>, embedParams?: Record<string, unknown>, existing?: Component): Promise<ComponentResult>;
|
|
232
|
+
/**
|
|
233
|
+
* A lazy component's first render (Livewire's): its placeholder, with the state it has before mount() and the mount
|
|
234
|
+
* params signed for the `__lazyLoad` call the browser makes when it shows it (`o:lazy`, or `o:lazy-on-load`).
|
|
235
|
+
*/
|
|
236
|
+
private mountPlaceholder;
|
|
198
237
|
/** Applies each component's updates and calls, re-renders it and returns its new snapshot and effects. */
|
|
199
238
|
update(request: LiveUpdateRequest): Promise<LiveUpdateResponse>;
|
|
200
239
|
updateComponent(snapshotJson: string, updates: Record<string, unknown>, calls: Call[]): Promise<ComponentResult>;
|
|
240
|
+
/**
|
|
241
|
+
* The mount params and placeholder tag of a `__lazyLoad` request, or undefined for any other update. The load comes
|
|
242
|
+
* alone (no updates, no other call: the browser sends it in a request of its own) and its payload must be the one
|
|
243
|
+
* signed for this component: anything else is refused as corrupt (a 403) before the component boots.
|
|
244
|
+
*/
|
|
245
|
+
private lazyLoad;
|
|
201
246
|
/**
|
|
202
247
|
* The route action of POST /oriel/live/update. Each component's update runs behind the persistent middleware of the
|
|
203
248
|
* route that rendered its page (Livewire's): `auth`, `verified`, `guest` and `can`, with the page route's parameters
|
|
@@ -216,7 +261,7 @@ declare class LiveComponentManager implements LiveManager {
|
|
|
216
261
|
/** Makes every computed getter (static `computed` too) cache its value for the rest of the request. */
|
|
217
262
|
private prepareComputed;
|
|
218
263
|
private applyUpdate;
|
|
219
|
-
/** Runs one call from the browser: a magic action, an event, or a public method. Returns its return value. */
|
|
264
|
+
/** Runs one call from the browser: a magic action, an event, an upload call, or a public method. Returns its return value. */
|
|
220
265
|
private call;
|
|
221
266
|
/**
|
|
222
267
|
* Renders the component (rendering/rendered hooks, nested children tracked) and checks for a single root. A
|
|
@@ -236,8 +281,9 @@ declare class LiveComponentManager implements LiveManager {
|
|
|
236
281
|
declare const UPDATE_URI = "/oriel/live/update";
|
|
237
282
|
/**
|
|
238
283
|
* Binds the LiveManager and the PaginationState that WithPagination components answer, merges the `live` config,
|
|
239
|
-
* registers POST /oriel/live/update (named `live.update`, in the `web` group: sessions and CSRF apply), the
|
|
240
|
-
* command, and the
|
|
284
|
+
* registers POST /oriel/live/update (named `live.update`, in the `web` group: sessions and CSRF apply), the upload and
|
|
285
|
+
* preview routes of WithFileUploads (`live.upload-file`, `live.preview-file`), the make:live command, and the
|
|
286
|
+
* components under app/Live.
|
|
241
287
|
*/
|
|
242
288
|
declare class LiveServiceProvider extends ServiceProvider {
|
|
243
289
|
register(): void;
|
|
@@ -287,6 +333,108 @@ declare class InvalidLiveRequestError extends LiveError {
|
|
|
287
333
|
readonly kind: ErrorKind;
|
|
288
334
|
readonly status: number;
|
|
289
335
|
}
|
|
336
|
+
/** An upload or preview URL whose signature is missing, wrong or expired (Livewire's 401). Nothing is read or stored. */
|
|
337
|
+
declare class InvalidUploadSignatureError extends LiveError {
|
|
338
|
+
readonly name: string;
|
|
339
|
+
readonly kind: ErrorKind;
|
|
340
|
+
readonly status: number;
|
|
341
|
+
constructor();
|
|
342
|
+
}
|
|
343
|
+
/** A preview of a temporary upload that is no longer on the disk (cleaned up, or stored and deleted). */
|
|
344
|
+
declare class TemporaryFileNotFoundError extends LiveError {
|
|
345
|
+
readonly name: string;
|
|
346
|
+
readonly kind: ErrorKind;
|
|
347
|
+
readonly status: number;
|
|
348
|
+
constructor(filename: string);
|
|
349
|
+
}
|
|
350
|
+
/** temporaryUrl() on an upload whose type has no preview (live.temporary_file_upload.preview_mimes). */
|
|
351
|
+
declare class FileNotPreviewableError extends LiveError {
|
|
352
|
+
readonly name: string;
|
|
353
|
+
constructor(extension: string);
|
|
354
|
+
}
|
|
355
|
+
/** The errors an upload came back with (`_uploadErrored`): the component re-renders with them on the property. */
|
|
356
|
+
declare class UploadValidationError extends LiveError {
|
|
357
|
+
readonly errors: Record<string, string[]>;
|
|
358
|
+
readonly name: string;
|
|
359
|
+
readonly kind: ErrorKind;
|
|
360
|
+
readonly status: number;
|
|
361
|
+
readonly errorBag: string;
|
|
362
|
+
constructor(errors: Record<string, string[]>);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Livewire's `temporary_file_upload` settings, read from config `live.temporary_file_upload.*` (each one falls back on
|
|
367
|
+
* its own): the disk (the default filesystem disk), the directory on it, the rules every upload must pass, the upload
|
|
368
|
+
* route's middleware, the types a preview is served for, how long an upload URL lasts (minutes), and whether a finished
|
|
369
|
+
* upload removes the temporary files older than a day.
|
|
370
|
+
*/
|
|
371
|
+
declare const TEMPORARY_UPLOAD_DEFAULTS: {
|
|
372
|
+
disk: string | null;
|
|
373
|
+
directory: string;
|
|
374
|
+
rules: string | string[];
|
|
375
|
+
middleware: string | string[];
|
|
376
|
+
preview_mimes: string[];
|
|
377
|
+
max_upload_time: number;
|
|
378
|
+
cleanup: boolean;
|
|
379
|
+
};
|
|
380
|
+
/** What a temporary upload is: its stored name on the temporary disk and what the browser sent (signed with it). */
|
|
381
|
+
interface TemporaryFileData {
|
|
382
|
+
/** A random 40-character name with the extension its bytes showed: `live-tmp/<filename>` on the temporary disk. */
|
|
383
|
+
filename: string;
|
|
384
|
+
/** The name the browser gave the file. */
|
|
385
|
+
name: string;
|
|
386
|
+
size: number;
|
|
387
|
+
/** The MIME type the browser claimed. */
|
|
388
|
+
type: string;
|
|
389
|
+
/** The extension of the upload's own type (its bytes, else a plain claimed extension), or ''. */
|
|
390
|
+
extension: string;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Livewire's TemporaryUploadedFile: a file the browser uploaded to the temporary directory before an action stores
|
|
394
|
+
* it. A WithFileUploads component's property holds one (or an array of them) once `o:model` on a file input has
|
|
395
|
+
* uploaded it: validate it like any upload (`'photo' => 'image|max:1024'`), preview it with `temporaryUrl()`, then
|
|
396
|
+
* `store()` it. It travels in the component's snapshot as its data, signed with the rest.
|
|
397
|
+
*/
|
|
398
|
+
declare class TemporaryUploadedFile implements UploadedFile {
|
|
399
|
+
#private;
|
|
400
|
+
readonly filename: string;
|
|
401
|
+
readonly clientName: string;
|
|
402
|
+
readonly mimeType: string;
|
|
403
|
+
readonly size: number;
|
|
404
|
+
constructor(data: TemporaryFileData);
|
|
405
|
+
/** What the snapshot carries. */
|
|
406
|
+
toData(): TemporaryFileData;
|
|
407
|
+
/** The file's path on the temporary disk. */
|
|
408
|
+
get path(): string;
|
|
409
|
+
/** The extension of the upload's own type, as FormUploadedFile's extension(): what putFile() names a stored copy by. */
|
|
410
|
+
extension(): string;
|
|
411
|
+
/** The extension of the name the browser gave the file, lower-cased ('' without one). */
|
|
412
|
+
clientExtension(): string;
|
|
413
|
+
/** A random 40-character name with extension(), as FormUploadedFile's. */
|
|
414
|
+
hashName(): string;
|
|
415
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
416
|
+
text(): Promise<string>;
|
|
417
|
+
/** The file's bytes from the temporary disk, opened on the first read. */
|
|
418
|
+
stream(): ReadableStream<Uint8Array>;
|
|
419
|
+
/** Stores a copy under a random name in `directory`, on `disk` (by default the temporary disk, as Livewire's). */
|
|
420
|
+
store(directory: string, disk?: string): Promise<string>;
|
|
421
|
+
storeAs(directory: string, name: string, disk?: string): Promise<string>;
|
|
422
|
+
exists(): Promise<boolean>;
|
|
423
|
+
/** Removes the temporary file (a stored copy stays). */
|
|
424
|
+
delete(): Promise<boolean>;
|
|
425
|
+
/** Whether temporaryUrl() serves this file: its type is in `live.temporary_file_upload.preview_mimes`. */
|
|
426
|
+
isPreviewable(): boolean;
|
|
427
|
+
/**
|
|
428
|
+
* A signed link to the file for `<img src>` or `<video src>` before it is stored. It lasts until the end of the hour
|
|
429
|
+
* at least 30 minutes away (Livewire's), so a re-render keeps the same link and the browser doesn't reload it.
|
|
430
|
+
*/
|
|
431
|
+
temporaryUrl(): string;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Where the browser POSTs a WithFileUploads component's files (Livewire's /livewire/upload-file), named live.upload-file. */
|
|
435
|
+
declare const UPLOAD_URI = "/oriel/live/upload-file";
|
|
436
|
+
/** Where temporaryUrl() links lead (Livewire's /livewire/preview-file/{filename}), named live.preview-file. */
|
|
437
|
+
declare const PREVIEW_URI = "/oriel/live/preview-file";
|
|
290
438
|
|
|
291
439
|
/** The route middleware an update re-applies by default (config live.persistent_middleware), by alias. */
|
|
292
440
|
declare const PERSISTENT_MIDDLEWARE: string[];
|
|
@@ -322,6 +470,14 @@ interface Paginated {
|
|
|
322
470
|
*/
|
|
323
471
|
declare function WithPagination<TBase extends ComponentConstructor>(Base: TBase): TBase & (abstract new (...args: any[]) => Paginated);
|
|
324
472
|
|
|
473
|
+
/**
|
|
474
|
+
* Livewire's WithFileUploads: `class Profile extends WithFileUploads(Component)`. A file input with `o:model="photo"`
|
|
475
|
+
* uploads the file to the temporary directory as soon as it is picked, then sets `photo` to a TemporaryUploadedFile
|
|
476
|
+
* (`<input type="file" multiple>` sets an array), through `updatingPhoto()`/`updatedPhoto()` like any update. Validate
|
|
477
|
+
* it with the file rules, preview it with `temporaryUrl()`, store it with `store()`.
|
|
478
|
+
*/
|
|
479
|
+
declare function WithFileUploads<TBase extends ComponentConstructor>(Base: TBase): TBase;
|
|
480
|
+
|
|
325
481
|
/**
|
|
326
482
|
* What <Pagination> reads from a paginator. Structural, since Live can't import Database: paginate()'s
|
|
327
483
|
* LengthAwarePaginator (with lastPage(), so page buttons) and simplePaginate()'s Paginator (Previous and Next only).
|
|
@@ -349,4 +505,4 @@ interface PaginationProps {
|
|
|
349
505
|
*/
|
|
350
506
|
declare function Pagination({ paginator, class: className }: PaginationProps): Htmlable;
|
|
351
507
|
|
|
352
|
-
export { Component, type ComponentResult, Computed, CorruptComponentPayloadError, ErrorBag, InvalidLiveRequestError, Live, LiveComponentManager, LiveComponentNotFoundError, LiveError, type LiveProps, LiveServiceProvider, Locked, MethodNotCallableError, On, PERSISTENT_MIDDLEWARE, type Paginated, Pagination, type PaginationProps, type PaginationSource, PendingEvent, type PersistentMiddleware, PropertyNotUpdatableError, TestRequestScope, Testable, UPDATE_URI, WithPagination, componentName, live, persistentMiddleware };
|
|
508
|
+
export { Component, type ComponentResult, Computed, CorruptComponentPayloadError, ErrorBag, FileNotPreviewableError, InvalidLiveRequestError, InvalidUploadSignatureError, Live, LiveComponentManager, LiveComponentNotFoundError, LiveError, type LiveProps, LiveServiceProvider, Locked, MethodNotCallableError, On, PERSISTENT_MIDDLEWARE, PREVIEW_URI, type Paginated, Pagination, type PaginationProps, type PaginationSource, PendingEvent, type PersistentMiddleware, PropertyNotUpdatableError, TEMPORARY_UPLOAD_DEFAULTS, type TemporaryFileData, TemporaryFileNotFoundError, TemporaryUploadedFile, TestRequestScope, Testable, UPDATE_URI, UPLOAD_URI, UploadValidationError, WithFileUploads, WithPagination, componentName, live, persistentMiddleware };
|