@axel669/zephyr 0.5.0-alpha.0 → 0.5.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/package.json CHANGED
@@ -1,41 +1,42 @@
1
- {
2
- "name": "@axel669/zephyr",
3
- "version": "0.5.0-alpha.0",
4
- "description": "",
5
- "main": "src/index.js",
6
- "type": "module",
7
- "scripts": {
8
- "site/start": "wrangler pages dev site --port 45067",
9
- "site/build": "rollup -c docsite/rollup.config.js",
10
- "site/watch": "rollup -c docsite/rollup.config.js --watch"
11
- },
12
- "keywords": [],
13
- "author": "",
14
- "license": "MIT",
15
- "dependencies": {
16
- "@axel669/windstorm": "0.5.0",
17
- "svelte": "^5.9.0"
18
- },
19
- "devDependencies": {
20
- "@axel669/asuid": "^0.2.1",
21
- "@axel669/ipsen": "0.4.0-beta.7",
22
- "@axel669/rollup-copy-static": "^0.2.0",
23
- "@axel669/rollup-html-input": "^0.1.2",
24
- "@rollup/plugin-node-resolve": "^15.0.2",
25
- "@rollup/plugin-terser": "^0.4.0",
26
- "marked": "^12.0.2",
27
- "prism-svelte": "^0.5.0",
28
- "prismjs": "^1.29.0",
29
- "rollup": "^3.20.2",
30
- "rollup-plugin-delete": "^2.0.0",
31
- "rollup-plugin-svelte": "^7.2.2"
32
- },
33
- "files": [
34
- "src/"
35
- ],
36
- "imports": {
37
- "#lib": "./src/index.js",
38
- "#comp/*": "./docsite/comp/*.svelte",
39
- "#state/*": "./docsite/state/*.js"
40
- }
41
- }
1
+ {
2
+ "name": "@axel669/zephyr",
3
+ "version": "0.5.5",
4
+ "description": "",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "site/start": "wrangler pages dev site --port 45067",
9
+ "site/build": "rollup -c docsite/rollup.config.js",
10
+ "site/watch": "rollup -c docsite/rollup.config.js --watch"
11
+ },
12
+ "keywords": [],
13
+ "author": "",
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "@axel669/windstorm": "0.5.5",
17
+ "svelte": "^5.30.1"
18
+ },
19
+ "devDependencies": {
20
+ "@axel669/asuid": "^0.2.1",
21
+ "@axel669/ipsen": "0.4.0-beta.7",
22
+ "@axel669/rollup-copy-static": "^0.2.0",
23
+ "@axel669/rollup-html-input": "^0.1.2",
24
+ "@rollup/plugin-node-resolve": "^15.0.2",
25
+ "@rollup/plugin-terser": "^0.4.0",
26
+ "marked": "^12.0.2",
27
+ "prism-svelte": "^0.5.0",
28
+ "prismjs": "^1.29.0",
29
+ "rollup": "^3.20.2",
30
+ "rollup-plugin-delete": "^2.0.0",
31
+ "rollup-plugin-svelte": "^7.2.2"
32
+ },
33
+ "files": [
34
+ "src/"
35
+ ],
36
+ "imports": {
37
+ "#lib": "./src/index.js",
38
+ "#comp/*": "./docsite/comp/*.svelte",
39
+ "#state/*": "./docsite/state/*.js",
40
+ "#README": "./readme.md"
41
+ }
42
+ }
package/readme.md CHANGED
@@ -4,6 +4,12 @@ library, with some additional parts to make it look nice in the markup.
4
4
 
