@e280/sly 0.2.0-3 → 0.2.0-5
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/README.md +217 -17
- package/package.json +1 -1
- package/s/demo/demo.bundle.ts +6 -1
- package/s/demo/views/demo.ts +1 -0
- package/s/demo/views/incredi.ts +28 -0
- package/s/dom/dom.ts +25 -13
- package/s/index.html.ts +1 -0
- package/s/index.ts +1 -0
- package/s/loot/drag-and-drops.ts +82 -0
- package/s/loot/{drop.ts → drops.ts} +8 -17
- package/s/loot/helpers.ts +3 -3
- package/s/loot/index.ts +2 -2
- package/s/views/attributes.ts +6 -6
- package/s/views/base-element.ts +84 -0
- package/s/views/use.ts +1 -1
- package/s/views/view.ts +4 -4
- package/x/demo/demo.bundle.js +5 -1
- package/x/demo/demo.bundle.js.map +1 -1
- package/x/demo/demo.bundle.min.js +17 -14
- package/x/demo/demo.bundle.min.js.map +4 -4
- package/x/demo/views/demo.js +1 -0
- package/x/demo/views/demo.js.map +1 -1
- package/x/demo/views/incredi.d.ts +12 -0
- package/x/demo/views/incredi.js +21 -0
- package/x/demo/views/incredi.js.map +1 -0
- package/x/dom/dom.d.ts +6 -5
- package/x/dom/dom.js +19 -11
- package/x/dom/dom.js.map +1 -1
- package/x/index.d.ts +1 -0
- package/x/index.html +3 -2
- package/x/index.html.js +1 -0
- package/x/index.html.js.map +1 -1
- package/x/index.js +1 -0
- package/x/index.js.map +1 -1
- package/x/loot/drag-and-drops.d.ts +30 -0
- package/x/loot/drag-and-drops.js +63 -0
- package/x/loot/drag-and-drops.js.map +1 -0
- package/x/loot/{drop.d.ts → drops.d.ts} +3 -5
- package/x/loot/drops.js +25 -0
- package/x/loot/drops.js.map +1 -0
- package/x/loot/helpers.d.ts +3 -3
- package/x/loot/helpers.js +3 -3
- package/x/loot/helpers.js.map +1 -1
- package/x/loot/index.d.ts +2 -2
- package/x/loot/index.js +2 -2
- package/x/loot/index.js.map +1 -1
- package/x/views/attributes.d.ts +1 -1
- package/x/views/attributes.js +5 -5
- package/x/views/attributes.js.map +1 -1
- package/x/views/base-element.d.ts +14 -0
- package/x/views/base-element.js +62 -0
- package/x/views/base-element.js.map +1 -0
- package/x/views/use.js.map +1 -1
- package/x/views/view.js +4 -4
- package/x/views/view.js.map +1 -1
- package/s/loot/drag-drop.ts +0 -76
- package/x/loot/drag-drop.d.ts +0 -29
- package/x/loot/drag-drop.js +0 -54
- package/x/loot/drag-drop.js.map +0 -1
- package/x/loot/drop.js +0 -32
- package/x/loot/drop.js.map +0 -1
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@ sly replaces its predecessor, [slate](https://github.com/benevolent-games/slate)
|
|
|
9
9
|
|
|
10
10
|
- 🍋 **views** — hooks-based, shadow-dom'd, componentizable
|
|
11
11
|
- 🪄 **dom** — the "it's not jquery" multitool
|
|
12
|
+
- 🪵 **base element** — for a more classical experience
|
|
12
13
|
- 🫛 **ops** — tools for async operations and loading spinners
|
|
14
|
+
- 🪙 **loot** — drag-and-drop facilities
|
|
13
15
|
- 🧪 **testing page** — https://sly.e280.org/
|
|
14
16
|
|
|
15
17
|
|
|
@@ -45,11 +47,10 @@ view(use => () => html`<p>hello world</p>`)
|
|
|
45
47
|
- views automatically rerender whenever any [strata-compatible](https://github.com/e280/strata) state changes
|
|
46
48
|
|
|
47
49
|
### 🍋 view example
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```
|
|
50
|
+
```ts
|
|
51
|
+
import {view, dom} from "@e280/sly"
|
|
52
|
+
import {html, css} from "lit"
|
|
53
|
+
```
|
|
53
54
|
- **declare a view**
|
|
54
55
|
```ts
|
|
55
56
|
export const CounterView = view(use => (start: number) => {
|
|
@@ -267,6 +268,85 @@ view(use => () => html`<p>hello world</p>`)
|
|
|
267
268
|
|
|
268
269
|
|
|
269
270
|
|
|
271
|
+
<br/><br/>
|
|
272
|
+
|
|
273
|
+
## 🦝🪵 sly base element
|
|
274
|
+
> *the classic experience*
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
import {BaseElement, Use, attributes, dom} from "@e280/sly"
|
|
278
|
+
import {html, css} from "lit"
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`BaseElement` is a class-based approach to create a custom element web component.
|
|
282
|
+
|
|
283
|
+
it lets you expose js properties on the element instance, which helps you setup a better developer experience for people interacting with your element through the dom.
|
|
284
|
+
|
|
285
|
+
base element enjoys the same `use` hooks as views.
|
|
286
|
+
|
|
287
|
+
### 🪵 base element setup
|
|
288
|
+
- **declare your element class**
|
|
289
|
+
```ts
|
|
290
|
+
export class MyElement extends BaseElement {
|
|
291
|
+
static styles = css`span{color:orange}`
|
|
292
|
+
|
|
293
|
+
// custom property
|
|
294
|
+
start = 10
|
|
295
|
+
|
|
296
|
+
// custom attributes
|
|
297
|
+
attrs = attributes(this, {
|
|
298
|
+
multiply: Number,
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
// custom methods
|
|
302
|
+
hello() {
|
|
303
|
+
return "world"
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
render(use: Use) {
|
|
307
|
+
const $count = use.signal(1)
|
|
308
|
+
const increment = () => $count.value++
|
|
309
|
+
|
|
310
|
+
const {start} = this
|
|
311
|
+
const {multiply = 1} = this.attrs
|
|
312
|
+
const result = start + (multiply * $count())
|
|
313
|
+
|
|
314
|
+
return html`
|
|
315
|
+
<span>${result}</span>
|
|
316
|
+
<button @click="${increment}">+</button>
|
|
317
|
+
`
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
- **register your element to the dom**
|
|
322
|
+
```ts
|
|
323
|
+
dom.register({MyElement})
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### 🪵 base element usage
|
|
327
|
+
- **place the element in your html body**
|
|
328
|
+
```html
|
|
329
|
+
<body>
|
|
330
|
+
<my-element></my-element>
|
|
331
|
+
</body>
|
|
332
|
+
```
|
|
333
|
+
- **now you can interact with it**
|
|
334
|
+
```ts
|
|
335
|
+
const myElement = dom<MyElement>("my-element")
|
|
336
|
+
|
|
337
|
+
// js property
|
|
338
|
+
myElement.start = 100
|
|
339
|
+
|
|
340
|
+
// html attributes
|
|
341
|
+
myElement.attr.multiply = 2
|
|
342
|
+
|
|
343
|
+
// methods
|
|
344
|
+
myElement.hello()
|
|
345
|
+
// "world"
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
270
350
|
<br/><br/>
|
|
271
351
|
|
|
272
352
|
## 🦝🪄 sly dom
|
|
@@ -277,51 +357,51 @@ import {dom} from "@e280/sly"
|
|
|
277
357
|
```
|
|
278
358
|
|
|
279
359
|
### 🪄 dom queries
|
|
280
|
-
- require an element
|
|
360
|
+
- `require` an element
|
|
281
361
|
```ts
|
|
282
362
|
dom(".demo")
|
|
283
363
|
// HTMLElement (or throws)
|
|
284
364
|
```
|
|
285
|
-
- maybe get an element
|
|
365
|
+
- `maybe` get an element
|
|
286
366
|
```ts
|
|
287
367
|
dom.maybe(".demo")
|
|
288
368
|
// HTMLElement | undefined
|
|
289
369
|
```
|
|
290
|
-
- select all elements
|
|
370
|
+
- `select` all elements
|
|
291
371
|
```ts
|
|
292
372
|
dom.all(".demo ul li")
|
|
293
373
|
// HTMLElement[]
|
|
294
374
|
```
|
|
295
|
-
-
|
|
375
|
+
- `in` the scope of an element
|
|
296
376
|
```ts
|
|
297
|
-
dom
|
|
377
|
+
dom(element).require("li")
|
|
298
378
|
// HTMLElement (or throws)
|
|
299
379
|
```
|
|
300
380
|
```ts
|
|
301
|
-
dom
|
|
381
|
+
dom(element).maybe("li")
|
|
302
382
|
// HTMLElement | undefined
|
|
303
383
|
```
|
|
304
384
|
```ts
|
|
305
|
-
dom
|
|
385
|
+
dom(element).all("li")
|
|
306
386
|
// HTMLElement[]
|
|
307
387
|
```
|
|
308
388
|
|
|
309
389
|
### 🪄 dom utilities
|
|
310
|
-
- register web components
|
|
390
|
+
- `register` web components
|
|
311
391
|
```ts
|
|
312
392
|
dom.register({MyComponent, AnotherCoolComponent})
|
|
313
393
|
// <my-component>
|
|
314
394
|
// <another-cool-component>
|
|
315
395
|
```
|
|
316
|
-
- render content into an element
|
|
396
|
+
- `render` content into an element
|
|
317
397
|
```ts
|
|
318
|
-
dom.render(
|
|
398
|
+
dom(element).render(html`<p>hello world</p>`)
|
|
319
399
|
```
|
|
320
400
|
```ts
|
|
321
|
-
dom.in(
|
|
401
|
+
dom.in(".demo").render(html`<p>hello world</p>`)
|
|
322
402
|
```
|
|
323
403
|
```ts
|
|
324
|
-
dom.
|
|
404
|
+
dom.render(element, html`<p>hello world</p>`)
|
|
325
405
|
```
|
|
326
406
|
|
|
327
407
|
|
|
@@ -455,6 +535,126 @@ import {Pod, podium, Op, makeLoader, anims} from "@e280/sly"
|
|
|
455
535
|
|
|
456
536
|
|
|
457
537
|
|
|
538
|
+
<br/><br/>
|
|
539
|
+
|
|
540
|
+
## 🦝🪙 loot
|
|
541
|
+
> *drag-and-drop facilities*
|
|
542
|
+
|
|
543
|
+
```ts
|
|
544
|
+
import {loot, ev, view} from "@e280/sly"
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### 🪙 `loot.Drops`
|
|
548
|
+
> *accept the user dropping stuff like files onto the page*
|
|
549
|
+
- **setup drops**
|
|
550
|
+
```ts
|
|
551
|
+
const drops = new loot.Drops({
|
|
552
|
+
predicate: loot.hasFiles,
|
|
553
|
+
acceptDrop: event => {
|
|
554
|
+
const files = loot.files(event)
|
|
555
|
+
console.log("files dropped", files)
|
|
556
|
+
},
|
|
557
|
+
})
|
|
558
|
+
```
|
|
559
|
+
- **attach event listeners to your dropzone,** one of these ways:
|
|
560
|
+
- **view example**
|
|
561
|
+
```ts
|
|
562
|
+
view(() => () => html`
|
|
563
|
+
<div
|
|
564
|
+
?data-indicator="${drops.$indicator()}"
|
|
565
|
+
@dragover="${drops.dragover}"
|
|
566
|
+
@dragleave="${drops.dragleave}"
|
|
567
|
+
@drop="${drops.drop}">
|
|
568
|
+
my dropzone
|
|
569
|
+
</div>
|
|
570
|
+
`)
|
|
571
|
+
```
|
|
572
|
+
- **vanilla-js whole-page example**
|
|
573
|
+
```ts
|
|
574
|
+
// attach listeners to accept drops and stuff
|
|
575
|
+
ev(document.body, {
|
|
576
|
+
dragover: drops.dragover,
|
|
577
|
+
dragleave: drops.dragleave,
|
|
578
|
+
drop: drops.drop,
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
// update indicator attribute on body
|
|
582
|
+
drops.$indicator.on(indicator => {
|
|
583
|
+
if (indicator) document.body.setAttribute("data-indicator", "")
|
|
584
|
+
else document.body.removeAttribute("data-indicator")
|
|
585
|
+
})
|
|
586
|
+
```
|
|
587
|
+
- **flashy css indicator for the dropzone,** so the user knows your app is eager to accept the drop
|
|
588
|
+
```css
|
|
589
|
+
[data-indicator] {
|
|
590
|
+
border: 0.5em dashed cyan;
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### 🪙 `loot.DragAndDrops`
|
|
595
|
+
> *setup drag-and-drops between items within your page*
|
|
596
|
+
- **declare types for your grabbable and hoverable things**
|
|
597
|
+
```ts
|
|
598
|
+
// money that can be picked up and dragged
|
|
599
|
+
type Money = {value: number}
|
|
600
|
+
// dnd will call this a "draggy"
|
|
601
|
+
|
|
602
|
+
// bag that money can be dropped into
|
|
603
|
+
type Bag = {id: number}
|
|
604
|
+
// dnd will call this a "droppy"
|
|
605
|
+
```
|
|
606
|
+
- **make your dnd**
|
|
607
|
+
```ts
|
|
608
|
+
const dnd = new loot.DragAndDrops<Money, Bag>({
|
|
609
|
+
acceptDrop: (event, money, bag) => {
|
|
610
|
+
console.log("drop!", {money, bag})
|
|
611
|
+
},
|
|
612
|
+
})
|
|
613
|
+
```
|
|
614
|
+
- **attach dragzone listeners**
|
|
615
|
+
(there can be many dragzones...)
|
|
616
|
+
```ts
|
|
617
|
+
view(use => () => {
|
|
618
|
+
const money = use.once((): Money => ({value: 280}))
|
|
619
|
+
const dragzone = use.once(() => dnd.dragzone(() => money))
|
|
620
|
+
|
|
621
|
+
return html`
|
|
622
|
+
<div
|
|
623
|
+
draggable="${dragzone.draggable}"
|
|
624
|
+
@dragstart="${dragzone.dragstart}"
|
|
625
|
+
@dragend="${dragzone.dragend}">
|
|
626
|
+
money ${money.value}
|
|
627
|
+
</div>
|
|
628
|
+
`
|
|
629
|
+
})
|
|
630
|
+
```
|
|
631
|
+
- **attach dropzone listeners**
|
|
632
|
+
(there can be many dropzones...)
|
|
633
|
+
```ts
|
|
634
|
+
view(use => () => {
|
|
635
|
+
const bag = use.once((): Bag => ({id: 1}))
|
|
636
|
+
const dropzone = use.once(() => dnd.dropzone(() => bag))
|
|
637
|
+
const indicator = !!(dnd.dragging && dnd.hovering === bag)
|
|
638
|
+
|
|
639
|
+
return html`
|
|
640
|
+
<div
|
|
641
|
+
?data-indicator="${indicator}"
|
|
642
|
+
@dragenter="${dropzone.dragenter}"
|
|
643
|
+
@dragleave="${dropzone.dragleave}"
|
|
644
|
+
@dragover="${dropzone.dragover}"
|
|
645
|
+
@drop="${dropzone.drop}">
|
|
646
|
+
bag ${bag.id}
|
|
647
|
+
</div>
|
|
648
|
+
`
|
|
649
|
+
})
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### 🪙 loot helpers
|
|
653
|
+
- **`loot.hasFiles(event)`** — return true if `DragEvent` contains any files (useful in `predicate`)
|
|
654
|
+
- **`loot.files(event)`** — returns an array of files in a drop's `DragEvent` (useful in `acceptDrop`)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
|
|
458
658
|
<br/><br/>
|
|
459
659
|
|
|
460
660
|
## 🦝🧑💻 sly is by e280
|
package/package.json
CHANGED
package/s/demo/demo.bundle.ts
CHANGED
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
import {dom} from "../dom/dom.js"
|
|
3
3
|
import {DemoView} from "./views/demo.js"
|
|
4
4
|
import {CounterView} from "./views/counter.js"
|
|
5
|
+
import {IncrediElement} from "./views/incredi.js"
|
|
5
6
|
|
|
6
7
|
dom.in(".demo").render(DemoView())
|
|
7
|
-
|
|
8
|
+
|
|
9
|
+
dom.register({
|
|
10
|
+
IncrediElement,
|
|
11
|
+
DemoCounter: CounterView.component(1),
|
|
12
|
+
})
|
|
8
13
|
|
|
9
14
|
console.log("🦝 sly")
|
|
10
15
|
|
package/s/demo/views/demo.ts
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
import {css, html} from "lit"
|
|
3
|
+
import {nap, repeat} from "@e280/stz"
|
|
4
|
+
|
|
5
|
+
import {Use} from "../../views/use.js"
|
|
6
|
+
import {attributes} from "../../views/attributes.js"
|
|
7
|
+
import {BaseElement} from "../../views/base-element.js"
|
|
8
|
+
|
|
9
|
+
export class IncrediElement extends BaseElement {
|
|
10
|
+
static styles = css`span{color:orange}`
|
|
11
|
+
attrs = attributes(this, {value: Number})
|
|
12
|
+
something = {whatever: "rofl"}
|
|
13
|
+
|
|
14
|
+
render(use: Use) {
|
|
15
|
+
const {value = 1} = this.attrs
|
|
16
|
+
const $count = use.signal(0)
|
|
17
|
+
|
|
18
|
+
use.mount(() => repeat(async() => {
|
|
19
|
+
await nap(10)
|
|
20
|
+
await $count($count() + 1)
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
return html`
|
|
24
|
+
<span>${$count() * value}</span>
|
|
25
|
+
`
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
package/s/dom/dom.ts
CHANGED
|
@@ -4,20 +4,29 @@ import {register} from "./register.js"
|
|
|
4
4
|
import {Content} from "../views/types.js"
|
|
5
5
|
import {Queryable, Renderable} from "./types.js"
|
|
6
6
|
|
|
7
|
+
function require<E extends Element>(
|
|
8
|
+
container: Queryable,
|
|
9
|
+
selector: string,
|
|
10
|
+
) {
|
|
11
|
+
const e = container.querySelector<E>(selector)
|
|
12
|
+
if (!e) throw new Error(`element not found (${selector})`)
|
|
13
|
+
return e
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
export class Dom<C extends Queryable> {
|
|
8
17
|
constructor(public element: C) {}
|
|
9
18
|
|
|
10
|
-
in<E extends HTMLElement>(
|
|
11
|
-
return new Dom(
|
|
12
|
-
typeof
|
|
13
|
-
? this.
|
|
14
|
-
:
|
|
19
|
+
in<E extends HTMLElement>(selectorOrElement: string | E) {
|
|
20
|
+
return new Dom<E>(
|
|
21
|
+
(typeof selectorOrElement === "string")
|
|
22
|
+
? require(this.element, selectorOrElement) as E
|
|
23
|
+
: selectorOrElement
|
|
15
24
|
)
|
|
16
25
|
}
|
|
17
26
|
|
|
18
27
|
require<E extends Element = HTMLElement>(selector: string) {
|
|
19
28
|
const e = this.element.querySelector<E>(selector)
|
|
20
|
-
if (!e) throw new Error(
|
|
29
|
+
if (!e) throw new Error(`element not found (${selector})`)
|
|
21
30
|
return e
|
|
22
31
|
}
|
|
23
32
|
|
|
@@ -34,18 +43,21 @@ export class Dom<C extends Queryable> {
|
|
|
34
43
|
}
|
|
35
44
|
}
|
|
36
45
|
|
|
37
|
-
export function dom(selector: string)
|
|
38
|
-
|
|
46
|
+
export function dom<E extends Queryable>(selector: string): E
|
|
47
|
+
export function dom<E extends Queryable>(element: E): Dom<E>
|
|
48
|
+
export function dom<E extends Queryable>(selectorOrElement: string | E): E | Dom<E> {
|
|
49
|
+
return (typeof selectorOrElement === "string")
|
|
50
|
+
? require(document, selectorOrElement) as E
|
|
51
|
+
: new Dom(selectorOrElement)
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
const doc = new Dom(document)
|
|
42
|
-
dom.register = register
|
|
43
|
-
dom.render = (container: Renderable, ...content: Content[]) => {
|
|
44
|
-
return render(content, container)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
55
|
dom.in = doc.in.bind(doc)
|
|
48
56
|
dom.require = doc.require.bind(doc)
|
|
49
57
|
dom.maybe = doc.maybe.bind(doc)
|
|
50
58
|
dom.all = doc.all.bind(doc)
|
|
59
|
+
dom.register = register
|
|
60
|
+
dom.render = (container: Renderable, ...content: Content[]) => {
|
|
61
|
+
return render(content, container)
|
|
62
|
+
}
|
|
51
63
|
|
package/s/index.html.ts
CHANGED
|
@@ -29,6 +29,7 @@ export default ssg.page(import.meta.url, async orb => ({
|
|
|
29
29
|
<h1>sly testing page</h1>
|
|
30
30
|
<p><a href="https://github.com/e280/sly">github.com/e280/sly</a></p>
|
|
31
31
|
<p class=lil>v${orb.packageVersion()}</p>
|
|
32
|
+
<incredi-element></incredi-element>
|
|
32
33
|
<demo-counter>component</demo-counter>
|
|
33
34
|
<div class=demo></div>
|
|
34
35
|
`,
|
package/s/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from "./ops/types.js"
|
|
|
14
14
|
export * as loot from "./loot/index.js"
|
|
15
15
|
|
|
16
16
|
export * from "./views/attributes.js"
|
|
17
|
+
export * from "./views/base-element.js"
|
|
17
18
|
export * from "./views/css-reset.js"
|
|
18
19
|
export * from "./views/types.js"
|
|
19
20
|
export * from "./views/use.js"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
|
|
2
|
+
import {signal} from "@e280/strata"
|
|
3
|
+
import {Drops} from "./drops.js"
|
|
4
|
+
import {outsideCurrentTarget} from "./helpers.js"
|
|
5
|
+
|
|
6
|
+
/** respond to user dragging-and-dropping things around on a webpage */
|
|
7
|
+
export class DragAndDrops<Draggy, Droppy> {
|
|
8
|
+
|
|
9
|
+
/** what is currently being dragged */
|
|
10
|
+
$draggy = signal<Draggy | undefined>(undefined)
|
|
11
|
+
|
|
12
|
+
/** what dropzone are we curently hovering over */
|
|
13
|
+
$droppy = signal<Droppy | undefined>(undefined)
|
|
14
|
+
|
|
15
|
+
constructor(private params: {
|
|
16
|
+
|
|
17
|
+
/** accept a dropped item that was declared within this system */
|
|
18
|
+
acceptDrop: (event: DragEvent, draggy: Draggy, droppy: Droppy) => void
|
|
19
|
+
|
|
20
|
+
/** also accept drops on the side */
|
|
21
|
+
backchannelDrops?: Drops
|
|
22
|
+
}) {}
|
|
23
|
+
|
|
24
|
+
get dragging() {
|
|
25
|
+
return this.$draggy()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get hovering() {
|
|
29
|
+
return this.$droppy()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** make event listeners to attach to your dragzone(s) */
|
|
33
|
+
dragzone = (getDraggy: () => Draggy) => ({
|
|
34
|
+
draggable: "true",
|
|
35
|
+
|
|
36
|
+
dragstart: (_: DragEvent) => {
|
|
37
|
+
this.$draggy.value = getDraggy()
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
dragend: (_: DragEvent) => {
|
|
41
|
+
this.$draggy.value = undefined
|
|
42
|
+
this.$droppy.value = undefined
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
/** make event listeners to attach to your dropzones(s) */
|
|
47
|
+
dropzone = (getDroppy: () => Droppy) => ({
|
|
48
|
+
dragenter: (_: DragEvent) => {},
|
|
49
|
+
|
|
50
|
+
dragover: (event: DragEvent) => {
|
|
51
|
+
event.preventDefault()
|
|
52
|
+
if (this.$draggy())
|
|
53
|
+
this.$droppy.value = getDroppy()
|
|
54
|
+
else
|
|
55
|
+
this.params.backchannelDrops?.dragover(event)
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
dragleave: (event: DragEvent) => {
|
|
59
|
+
if (outsideCurrentTarget(event))
|
|
60
|
+
this.$droppy.value = undefined
|
|
61
|
+
this.params.backchannelDrops?.dragleave(event)
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
drop: (event: DragEvent) => {
|
|
65
|
+
event.preventDefault()
|
|
66
|
+
const {acceptDrop} = this.params
|
|
67
|
+
const draggy = this.$draggy()
|
|
68
|
+
const droppy = this.$droppy()
|
|
69
|
+
try {
|
|
70
|
+
if (draggy && droppy)
|
|
71
|
+
acceptDrop(event, draggy, droppy)
|
|
72
|
+
else
|
|
73
|
+
this.params.backchannelDrops?.drop(event)
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
this.$draggy.value = undefined
|
|
77
|
+
this.$droppy.value = undefined
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
|
|
2
2
|
import {signal} from "@e280/strata"
|
|
3
|
-
import {
|
|
3
|
+
import {outsideCurrentTarget} from "./helpers.js"
|
|
4
4
|
|
|
5
|
-
/** dropzone that accepts dropped stuff like files */
|
|
6
|
-
export class
|
|
7
|
-
|
|
5
|
+
/** dropzone that accepts user-dropped stuff like files */
|
|
6
|
+
export class Drops {
|
|
7
|
+
$indicator = signal(false)
|
|
8
8
|
|
|
9
9
|
constructor(private params: {
|
|
10
10
|
|
|
@@ -15,28 +15,19 @@ export class Drop {
|
|
|
15
15
|
acceptDrop: (event: DragEvent) => void
|
|
16
16
|
}) {}
|
|
17
17
|
|
|
18
|
-
get indicator() {
|
|
19
|
-
return this.#$indicator.value
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
resetIndicator = () => {
|
|
23
|
-
this.#$indicator.value = false
|
|
24
|
-
}
|
|
25
|
-
|
|
26
18
|
dragover = (event: DragEvent) => {
|
|
27
19
|
event.preventDefault()
|
|
28
|
-
|
|
29
|
-
this.#$indicator.value = true
|
|
20
|
+
this.$indicator.value = this.params.predicate(event)
|
|
30
21
|
}
|
|
31
22
|
|
|
32
23
|
dragleave = (event: DragEvent) => {
|
|
33
|
-
if (
|
|
34
|
-
this
|
|
24
|
+
if (outsideCurrentTarget(event))
|
|
25
|
+
this.$indicator.value = false
|
|
35
26
|
}
|
|
36
27
|
|
|
37
28
|
drop = (event: DragEvent) => {
|
|
38
29
|
event.preventDefault()
|
|
39
|
-
this
|
|
30
|
+
this.$indicator.value = false
|
|
40
31
|
if (this.params.predicate(event))
|
|
41
32
|
this.params.acceptDrop(event)
|
|
42
33
|
}
|
package/s/loot/helpers.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
|
|
2
|
-
export function
|
|
2
|
+
export function hasFiles(event: DragEvent) {
|
|
3
3
|
return !!(
|
|
4
4
|
event.dataTransfer &&
|
|
5
5
|
event.dataTransfer.types.includes("Files")
|
|
6
6
|
)
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export function
|
|
9
|
+
export function files(event: DragEvent) {
|
|
10
10
|
return event.dataTransfer
|
|
11
11
|
? Array.from(event.dataTransfer.files)
|
|
12
12
|
: []
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function
|
|
15
|
+
export function outsideCurrentTarget(event: DragEvent) {
|
|
16
16
|
const isCursorOutsideViewport = !event.relatedTarget || (
|
|
17
17
|
event.clientX === 0 &&
|
|
18
18
|
event.clientY === 0
|
package/s/loot/index.ts
CHANGED
package/s/views/attributes.ts
CHANGED
|
@@ -26,6 +26,12 @@ export type AttrTypes<A extends AttrSpec> = {
|
|
|
26
26
|
[P in keyof A]: AttrType<A[P]>
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export function onAttrChange(element: HTMLElement, fn: () => void) {
|
|
30
|
+
const observer = new MutationObserver(fn)
|
|
31
|
+
observer.observe(element, {attributes: true})
|
|
32
|
+
return () => observer.disconnect()
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
export const attributes = <A extends AttrSpec>(
|
|
30
36
|
element: HTMLElement,
|
|
31
37
|
spec: A,
|
|
@@ -81,9 +87,3 @@ export const attributes = <A extends AttrSpec>(
|
|
|
81
87
|
|
|
82
88
|
}) as any as AttrTypes<A>
|
|
83
89
|
|
|
84
|
-
export function onAttrChange(element: HTMLElement, fn: () => void) {
|
|
85
|
-
const observer = new MutationObserver(fn)
|
|
86
|
-
observer.observe(element, {attributes: true})
|
|
87
|
-
return () => observer.disconnect()
|
|
88
|
-
}
|
|
89
|
-
|