firstly 0.6.2 → 0.7.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/CHANGELOG.md +20 -0
- package/esm/changeLog/index.d.ts +1 -0
- package/esm/svelte/FF_Config.svelte +2 -2
- package/esm/svelte/FF_Config.svelte.js +3 -0
- package/esm/svelte/grid/DefaultInput.svelte +42 -0
- package/esm/svelte/grid/DefaultInput.svelte.d.ts +9 -0
- package/esm/svelte/grid/FF_Cell.svelte +98 -0
- package/esm/svelte/grid/FF_Cell.svelte.d.ts +10 -0
- package/esm/svelte/grid/FF_CellValue.svelte +24 -0
- package/esm/svelte/grid/FF_CellValue.svelte.d.ts +28 -0
- package/esm/svelte/grid/FF_Cell_Content.svelte +37 -0
- package/esm/svelte/grid/FF_Cell_Content.svelte.d.ts +10 -0
- package/esm/svelte/grid/FF_Cell_Error.svelte +26 -0
- package/esm/svelte/grid/FF_Cell_Error.svelte.d.ts +8 -0
- package/esm/svelte/grid/FF_Cell_Hint.svelte +26 -0
- package/esm/svelte/grid/FF_Cell_Hint.svelte.d.ts +8 -0
- package/esm/svelte/grid/FF_Cell_Label.svelte +34 -0
- package/esm/svelte/grid/FF_Cell_Label.svelte.d.ts +8 -0
- package/esm/svelte/grid/FF_Grid.svelte +385 -0
- package/esm/svelte/grid/FF_Grid.svelte.d.ts +47 -0
- package/esm/svelte/grid/GroupFields.svelte +141 -0
- package/esm/svelte/grid/GroupFields.svelte.d.ts +45 -0
- package/esm/svelte/grid/_test/FF_Cell_ContextWrapper.svelte +9 -0
- package/esm/svelte/grid/_test/FF_Cell_ContextWrapper.svelte.d.ts +18 -0
- package/esm/svelte/grid/buildCells.d.ts +16 -0
- package/esm/svelte/grid/buildCells.js +79 -0
- package/esm/svelte/grid/cellComponent.d.ts +9 -0
- package/esm/svelte/grid/cellComponent.js +31 -0
- package/esm/svelte/grid/cellConfig.d.ts +13 -0
- package/esm/svelte/grid/cellConfig.js +48 -0
- package/esm/svelte/grid/cellTypes.d.ts +168 -0
- package/esm/svelte/grid/cellTypes.js +1 -0
- package/esm/svelte/grid/index.d.ts +10 -0
- package/esm/svelte/grid/index.js +12 -0
- package/esm/svelte/grid/metaKind.d.ts +27 -0
- package/esm/svelte/grid/metaKind.js +34 -0
- package/esm/svelte/index.d.ts +3 -2
- package/esm/svelte/index.js +3 -2
- package/package.json +4 -3
- package/esm/svelte/DemoForm.svelte +0 -121
- package/esm/svelte/DemoForm.svelte.d.ts +0 -42
- package/esm/svelte/DemoGrid.svelte +0 -258
- package/esm/svelte/DemoGrid.svelte.d.ts +0 -49
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
<script lang="ts" generics="T extends { id: string }">
|
|
2
|
-
import { untrack } from 'svelte'
|
|
3
|
-
|
|
4
|
-
import type { ClassType, EntityFilter } from 'remult'
|
|
5
|
-
|
|
6
|
-
import { ff, type FF_Many, type ManyStrategy } from './ff.svelte.js'
|
|
7
|
-
|
|
8
|
-
type Props = {
|
|
9
|
-
/** The remult entity class to CRUD. */
|
|
10
|
-
entity: ClassType<T>
|
|
11
|
-
/** Fields to show as columns / edit inputs. Headers come from each field's `caption`. */
|
|
12
|
-
fields: (keyof T & string)[]
|
|
13
|
-
/** Filter the list (remult `EntityFilter`). Reactive: change it to re-fetch. */
|
|
14
|
-
where?: EntityFilter<T>
|
|
15
|
-
/** Fetch strategy: `paginate` (default), `listen` (live), or `load` (static one-shot). */
|
|
16
|
-
strategy?: ManyStrategy
|
|
17
|
-
/** When false the list query is skipped (no auto-load) until it flips true. */
|
|
18
|
-
enabled?: boolean
|
|
19
|
-
/** Rows per page (paginate strategy). */
|
|
20
|
-
pageSize?: number
|
|
21
|
-
}
|
|
22
|
-
let {
|
|
23
|
-
entity,
|
|
24
|
-
fields,
|
|
25
|
-
where,
|
|
26
|
-
strategy = 'paginate',
|
|
27
|
-
enabled = true,
|
|
28
|
-
pageSize = 25,
|
|
29
|
-
}: Props = $props()
|
|
30
|
-
|
|
31
|
-
// ONE handle does it all: list (per strategy) + editing draft + writes.
|
|
32
|
-
// `entity` + `strategy` are init-only - the handle builds its `$effect` once; the
|
|
33
|
-
// reactive bits live in the getter (`where`/`enabled`/`pageSize`, read on each run).
|
|
34
|
-
// `untrack` says "read these once on purpose" so Svelte doesn't flag a stale capture.
|
|
35
|
-
// Cast to the paginate view so `more()`/`aggregates`/`refresh` are reachable; guarded below.
|
|
36
|
-
const m = untrack(() =>
|
|
37
|
-
ff(entity).many(() => ({ where, enabled, pageSize }), strategy),
|
|
38
|
-
) as unknown as FF_Many<T, 'paginate'>
|
|
39
|
-
|
|
40
|
-
const creating = $derived(!!m.draft && !m.draft.id)
|
|
41
|
-
|
|
42
|
-
// dynamic read/write of a field by key (v1: inputs are text)
|
|
43
|
-
const get = (row: T, f: keyof T & string) => (row as Record<string, unknown>)[f]
|
|
44
|
-
function setField(f: keyof T & string, value: string) {
|
|
45
|
-
if (m.draft) (m.draft as Record<string, unknown>)[f] = value
|
|
46
|
-
}
|
|
47
|
-
</script>
|
|
48
|
-
|
|
49
|
-
{#snippet editRow(draft: T)}
|
|
50
|
-
{#each fields as f (f)}
|
|
51
|
-
<td>
|
|
52
|
-
<input
|
|
53
|
-
placeholder={m.meta.fields.find(f)?.caption ?? f}
|
|
54
|
-
value={String(get(draft, f) ?? '')}
|
|
55
|
-
oninput={(e) => setField(f, e.currentTarget.value)}
|
|
56
|
-
/>
|
|
57
|
-
</td>
|
|
58
|
-
{/each}
|
|
59
|
-
<td class="actions">
|
|
60
|
-
<button
|
|
61
|
-
disabled={m.isWriting ||
|
|
62
|
-
(draft.id ? !m.meta.apiUpdateAllowed(draft) : !m.meta.apiInsertAllowed(draft))}
|
|
63
|
-
onclick={() => m.save()}
|
|
64
|
-
>
|
|
65
|
-
Save
|
|
66
|
-
</button>
|
|
67
|
-
<button onclick={() => m.cancel()}>Cancel</button>
|
|
68
|
-
</td>
|
|
69
|
-
{/snippet}
|
|
70
|
-
|
|
71
|
-
<div class="crud">
|
|
72
|
-
<div class="bar">
|
|
73
|
-
<button disabled={!m.meta.apiInsertAllowed()} onclick={() => m.create()}>+ New</button>
|
|
74
|
-
{#if strategy !== 'listen'}
|
|
75
|
-
<button onclick={() => m.refresh()}>Refresh</button>
|
|
76
|
-
{/if}
|
|
77
|
-
{#if strategy === 'paginate' && m.aggregates}<span class="count">{m.aggregates.$count} rows</span
|
|
78
|
-
>{/if}
|
|
79
|
-
{#if m.isBusy}<span class="busy">busy…</span>{/if}
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
{#if m.error}<p class="err">{m.error}</p>{/if}
|
|
83
|
-
|
|
84
|
-
<table>
|
|
85
|
-
<thead>
|
|
86
|
-
<tr>
|
|
87
|
-
{#each fields as f (f)}<th>{m.meta.fields.find(f)?.caption ?? f}</th>{/each}
|
|
88
|
-
<th></th>
|
|
89
|
-
</tr>
|
|
90
|
-
</thead>
|
|
91
|
-
<tbody>
|
|
92
|
-
{#if creating && m.draft}
|
|
93
|
-
<tr class="editing">{@render editRow(m.draft)}</tr>
|
|
94
|
-
{/if}
|
|
95
|
-
{#if m.loading.init}
|
|
96
|
-
<!--
|
|
97
|
-
First-load placeholder. We know `fields`, so we render one shimmer cell per column
|
|
98
|
-
plus a button-sized cell in the actions column - that keeps each skeleton row the
|
|
99
|
-
same height as a real row, so the table doesn't jump when the data arrives.
|
|
100
|
-
-->
|
|
101
|
-
{#each Array(2) as _, i (i)}
|
|
102
|
-
<tr>
|
|
103
|
-
{#each fields as f (f)}<td><span class="sk"></span></td>{/each}
|
|
104
|
-
<td class="actions"><span class="sk sk-btn"></span></td>
|
|
105
|
-
</tr>
|
|
106
|
-
{/each}
|
|
107
|
-
{:else}
|
|
108
|
-
{#each m.items as row (row.id)}
|
|
109
|
-
<tr class:editing={m.draft?.id === row.id}>
|
|
110
|
-
{#if m.draft && m.draft.id === row.id}
|
|
111
|
-
{@render editRow(m.draft)}
|
|
112
|
-
{:else}
|
|
113
|
-
{#each fields as f (f)}<td>{get(row, f)}</td>{/each}
|
|
114
|
-
<td class="actions">
|
|
115
|
-
<button disabled={!m.meta.apiUpdateAllowed(row)} onclick={() => m.edit(row)}> Edit </button>
|
|
116
|
-
<button disabled={!m.meta.apiDeleteAllowed(row)} onclick={() => m.remove(row)}>
|
|
117
|
-
Delete
|
|
118
|
-
</button>
|
|
119
|
-
</td>
|
|
120
|
-
{/if}
|
|
121
|
-
</tr>
|
|
122
|
-
{/each}
|
|
123
|
-
{/if}
|
|
124
|
-
</tbody>
|
|
125
|
-
</table>
|
|
126
|
-
|
|
127
|
-
{#if strategy === 'paginate' && m.hasNextPage}
|
|
128
|
-
<!--
|
|
129
|
-
Manual "More". To auto-load on scroll instead, use the `infiniteScroll` attachment
|
|
130
|
-
(also from 'firstly/svelte') on a bottom sentinel:
|
|
131
|
-
<div {@attach infiniteScroll({
|
|
132
|
-
hasMore: () => m.hasNextPage,
|
|
133
|
-
loading: () => m.loading.more,
|
|
134
|
-
onMore: () => m.more(),
|
|
135
|
-
})}></div>
|
|
136
|
-
-->
|
|
137
|
-
<button disabled={m.loading.more} onclick={() => m.more()}>
|
|
138
|
-
{m.loading.more ? 'Loading…' : 'More'}
|
|
139
|
-
</button>
|
|
140
|
-
{/if}
|
|
141
|
-
|
|
142
|
-
{#if !enabled}
|
|
143
|
-
<p class="muted">Not loaded (<code>enabled: false</code>) - flip it to fetch.</p>
|
|
144
|
-
{:else if !m.loading.init && m.items.length === 0 && !creating}
|
|
145
|
-
<p class="muted">Nothing yet - hit “+ New”.</p>
|
|
146
|
-
{/if}
|
|
147
|
-
</div>
|
|
148
|
-
|
|
149
|
-
<style>
|
|
150
|
-
.crud {
|
|
151
|
-
font-size: 14px;
|
|
152
|
-
display: flex;
|
|
153
|
-
flex-direction: column;
|
|
154
|
-
gap: 10px;
|
|
155
|
-
align-items: start;
|
|
156
|
-
}
|
|
157
|
-
.bar {
|
|
158
|
-
display: flex;
|
|
159
|
-
gap: 10px;
|
|
160
|
-
align-items: center;
|
|
161
|
-
flex-wrap: wrap;
|
|
162
|
-
width: 100%;
|
|
163
|
-
}
|
|
164
|
-
.count {
|
|
165
|
-
font-size: 12px;
|
|
166
|
-
font-variant-numeric: tabular-nums;
|
|
167
|
-
opacity: 0.85;
|
|
168
|
-
}
|
|
169
|
-
.busy {
|
|
170
|
-
font-size: 12px;
|
|
171
|
-
color: #f59e0b;
|
|
172
|
-
font-weight: 600;
|
|
173
|
-
}
|
|
174
|
-
.err {
|
|
175
|
-
color: #ef4444;
|
|
176
|
-
margin: 0;
|
|
177
|
-
}
|
|
178
|
-
table {
|
|
179
|
-
border-collapse: collapse;
|
|
180
|
-
width: 100%;
|
|
181
|
-
}
|
|
182
|
-
th,
|
|
183
|
-
td {
|
|
184
|
-
border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
|
|
185
|
-
padding: 7px 10px;
|
|
186
|
-
text-align: left;
|
|
187
|
-
vertical-align: middle;
|
|
188
|
-
}
|
|
189
|
-
th {
|
|
190
|
-
font-weight: 600;
|
|
191
|
-
background: color-mix(in srgb, currentColor 7%, transparent);
|
|
192
|
-
}
|
|
193
|
-
tr.editing {
|
|
194
|
-
background: color-mix(in srgb, currentColor 5%, transparent);
|
|
195
|
-
}
|
|
196
|
-
td.actions {
|
|
197
|
-
white-space: nowrap;
|
|
198
|
-
text-align: right;
|
|
199
|
-
}
|
|
200
|
-
td.actions button + button {
|
|
201
|
-
margin-left: 6px;
|
|
202
|
-
}
|
|
203
|
-
input {
|
|
204
|
-
width: 100%;
|
|
205
|
-
box-sizing: border-box;
|
|
206
|
-
padding: 5px 7px;
|
|
207
|
-
font: inherit;
|
|
208
|
-
color: inherit;
|
|
209
|
-
background: transparent;
|
|
210
|
-
border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
|
|
211
|
-
border-radius: 6px;
|
|
212
|
-
}
|
|
213
|
-
button {
|
|
214
|
-
cursor: pointer;
|
|
215
|
-
font: inherit;
|
|
216
|
-
padding: 4px 11px;
|
|
217
|
-
color: inherit;
|
|
218
|
-
background: color-mix(in srgb, currentColor 6%, transparent);
|
|
219
|
-
border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
|
|
220
|
-
border-radius: 6px;
|
|
221
|
-
transition: background 0.12s ease;
|
|
222
|
-
}
|
|
223
|
-
button:hover {
|
|
224
|
-
background: color-mix(in srgb, currentColor 14%, transparent);
|
|
225
|
-
}
|
|
226
|
-
button:disabled {
|
|
227
|
-
opacity: 0.45;
|
|
228
|
-
cursor: not-allowed;
|
|
229
|
-
}
|
|
230
|
-
.muted {
|
|
231
|
-
opacity: 0.6;
|
|
232
|
-
}
|
|
233
|
-
.sk {
|
|
234
|
-
display: inline-block;
|
|
235
|
-
width: 70%;
|
|
236
|
-
height: 0.8em;
|
|
237
|
-
border-radius: 4px;
|
|
238
|
-
background: color-mix(in srgb, currentColor 16%, transparent);
|
|
239
|
-
animation: sk-pulse 1.2s ease-in-out infinite;
|
|
240
|
-
}
|
|
241
|
-
/* button-sized so a skeleton row matches a real (button-bearing) row's height */
|
|
242
|
-
.sk-btn {
|
|
243
|
-
width: 3.4em;
|
|
244
|
-
height: 1.9em;
|
|
245
|
-
}
|
|
246
|
-
@keyframes sk-pulse {
|
|
247
|
-
0%,
|
|
248
|
-
100% {
|
|
249
|
-
opacity: 0.45;
|
|
250
|
-
}
|
|
251
|
-
50% {
|
|
252
|
-
opacity: 0.85;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
code {
|
|
256
|
-
font-size: 12px;
|
|
257
|
-
}
|
|
258
|
-
</style>
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { ClassType, EntityFilter } from 'remult';
|
|
2
|
-
import { type ManyStrategy } from './ff.svelte.js';
|
|
3
|
-
declare function $$render<T extends {
|
|
4
|
-
id: string;
|
|
5
|
-
}>(): {
|
|
6
|
-
props: {
|
|
7
|
-
/** The remult entity class to CRUD. */
|
|
8
|
-
entity: ClassType<T>;
|
|
9
|
-
/** Fields to show as columns / edit inputs. Headers come from each field's `caption`. */
|
|
10
|
-
fields: (keyof T & string)[];
|
|
11
|
-
/** Filter the list (remult `EntityFilter`). Reactive: change it to re-fetch. */
|
|
12
|
-
where?: EntityFilter<T>;
|
|
13
|
-
/** Fetch strategy: `paginate` (default), `listen` (live), or `load` (static one-shot). */
|
|
14
|
-
strategy?: ManyStrategy;
|
|
15
|
-
/** When false the list query is skipped (no auto-load) until it flips true. */
|
|
16
|
-
enabled?: boolean;
|
|
17
|
-
/** Rows per page (paginate strategy). */
|
|
18
|
-
pageSize?: number;
|
|
19
|
-
};
|
|
20
|
-
exports: {};
|
|
21
|
-
bindings: "";
|
|
22
|
-
slots: {};
|
|
23
|
-
events: {};
|
|
24
|
-
};
|
|
25
|
-
declare class __sveltets_Render<T extends {
|
|
26
|
-
id: string;
|
|
27
|
-
}> {
|
|
28
|
-
props(): ReturnType<typeof $$render<T>>['props'];
|
|
29
|
-
events(): ReturnType<typeof $$render<T>>['events'];
|
|
30
|
-
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
31
|
-
bindings(): "";
|
|
32
|
-
exports(): {};
|
|
33
|
-
}
|
|
34
|
-
interface $$IsomorphicComponent {
|
|
35
|
-
new <T extends {
|
|
36
|
-
id: string;
|
|
37
|
-
}>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
38
|
-
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
39
|
-
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
40
|
-
<T extends {
|
|
41
|
-
id: string;
|
|
42
|
-
}>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
43
|
-
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
44
|
-
}
|
|
45
|
-
declare const DemoGrid: $$IsomorphicComponent;
|
|
46
|
-
type DemoGrid<T extends {
|
|
47
|
-
id: string;
|
|
48
|
-
}> = InstanceType<typeof DemoGrid<T>>;
|
|
49
|
-
export default DemoGrid;
|