5
5
  [Docs Here](https://zephyr.axel669.net)
6
6
 
7
+ ## Todo
8
+ These are things that I plan to do, but didn't want to hold up the 0.5.x major
9
+ changes for svelte 5/runes/improvements.
10
+ - Add filter/sort back into data table
11
+ - Form element
12
+
7
13
  ## Installation
8
14
  Zephyr can be installed through npm (or the variants like yarn, pnpm, etc).
9
15
 
@@ -15,7 +21,8 @@ npm install @axel669/zephyr
15
21
  Components can be imported individually from the library for tree-shaking, or
16
22
  the entire lib can imported at once, but I dunno how big that bundle will be.
17
23
  Components can also take any windstorm macro as a prop, including custom macros,
18
- using the same rules as the `wsx` covered in [Functions](./src/functions.md).
24
+ using the same rules as the `wsx` covered in
25
+ [Functions](./docsite/docs/functions.md).
19
26
 
20
27
  ### Theming
21
28
  Windstorm expects a theme to be defined on an ancestor of the html elements
package/src/changed.js CHANGED
@@ -1,10 +1,10 @@
1
- export const trackChanges = (initial) => {
2
- let current = initial
3
- return (next) => {
4
- if (next !== current) {
5
- current = next
6
- return true
7
- }
8
- return false
9
- }
10
- }
1
+ export const trackChanges = (initial) => {
2
+ let current = initial
3
+ return (next) => {
4
+ if (next !== current) {
5
+ current = next
6
+ return true
7
+ }
8
+ return false
9
+ }
10
+ }
@@ -1,200 +1,174 @@
1
- <svelte:options immutable />
2
-
3
- <script context="module">
4
- // find the first filter in the list that returns false
5
- const applyFilters = (data, filters) => {
6
- if (Array.isArray(data) === false) {
7
- return null
1
+ <script module>
2
+ const itemRange = (info) => {
3
+ const {
4
+ scrollPos,
5
+ scrollMax,
6
+ rowSize,
7
+ length,
8
+ containerHeight,
9
+ } = info
10
+ if (scrollMax === null) {
11
+ return [0, 0]
8
12
  }
9
- return data.filter(
10
- (row) => {
11
- for (const [filter, value] of filters) {
12
- if (filter(row, value) === false) {
13
- return false
14
- }
15
- }
16
- return true
17
- }
13
+ const displayed = Math.ceil(containerHeight / rowSize) - 1
14
+ const itemPos = Math.floor(
15
+ (scrollPos / scrollMax) * (length - displayed)
18
16
  )
17
+ return [
18
+ itemPos - 1,
19
+ itemPos + displayed + 1,
20
+ displayed + 2,
21
+ ]
19
22
  }
20
- const sliceData = (data, page, pageSize, sortFunc) => {
21
- if (data === null) {
22
- return []
23
- }
24
- const sorted = data.sort(sortFunc)
25
- return Array.from(
26
- { length: pageSize },
27
- (_, i) => sorted[page * pageSize + i]
23
+ const emptyRow = Symbol("empty row")
24
+ const nullRow = Symbol("null row")
25
+
26
+ const sliceData = (data, range) => {
27
+ const displayed = data.slice(
28
+ Math.max(range[0], 0),
29
+ range[1]
28
30
  )
31
+ if (range[0] === -1) {
32
+ return [emptyRow, ...displayed]
33
+ }
34
+ if (displayed.length < range[2]) {
35
+ return [
36
+ ...displayed,
37
+ ...Array.from(
38
+ { length: range[2] - displayed.length },
39
+ () => nullRow
40
+ )
41
+ ]
42
+ }
43
+ return displayed
29
44
  }
30
- const noSort = {
31
- direction: null,
32
- func: (a, b) => 0,
33
- base: null,
34
- }
35
-
36
- export const dtContext = Symbol("table key")
37
45
  </script>
38
46
 
39
47
  <script>
40
- import { createEventDispatcher, setContext } from "svelte"
41
- import { writable } from "svelte/store"
42
-
43
- import Button from "../control/button.svelte"
44
- import Icon from "../info/icon.svelte"
45
- import Paper from "../layout/paper.svelte"
46
- import Table from "../data-display/table.svelte"
47
- import Text from "../text.svelte"
48
+ import { on } from "svelte/events"
48
49
 
49
50
  import Grid from "../layout/grid.svelte"
50
51
 
51
52
  import wsx from "../wsx.js"
52
- import { handler$, eventHandler$ } from "../handler$.js"
53
-
54
- export let color = "@primary"
55
- export let fillHeader = true
56
- export let data
57
- export let pageSize = 10
58
- export let page = 0
59
- export let rowHeight = "40px"
60
- export let scrollable = false
61
- export let height = null
62
-
63
- let sorting = noSort
64
- let filters = new Map()
65
- let filterFunctions = []
66
- let jumpTarget = "1"
67
-
68
- $: filteredData = applyFilters(data, filterFunctions, sorting.func)
69
- $: rows = sliceData(filteredData, page, pageSize, sorting.func)
70
- $: rowCount = filteredData?.length ?? 0
71
- $: pageCount = Math.ceil(rowCount / pageSize)
72
- $: maxPage = Math.max(pageCount - 1, 0)
73
- $: jumpTarget = (page + 1).toString()
74
-
75
- $: if (filteredData === null) {
76
- console.warn("DataTable: data is not an array")
77
- }
78
-
79
- const prev = () => page = Math.max(0, page - 1)
80
- const next = () => page = Math.min(maxPage, page + 1)
81
-
82
- const updateSort = (func) => {
83
- if (sorting.base === func) {
84
- if (sorting.direction === "desc") {
85
- sorting = noSort
86
- return
87
- }
88
- sorting = {
89
- func: (a, b) => -func(a, b),
90
- base: func,
91
- direction: "desc"
92
- }
93
- return
94
- }
95
- sorting = {
96
- func,
97
- base: func,
98
- direction: "asc",
99
- }
100
- }
101
- const updateFilter = (func, value) => {
102
- if (func === null) {
103
- return
104
- }
105
- filters.set(func, value)
106
- filterFunctions = [
107
- ...filters.entries()
108
- ]
109
- }
110
53
 
111
- const context = writable({})
112
- $: $context = {
113
- updateSort,
114
- fillHeader,
115
- sorting,
116
- updateFilter,
54
+ const {
55
+ data,
56
+ rowSize = 40,
57
+ headerSize = rowSize,
58
+ cols = "1fr",
59
+ gap = 2,
60
+ colGap = gap,
61
+ rowGap = gap,
62
+ header,
63
+ row,
117
64
  color,
118
- }
119
- setContext(dtContext, context)
65
+ fillHeader = false,
66
+ ...rest
67
+ } = $props()
120
68
 
121
- const fire = createEventDispatcher()
122
- const pass = handler$(
123
- (row) => fire("row-click", row)
69
+ const rowPx = rowSize + rowGap
70
+ const height = $derived(
71
+ rowPx * data.length - rowGap
124
72
  )
73
+ const tableHeight = $derived(height + rowPx)
125
74
 
126
- let scroller = null
127
- $: if (scroller !== null && isNaN(page) === false) {
128
- scroller.scrollTop = 0
75
+ let scrollPos = $state(0)
76
+ let scrollMax = $state(null)
77
+ let containerHeight = $state(null)
78
+ const scrollHandler = (evt) => {
79
+ scrollPos = container.scrollTop
129
80
  }
130
- const jump = (evt) => {
131
- if (evt.type === "keypress" && evt.key !== "Enter") {
132
- return
133
- }
134
- const target = parseInt(evt.target.value)
135
- if (isNaN(target) === true) {
136
- jumpTarget = (page + 1).toString()
137
- return
138
- }
139
- page = Math.max(
140
- Math.min(jumpTarget - 1, maxPage),
141
- 0
81
+ const range = $derived(
82
+ itemRange({
83
+ scrollPos,
84
+ scrollMax,
85
+ containerHeight,
86
+ rowSize: rowSize + rowGap,
87
+ length: data.length,
88
+ })
89
+ )
90
+ let container = $state(null)
91
+
92
+ $effect(() => {
93
+ containerHeight = container.clientHeight
94
+ scrollMax = container.scrollHeight - containerHeight
95
+ on(container, "scroll", scrollHandler, { passive: true })
96
+ const observer = new ResizeObserver(
97
+ () => {
98
+ containerHeight = container.clientHeight
99
+ scrollMax = container.scrollHeight - containerHeight
100
+ }
142
101
  )
143
- jumpTarget = (page + 1).toString()
144
- }
102
+ observer.observe(container)
103
+ return () => observer.unobserve(container)
104
+ })
145
105
 
146
- $: content = {
147
- "fl.cross": "stretch",
148
- p: "0px",
149
- gap: "0px",
150
- over: (scrollable === true) ? "auto" : null,
151
- }
152
- $: header = {
153
- "h.min": rowHeight,
154
- y: "0px",
155
- z: "+10",
156
- pos: (scrollable === true) ? "sticky" : null,
157
- }
106
+ const displayed = $derived(range[2])
107
+ const displayedRows = $derived(
108
+ sliceData(data, range)
109
+ )
110
+ const tbodyHeight = $derived(displayRows.length * rowSize)
111
+
112
+ const tableWSX = $derived({
113
+ "$fill-header": fillHeader,
114
+ $color: color,
115
+ "gr.cols": cols,
116
+ "gap.col": `${colGap}px`,
117
+ "gap.row": `${rowGap}px`,
118
+ "@header-size": `${headerSize}px`,
119
+ "@row-size": `${rowSize}px`,
120
+ "$sticky-header": true,
121
+ w: "100%",
122
+ h: `${tableHeight}px`,
123
+ over: "visible",
124
+ con: "paint",
125
+ })
126
+ const tbodyWSX = $derived({
127
+ disp: "grid",
128
+ "gr.rows": "subgrid",
129
+ "gr.cols": "subgrid",
130
+ row: `span ${displayed}`,
131
+ tf: `translateY(${range[0] * rowPx}px)`,
132
+ })
133
+ const footerWSX = $derived({
134
+ h: "40px",
135
+ bg: "@background-element",
136
+ $color: color,
137
+ "b.y": "2px solid @ripple-base-color",
138
+ disp: "flex",
139
+ "fl-center": true,
140
+ })
141
+
142
+ const show = $derived({
143
+ start: Math.max(1, range[0] + 2),
144
+ end: Math.min(data.length, range[1] - 1),
145
+ total: data.length,
146
+ })
158
147
  </script>
159
148
 
160
- <Paper h={height} r="0px">
161
- <ws-flex use:wsx={content} slot="content" bind:this={scroller}>
162
- <Table data={rows} {color} {fillHeader} b.t.w="2px" {...$$restProps}>
163
- <tr use:wsx={header} slot="header">
164
- <slot name="header">
165
- <th>No Header Defined</th>
166
- </slot>
167
- </tr>
168
- <tr ws-x="[h {rowHeight}]" slot="row" let:row>
169
- <slot name="row" {row}>
170
- <th>No Row Defined</th>
171
- </slot>
172
- </tr>
173
- <tr ws-x="[h {rowHeight}]" slot="empty-row" />
174
- </Table>
175
- </ws-flex>
176
- <Grid slot="footer" gr.cols="min-content min-content min-content 1fr"
177
- rows="32px" b="1px solid {color}" b.b.w="2px">
178
- {#if pageCount > 0}
179
- <Button on:click={prev} disabled={page === 0} ground>
180
- <Icon name="caret-left" />
181
- </Button>
182
- <Button on:click={next} disabled={page === maxPage} ground>
183
- <Icon name="caret-right" />
184
- </Button>
185
- <Text adorn t.ws="nowrap">
186
- Page
187
- <input ws-x="[b 1px solid @text-color-normal] [w 36px] [r 4px]
188
- [h 24px] [bg.c transaprent] [t.a center] [m.l 4px] [m.r 4px]"
189
- type="text" bind:value={jumpTarget}
190
- on:keypress={jump} on:blur={jump} />
191
- / {pageCount}
192
- </Text>
193
- {:else}
194
- <div ws-x="[col span 3] [p.l 4px]">
195
- No data to show
196
- </div>
197
- {/if}
198
- <slot name="action" />
199
- </Grid>
200
- </Paper>
149
+ <Grid cols="1fr" rows="1fr min-content" pos="relative" {...rest} gap="0px">
150
+ <div ws-x="[over auto]" bind:this={container}>
151
+ <table use:wsx={tableWSX}>
152
+ <thead>
153
+ {@render header()}
154
+ </thead>
155
+ <tbody use:wsx={tbodyWSX}>
156
+ {#each displayedRows as dataRow}
157
+ {#if dataRow === nullRow}
158
+ <tr>
159
+ <td ws-x="[col 1 / -1]"></td>
160
+ </tr>
161
+ {:else if dataRow === emptyRow}
162
+ <tr></tr>
163
+ {:else}
164
+ {@render row(dataRow)}
165
+ {/if}
166
+ {/each}
167
+ </tbody>
168
+ </table>
169
+ </div>
170
+
171
+ <div use:wsx={footerWSX}>
172
+ Showing: {show.start} - {show.end} of {show.total}
173
+ </div>
174
+ </Grid>
@@ -2,27 +2,28 @@
2
2
  import Button from "../control/button.svelte"
3
3
  import Modal from "../layout/modal.svelte"
4
4
  import { handler$ } from "../handler$.js"
5
+ import { splitProps } from "../props.js"
5
6
 
6
7
  const {
7
- component,
8
- props,
9
- this:Wrapper = Modal,
10
- "w!props":wrapperProps = {},
11
8
  children,
9
+ modal,
12
10
  onentry,
11
+ onopen,
13
12
  ...rest
14
13
  } = $props()
15
14
 
15
+ const prop = splitProps(rest, "m!")
16
+
16
17
  let element = null
17
- const open = handler$(
18
- async (props) => {
19
- const elemProps = (typeof props === "function") ? props() : props
20
- const result = await element.show(elemProps)
21
- onentry?.(result)
22
- }
23
- )
18
+ const open = async (props) => {
19
+ onopen?.()
20
+ const result = await element.show()
21
+ onentry?.(result)
22
+ }
24
23
  </script>
25
24
 
26
- <Button {...rest} onclick={open(props)} {children} />
25
+ <Button {...prop.rest} onclick={open} {children} />
27
26
 
28
- <Wrapper bind:this={element} {...rest} {component} />
27
+ <Modal bind:this={element} {...prop["m!"]}>
28
+ {@render modal()}
29
+ </Modal>
@@ -0,0 +1,9 @@
1
+ const head = document.head
2
+ const macros = document.createElement("style")
3
+ macros.setAttribute("ws-root", "")
4
+ macros.innerHTML = `
5
+ .ws-style {
6
+ --con: "contain: {$}";
7
+ }
8
+ `
9
+ head.append(macros)
@@ -5,6 +5,8 @@
5
5
  color = "@default",
6
6
  data = [],
7
7
  fillHeader = false,
8
+ stickyHeader = false,
9
+ cols = "1fr",
8
10
  header,
9
11
  row,
10
12
  emptyRow,
@@ -13,7 +15,9 @@
13
15
 
14
16
  const wind = $derived({
15
17
  "$color": color,
16
- "$header-fill": fillHeader,
18
+ "$fill-header": fillHeader,
19
+ "$sticky-header": stickyHeader,
20
+ "gr.cols": cols,
17
21
  ...rest,
18
22
  })
19
23
  </script>
@@ -4,16 +4,16 @@
4
4
  import Icon from "../info/icon.svelte"
5
5
  import Text from "../text.svelte"
6
6
  import Titlebar from "../info/titlebar.svelte"
7
+ import { modalContext } from "../layout/modal.svelte"
7
8
 
8
9
  const {
9
- close,
10
- color = "@primary",
10
+ color = "@default",
11
11
  icon,
12
12
  message,
13
13
  okText = "OK",
14
- titleText = "Alert",
15
- ...rest
14
+ title: titleText = "Alert",
16
15
  } = $props()
16
+ const { close } = modalContext()
17
17
  </script>
18
18
 
19
19
  <Dialog card {color}>
@@ -34,7 +34,7 @@
34
34
  </Text>
35
35
 
36
36
  {#snippet footer()}
37
- <Button onclick={close} {color} ground>
37
+ <Button onclick={() => close(true)} {color} ground>
38
38
  {okText}
39
39
  </Button>
40
40
  {/snippet}
@@ -5,19 +5,20 @@
5
5
  import Icon from "../info/icon.svelte"
6
6
  import Text from "../text.svelte"
7
7
  import Titlebar from "../info/titlebar.svelte"
8
+ import { modalContext } from "../layout/modal.svelte"
8
9
 
9
10
  import { handler$ } from "../handler$.js"
10
11
 
11
12
  const {
12
- close,
13
- titleText = "Confirm",
13
+ title: titleText = "Confirm",
14
14
  icon,
15
15
  message,
16
16
  okText = "OK",
17
17
  cancelText = "Cancel",
18
- color = "@primary",
18
+ color = "@default",
19
19
  } = $props()
20
20
 
21
+ const { close } = modalContext()
21
22
  const cls = handler$(close)
22
23
  </script>
23
24
 
@@ -0,0 +1,67 @@
1
+ <script>
2
+ import Button from "../control/button.svelte"
3
+ import Dialog from "../layout/dialog.svelte"
4
+ import Grid from "../layout/grid.svelte"
5
+ import Icon from "../info/icon.svelte"
6
+ import Input from "../control/input.svelte"
7
+ import Text from "../text.svelte"
8
+ import Titlebar from "../info/titlebar.svelte"
9
+ import { modalContext } from "../layout/modal.svelte"
10
+
11
+ import { handler$ } from "../handler$.js"
12
+ import { frameDelay } from "../internals.js"
13
+
14
+ let {
15
+ title: titleText = "Confirm",
16
+ icon,
17
+ message,
18
+ okText = "OK",
19
+ cancelText = "Cancel",
20
+ color = "@default",
21
+ value = $bindable(""),
22
+ initial = ""
23
+ } = $props()
24
+
25
+ const { close } = modalContext()
26
+ const cls = handler$(close)
27
+ let input = $state(null)
28
+
29
+ const done = (evt) => {
30
+ evt.preventDefault()
31
+ evt.stopPropagation()
32
+ close(true)
33
+ }
34
+
35
+ // the first 1-2 frames it's in the DOM it's not visible, focus didn't work
36
+ frameDelay(3, () => input.focus())
37
+ value = initial
38
+ </script>
39
+
40
+ <Dialog card {color}>
41
+ {#snippet header()}
42
+ <Titlebar {color}>
43
+ {#snippet title()}
44
+ <Text title>
45
+ <Icon name={icon}>
46
+ {titleText}
47
+ </Icon>
48
+ </Text>
49
+ {/snippet}
50
+ </Titlebar>
51
+ {/snippet}
52
+
53
+ <form onsubmit={done} ws-x="[grid]">
54
+ <Input bind:value {color} bind:this={input} label={message} />
55
+ </form>
56
+
57
+ {#snippet footer()}
58
+ <Grid cols="1fr 1fr" p="0px">
59
+ <Button onclick={cls(false)} color="@danger" ground>
60
+ {cancelText}
61
+ </Button>
62
+ <Button onclick={cls(true)} color="@secondary" ground>
63
+ {okText}
64
+ </Button>
65
+ </Grid>
66
+ {/snippet}
67
+ </Dialog>
@@ -1,14 +1,5 @@
1
1
  import { readable } from "svelte/store"
2
2
 
3
- // export const hash = $state(location.hash.slice(1))
4
- // window.addEventListener(
5
- // "popstate",
6
- // () => hashValue = location.hash.slice(1)
7
- // )
8
- // $effect(
9
- // () => location.hash = hash
10
- // )
11
-
12
3
  export const hash = readable(
13
4
  location.hash.slice(1),
14
5
  (set) => {
package/src/index.js CHANGED
@@ -27,7 +27,7 @@ export { default as Dialog } from "./layout/dialog.svelte"
27
27
  export { default as Drawer } from "./layout/drawer.svelte"
28
28
  export { default as Flex } from "./layout/flex.svelte"
29
29
  export { default as Grid } from "./layout/grid.svelte"
30
- export { default as Modal } from "./layout/modal.svelte"
30
+ export { default as Modal, modalAnimTime, modalContext } from "./layout/modal.svelte"
31
31
  export { default as Paper } from "./layout/paper.svelte"
32
32
  export { default as Screen } from "./layout/screen.svelte"
33
33
 
@@ -38,14 +38,14 @@ export { default as HexagonSpinner } from "./spinner/hexagon-spinner.svelte"
38
38
 
39
39
  export { default as Alert } from "./dialogs/alert.svelte"
40
40
  export { default as Confirm } from "./dialogs/confirm.svelte"
41
+ export { default as Prompt } from "./dialogs/prompt.svelte"
41
42
 
42
- // export { default as DataTable } from "./composed/data-table.svelte"
43
+ export { default as DataTable } from "./composed/data-table.svelte"
43
44
  // export { default as TH } from "./composed/data-table/th.svelte"
44
45
  export { default as EntryButton } from "./composed/entry-button.svelte"
45
46
  // export { default as Form } from "./composed/form.svelte"
46
47
 
47
48
  export { default as AsyncButton } from "./async/button.svelte"
48
- // export { default as LoadZone } from "./async/load-zone.svelte"
49
49
  export { default as Suspend } from "./async/suspend.svelte"
50
50
 
51
51
  export { default as Route } from "./routing/route.svelte"
@@ -53,7 +53,7 @@ export { default as Title } from "./routing/title.svelte"
53
53
 
54
54
  export { default as wsx } from "./wsx.js"
55
55
 
56
- export { hash } from "./hash.svelte.js"
56
+ export { hash } from "./hash.js"
57
57
 
58
58
  export {
59
59
  route,
@@ -0,0 +1,12 @@
1
+ export const frameDelay = (frames, action) => {
2
+ let frameCount = 0
3
+ const process = () => {
4
+ frameCount += 1
5
+ if (frameCount >= frames) {
6
+ action()
7
+ return
8
+ }
9
+ requestAnimationFrame(process)
10
+ }
11
+ requestAnimationFrame(process)
12
+ }
@@ -19,6 +19,7 @@
19
19
 
20
20
  <!-- svelte-ignore a11y_click_events_have_key_events -->
21
21
  <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
22
+ <!-- svelte-ignore a11y_interactive_supports_focus -->
22
23
  <wind-wrapper transition:trick ws-x="@@dialog [grid]" {onclick} role="dialog">
23
24
  <Paper {...props} />
24
25
  </wind-wrapper>
@@ -31,26 +31,18 @@
31
31
  </script>
32
32
 
33
33
  <script>
34
- import { getContext } from "svelte"
35
-
36
34
  import wsx from "../wsx.js"
37
35
 
38
36
  import Paper from "./paper.svelte"
39
- import { modalContext } from "./modal.svelte"
37
+ import { modalAnimTime } from "./modal.svelte"
40
38
 
41
39
  const {
42
40
  height,
43
41
  type = "menu",
44
- children,
45
- header,
46
- footer,
47
- content,
48
42
  ...rest
49
43
  } = $props()
50
- // export let height
51
- // export let type = "menu"
52
44
 
53
- const animTime = getContext(modalContext)
45
+ const animTime = modalAnimTime()
54
46
 
55
47
  // When Svelte removes things from the DOM they are removed immediately
56
48
  // unless Svelte has a transition running. Adding this prevents the drawer
@@ -74,5 +66,5 @@
74
66
  <!-- svelte-ignore a11y_click_events_have_key_events -->
75
67
  <wind-drawer-container use:wsx={container} {onclick} role="menubar"
76
68
  transition:trick>
77
- <Paper r="0px" {...rest} {header} {footer} {content} {children} />
69
+ <Paper r="0px" {...rest} />
78
70
  </wind-drawer-container>
@@ -1,29 +1,34 @@
1
1
  <script module>
2
+ import { getContext } from "svelte"
3
+
2
4
  let topClose = null
3
- export const modalContext = Symbol("modal context")
5
+ const modalAnimTimeSymbol = Symbol("modal animation time")
6
+ const modalContextSymbol = Symbol("modal context")
7
+
8
+ export const modalAnimTime = () => getContext(modalAnimTimeSymbol)
9
+ export const modalContext = () => getContext(modalContextSymbol)
4
10
  </script>
5
11
 
6
12
  <script>
7
- import { tick, setContext } from "svelte"
13
+ import { setContext } from "svelte"
8
14
  import { writable } from "svelte/store"
9
15
 
10
16
  import wsx from "../wsx.js"
17
+ import { frameDelay } from "../internals.js"
11
18
 
12
19
  const {
13
- component,
20
+ children,
21
+ cancelable = false,
14
22
  animTime = "200ms"
15
23
  } = $props()
16
24
 
17
- let modalProps = $state(null)
18
25
  let resolver = $state(null)
19
- let displayed = $state(null)
26
+ let shown = $state(false)
20
27
 
21
28
  const close = (value) => {
22
29
  resolver(value)
23
30
  resolver = null
24
- modalProps = null
25
- Component = null
26
- // visible.checked = false
31
+ shown = false
27
32
  if (topClose !== close) {
28
33
  return
29
34
  }
@@ -32,39 +37,35 @@
32
37
  const closeToTop = (value) => {
33
38
  topClose(value)
34
39
  }
35
- const cancel = () => displayed.cancel?.()
40
+ const cancel = () => {
41
+ if (cancelable === false) {
42
+ return
43
+ }
44
+ close(null)
45
+ }
36
46
 
37
47
  export const show = (props) => new Promise(
38
- async (resolve) => {
39
- modalProps = props ?? {}
48
+ (resolve) => {
40
49
  topClose = topClose ?? close
41
- Component = component
42
- await tick()
43
- setTimeout(
44
- () => resolver = resolve,
45
- 0
46
- )
50
+ resolver = resolve
51
+ frameDelay(1, () => shown = true)
47
52
  }
48
53
  )
49
54
 
50
- // let visible = null
51
55
  const animationTime = writable(animTime)
52
56
  const wind = $derived({
53
57
  "@anim-time": $animationTime,
54
- $show: resolver !== null,
58
+ $show: shown === true,
55
59
  })
56
- let Component = $state(null)
57
- setContext(modalContext, animationTime)
60
+ setContext(modalAnimTimeSymbol, animationTime)
61
+ setContext(modalContextSymbol, { close, closeToTop })
58
62
  </script>
59
63
 
60
64
  <!-- svelte-ignore a11y_click_events_have_key_events -->
61
65
  <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
66
+ <!-- svelte-ignore a11y_interactive_supports_focus -->
62
67
  <ws-modal onclick={cancel} role="dialog" use:wsx={wind}>
63
- <Component
64
- bind:this={displayed}
65
- this={component}
66
- {...modalProps}
67
- {close}
68
- {closeToTop}
69
- />
68
+ {#if resolver !== null}
69
+ {@render children()}
70
+ {/if}
70
71
  </ws-modal>
@@ -16,17 +16,6 @@
16
16
  ...rest
17
17
  } = $props()
18
18
 
19
- // $: props = Object.entries($$restProps).reduce(
20
- // (p, [key, value]) => {
21
- // const [target, name] =
22
- // (key.startsWith("l!") === true)
23
- // ? [ p.layout, key.slice(2) ]
24
- // : [ p.paper, key ]
25
- // target[name] = value
26
- // return p
27
- // },
28
- // { layout: {}, paper: {} }
29
- // )
30
19
  const props = $derived(
31
20
  splitProps(rest, "l!")
32
21
  )
package/src/props.js CHANGED
@@ -1,24 +1,24 @@
1
- export const filterProps = (props, filter) => Object.entries(props).reduce(
2
- (p, [name, value]) => {
3
- if (name.startsWith(filter) === true) {
4
- p[name.slice(filter.length)] = value
5
- }
6
- return p
7
- },
8
- {}
9
- )
10
-
11
- export const splitProps = (props, ...splits) => Object.entries(props).reduce(
12
- (groups, [name, value]) => {
13
- for (const split of splits) {
14
- if (name.startsWith(split) === true) {
15
- groups[split] = groups[split] ?? {}
16
- groups[split][name.slice(split.length)] = value
17
- return groups
18
- }
19
- }
20
- groups.rest[name] = value
21
- return groups
22
- },
23
- { rest: {} }
24
- )
1
+ export const filterProps = (props, filter) => Object.entries(props).reduce(
2
+ (p, [name, value]) => {
3
+ if (name.startsWith(filter) === true) {
4
+ p[name.slice(filter.length)] = value
5
+ }
6
+ return p
7
+ },
8
+ {}
9
+ )
10
+
11
+ export const splitProps = (props, ...splits) => Object.entries(props).reduce(
12
+ (groups, [name, value]) => {
13
+ for (const split of splits) {
14
+ if (name.startsWith(split) === true) {
15
+ groups[split] = groups[split] ?? {}
16
+ groups[split][name.slice(split.length)] = value
17
+ return groups
18
+ }
19
+ }
20
+ groups.rest[name] = value
21
+ return groups
22
+ },
23
+ { rest: {} }
24
+ )
@@ -58,8 +58,5 @@
58
58
  {:else}
59
59
  {@render (content ?? children)?.(routeInfo, props)}
60
60
  {/if}
61
- <!-- <slot {routeInfo}>
62
- <svelte:component this={component} {...props} {routeInfo} />
63
- </slot> -->
64
61
  {/key}
65
62
  {/if}
@@ -1,7 +1,7 @@
1
1
  import { onMount, getContext } from "svelte"
2
2
  import { writable, derived } from "svelte/store"
3
3
 
4
- import { hash } from "../hash.svelte.js"
4
+ import { hash } from "../hash.js"
5
5
 
6
6
  export const route = derived(
7
7
  hash,
@@ -15,12 +15,6 @@ export const route = derived(
15
15
  route.set = (path) => {
16
16
  hash.set(path)
17
17
  }
18
- // export const route = $derived(
19
- // hash.startsWith("/") === false ? `/${hash}` : hash
20
- // )
21
- // $effect(
22
- // () => hash = route
23
- // )
24
18
 
25
19
  export const ctx = {
26
20
  router: Symbol("Router Context"),
package/src/wind.js ADDED
@@ -0,0 +1,4 @@
1
+ import "./custom-macros.js"
2
+ import ws from "@axel669/windstorm"
3
+
4
+ export default ws
package/src/wsx.js CHANGED
@@ -1,4 +1,4 @@
1
- import ws from "@axel669/windstorm"
1
+ import ws from "./wind.js"
2
2
 
3
3
  export const none = Symbol("none")
4
4
  export default (node, props) => {
@@ -1,22 +0,0 @@
1
- <svelte:options immutable />
2
-
3
- <script>
4
- import Spinner from "../spinner/hexagon-spinner.svelte"
5
- export let source = null
6
- export let message = "Loading"
7
- </script>
8
-
9
- {#if source !== null}
10
- {#await source}
11
- <slot name="loading">
12
- <ws-flex ws-x="[fl-center]">
13
- <span>{message}</span>
14
- <Spinner size="100px" />
15
- </ws-flex>
16
- </slot>
17
- {:then result}
18
- <slot {result} />
19
- {:catch error}
20
- <slot name="error" {error} />
21
- {/await}
22
- {/if}