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.
Files changed (43) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/esm/changeLog/index.d.ts +1 -0
  3. package/esm/svelte/FF_Config.svelte +2 -2
  4. package/esm/svelte/FF_Config.svelte.js +3 -0
  5. package/esm/svelte/grid/DefaultInput.svelte +42 -0
  6. package/esm/svelte/grid/DefaultInput.svelte.d.ts +9 -0
  7. package/esm/svelte/grid/FF_Cell.svelte +98 -0
  8. package/esm/svelte/grid/FF_Cell.svelte.d.ts +10 -0
  9. package/esm/svelte/grid/FF_CellValue.svelte +24 -0
  10. package/esm/svelte/grid/FF_CellValue.svelte.d.ts +28 -0
  11. package/esm/svelte/grid/FF_Cell_Content.svelte +37 -0
  12. package/esm/svelte/grid/FF_Cell_Content.svelte.d.ts +10 -0
  13. package/esm/svelte/grid/FF_Cell_Error.svelte +26 -0
  14. package/esm/svelte/grid/FF_Cell_Error.svelte.d.ts +8 -0
  15. package/esm/svelte/grid/FF_Cell_Hint.svelte +26 -0
  16. package/esm/svelte/grid/FF_Cell_Hint.svelte.d.ts +8 -0
  17. package/esm/svelte/grid/FF_Cell_Label.svelte +34 -0
  18. package/esm/svelte/grid/FF_Cell_Label.svelte.d.ts +8 -0
  19. package/esm/svelte/grid/FF_Grid.svelte +385 -0
  20. package/esm/svelte/grid/FF_Grid.svelte.d.ts +47 -0
  21. package/esm/svelte/grid/GroupFields.svelte +141 -0
  22. package/esm/svelte/grid/GroupFields.svelte.d.ts +45 -0
  23. package/esm/svelte/grid/_test/FF_Cell_ContextWrapper.svelte +9 -0
  24. package/esm/svelte/grid/_test/FF_Cell_ContextWrapper.svelte.d.ts +18 -0
  25. package/esm/svelte/grid/buildCells.d.ts +16 -0
  26. package/esm/svelte/grid/buildCells.js +79 -0
  27. package/esm/svelte/grid/cellComponent.d.ts +9 -0
  28. package/esm/svelte/grid/cellComponent.js +31 -0
  29. package/esm/svelte/grid/cellConfig.d.ts +13 -0
  30. package/esm/svelte/grid/cellConfig.js +48 -0
  31. package/esm/svelte/grid/cellTypes.d.ts +168 -0
  32. package/esm/svelte/grid/cellTypes.js +1 -0
  33. package/esm/svelte/grid/index.d.ts +10 -0
  34. package/esm/svelte/grid/index.js +12 -0
  35. package/esm/svelte/grid/metaKind.d.ts +27 -0
  36. package/esm/svelte/grid/metaKind.js +34 -0
  37. package/esm/svelte/index.d.ts +3 -2
  38. package/esm/svelte/index.js +3 -2
  39. package/package.json +4 -3
  40. package/esm/svelte/DemoForm.svelte +0 -121
  41. package/esm/svelte/DemoForm.svelte.d.ts +0 -42
  42. package/esm/svelte/DemoGrid.svelte +0 -258
  43. 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;