firstly 0.5.0 → 0.5.1

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # firstly
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#282](https://github.com/jycouet/firstly/pull/282) [`a93d4c8`](https://github.com/jycouet/firstly/commit/a93d4c8815da30f2c4d701ddd7e3d8cdf39fd8f5) Thanks [@jycouet](https://github.com/jycouet)! - ffRepo (svelte): leaner surface. Rename `firstOnce` → `onFirst`; remove `draft`, `first`, `insert`, `update`, `deleteMany`. List handles (`load`/`listen`/`paginate`) are now read-only - write via `.repo` (+ `addItem`/`updateItem`/`removeItem` to reconcile). Editing lives on `one`/`create()` with argless `save()`/`delete()`.
8
+
9
+ New `DemoGrid` (from `firstly/svelte`): a generic inline-CRUD table over any entity - props `entity` + `fields`, headers/placeholders from each field's `caption`.
10
+
3
11
  ## 0.5.0
4
12
 
5
13
  ### Minor Changes
@@ -0,0 +1,167 @@
1
+ <script lang="ts" generics="T extends { id: string }">
2
+ import type { ClassType, EntityFilter, MembersOnly } from 'remult'
3
+
4
+ import { ffRepo } from './FF_Repo.svelte.js'
5
+
6
+ type 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
+ }
12
+ let { entity, fields }: Props = $props()
13
+
14
+ // live list - any write re-emits it, so no reconcile code (entity is static config)
15
+ const list = ffRepo(entity).listen(() => ({}))
16
+
17
+ // the row being edited (by id), or a fresh draft for "new" - one reactive slot
18
+ let editingId = $state<string | null>(null)
19
+ const editor = ffRepo(entity).one(() => ({
20
+ where: { id: editingId ?? '' } as EntityFilter<T>,
21
+ enabled: !!editingId,
22
+ }))
23
+ const creating = $derived(!!editor.item && !editor.item.id)
24
+
25
+ // dynamic read/write of a field by key (v1: inputs are text)
26
+ const get = (row: T, f: keyof T & string) => (row as Record<string, unknown>)[f]
27
+ function setField(f: keyof T & string, value: string) {
28
+ if (editor.item) (editor.item as Record<string, unknown>)[f] = value
29
+ }
30
+
31
+ function edit(id: string) {
32
+ editingId = id // → editor.item loads that row
33
+ }
34
+ function add() {
35
+ editingId = null
36
+ editor.create() // blank draft into editor.item
37
+ }
38
+ function cancel() {
39
+ editingId = null
40
+ editor.item = undefined // drop the draft / stop editing
41
+ }
42
+ async function save() {
43
+ await editor.save() // insert (a draft) or update (the loaded row); the live list self-syncs
44
+ cancel()
45
+ }
46
+ async function remove(row: T) {
47
+ await list.repo.delete(row as Partial<MembersOnly<T>>) // raw delete via .repo; the live list drops the row
48
+ }
49
+ </script>
50
+
51
+ {#snippet editRow(draft: T)}
52
+ {#each fields as f (f)}
53
+ <td>
54
+ <input
55
+ placeholder={list.meta.fields.find(f)?.caption ?? f}
56
+ value={String(get(draft, f) ?? '')}
57
+ oninput={(e) => setField(f, e.currentTarget.value)}
58
+ />
59
+ </td>
60
+ {/each}
61
+ <td class="actions">
62
+ <button disabled={editor.loading.saving} onclick={save}>Save</button>
63
+ <button onclick={cancel}>Cancel</button>
64
+ </td>
65
+ {/snippet}
66
+
67
+ <div class="crud">
68
+ <button class="new" onclick={add}>+ New</button>
69
+
70
+ <table>
71
+ <thead>
72
+ <tr>
73
+ {#each fields as f (f)}<th>{list.meta.fields.find(f)?.caption ?? f}</th>{/each}
74
+ <th></th>
75
+ </tr>
76
+ </thead>
77
+ <tbody>
78
+ {#if creating && editor.item}
79
+ <tr class="editing">{@render editRow(editor.item)}</tr>
80
+ {/if}
81
+ {#each list.items as row (row.id)}
82
+ <tr class:editing={editingId === row.id}>
83
+ {#if editingId === row.id && editor.item}
84
+ {@render editRow(editor.item)}
85
+ {:else}
86
+ {#each fields as f (f)}<td>{get(row, f)}</td>{/each}
87
+ <td class="actions">
88
+ <button onclick={() => edit(row.id)}>Edit</button>
89
+ <button onclick={() => remove(row)}>Delete</button>
90
+ </td>
91
+ {/if}
92
+ </tr>
93
+ {/each}
94
+ </tbody>
95
+ </table>
96
+
97
+ {#if list.loading.init}
98
+ <p class="muted">Loading…</p>
99
+ {:else if list.items.length === 0 && !creating}
100
+ <p class="muted">Nothing yet - hit “+ New”.</p>
101
+ {/if}
102
+ </div>
103
+
104
+ <style>
105
+ .crud {
106
+ font-size: 14px;
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 10px;
110
+ align-items: start;
111
+ }
112
+ table {
113
+ border-collapse: collapse;
114
+ width: 100%;
115
+ }
116
+ th,
117
+ td {
118
+ border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
119
+ padding: 7px 10px;
120
+ text-align: left;
121
+ vertical-align: middle;
122
+ }
123
+ th {
124
+ font-weight: 600;
125
+ background: color-mix(in srgb, currentColor 7%, transparent);
126
+ }
127
+ tr.editing {
128
+ background: color-mix(in srgb, currentColor 5%, transparent);
129
+ }
130
+ td.actions {
131
+ white-space: nowrap;
132
+ text-align: right;
133
+ }
134
+ td.actions button + button {
135
+ margin-left: 6px;
136
+ }
137
+ input {
138
+ width: 100%;
139
+ box-sizing: border-box;
140
+ padding: 5px 7px;
141
+ font: inherit;
142
+ color: inherit;
143
+ background: transparent;
144
+ border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
145
+ border-radius: 6px;
146
+ }
147
+ button {
148
+ cursor: pointer;
149
+ font: inherit;
150
+ padding: 4px 11px;
151
+ color: inherit;
152
+ background: color-mix(in srgb, currentColor 6%, transparent);
153
+ border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
154
+ border-radius: 6px;
155
+ transition: background 0.12s ease;
156
+ }
157
+ button:hover {
158
+ background: color-mix(in srgb, currentColor 14%, transparent);
159
+ }
160
+ button:disabled {
161
+ opacity: 0.45;
162
+ cursor: not-allowed;
163
+ }
164
+ .muted {
165
+ opacity: 0.6;
166
+ }
167
+ </style>
@@ -0,0 +1,40 @@
1
+ import type { ClassType } from 'remult';
2
+ declare function $$render<T extends {
3
+ id: string;
4
+ }>(): {
5
+ props: {
6
+ /** The remult entity class to CRUD. */
7
+ entity: ClassType<T>;
8
+ /** Fields to show as columns / edit inputs. Headers come from each field's `caption`. */
9
+ fields: (keyof T & string)[];
10
+ };
11
+ exports: {};
12
+ bindings: "";
13
+ slots: {};
14
+ events: {};
15
+ };
16
+ declare class __sveltets_Render<T extends {
17
+ id: string;
18
+ }> {
19
+ props(): ReturnType<typeof $$render<T>>['props'];
20
+ events(): ReturnType<typeof $$render<T>>['events'];
21
+ slots(): ReturnType<typeof $$render<T>>['slots'];
22
+ bindings(): "";
23
+ exports(): {};
24
+ }
25
+ interface $$IsomorphicComponent {
26
+ new <T extends {
27
+ id: string;
28
+ }>(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']>> & {
29
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
30
+ } & ReturnType<__sveltets_Render<T>['exports']>;
31
+ <T extends {
32
+ id: string;
33
+ }>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
34
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
35
+ }
36
+ declare const DemoGrid: $$IsomorphicComponent;
37
+ type DemoGrid<T extends {
38
+ id: string;
39
+ }> = InstanceType<typeof DemoGrid<T>>;
40
+ export default DemoGrid;
@@ -29,12 +29,12 @@ import { type ClassType, type EntityFilter, type EntityMetadata, type EntityOrde
29
29
  * the moment it flips true - use it for search-min-length, tab visibility,
30
30
  * dependent queries, or a manual button trigger.
31
31
  *
32
- * Mutations on a handle (`insert`/`update`/`save`/`delete`) flip `loading` and keep
33
- * the result in sync - in `live` mode the liveQuery does it; in `load`/`paginate` a
34
- * delete is removed locally and an insert/update triggers a keep-count re-fetch; in
35
- * `one` any write re-fetches the single record. `save()`/`delete()` with no argument
36
- * target the current `item` (pairs with `one`/`create()`). For a raw write that syncs
37
- * nothing, use `.repo` (e.g. `ffRepo(E).repo.insert(...)`).
32
+ * Writes: only the record handle (`one` / `create()`) writes - argless `save()` / `delete()`
33
+ * act on its `item` and re-sync it. List handles (`load` / `listen` / `paginate`) don't write;
34
+ * go through `.repo` (the plain remult repo: `insert` / `update` / `save` / `delete` /
35
+ * `deleteMany`), then reflect it in a `load` / `paginate` list with the local reconcilers
36
+ * (`addItem` / `updateItem` / `removeItem`) - a `listen` list re-syncs itself via the liveQuery.
37
+ * A failed write fills `error` and re-throws.
38
38
  *
39
39
  * The factory's return type is mode-specific, so e.g. `.more()` doesn't exist on
40
40
  * a `listen()` handle. Methods also throw if reached via a cast in the wrong mode.
@@ -113,29 +113,36 @@ declare class FF_RepoHandle<Entity, O extends FF_RepoOptions<Entity> = FF_RepoOp
113
113
  hasNextPage: boolean;
114
114
  /** Aggregations for the whole query (paginate mode). `aggregates.$count` is the total row count. */
115
115
  aggregates: ExtractAggregateResult<Entity, O> | undefined;
116
- first: Entity | null;
117
116
  constructor(r: Repository<Entity>, opts: Getter<Entity, O>, mode: Mode);
118
117
  /** Re-run the current query (load/paginate/one), back to the first page. */
119
118
  refresh(): Promise<void>;
120
119
  /** Load and append the next page (paginate mode). */
121
120
  more(): Promise<void>;
122
121
  /**
123
- * Run `fn` once, the first time a row is known (first non-null `first`).
124
- * Never fires while the result is empty - so it's robust to liveQuery emitting
125
- * an empty snapshot before the data lands. Nothing to seed from an empty result.
122
+ * Run `fn` once - the first time a row exists (`items[0]`).
123
+ *
124
+ * The point: seed editable UI state from the latest row WITHOUT a live query
125
+ * clobbering in-progress edits. It fires a single time, on the first non-empty
126
+ * result, and never again - later ticks (an edit, a delete, a re-sort) are
127
+ * ignored. Empty snapshots are skipped (a liveQuery often emits one before the
128
+ * data lands; there is nothing to seed from an empty result).
129
+ *
130
+ * For pure derived state prefer `$derived`; reach for `onFirst` only when the
131
+ * seed must become independently editable (a draft the user then mutates).
132
+ *
133
+ * ```svelte
134
+ * const list = ffRepo(Plan).listen(() => ({ where: { ownerDid } }))
135
+ * let draft = $state({ title: '' })
136
+ * list.onFirst((latest) => (draft.title = latest.title)) // seed once, then edit freely
137
+ * ```
126
138
  */
127
- firstOnce(fn: (latest: Entity) => void): void;
128
- /** Reactive, bindable editor state seeded once from the latest row. */
129
- draft<D extends Record<string, unknown>>(seed: (latest: Entity | null) => D): D;
139
+ onFirst(fn: (latest: Entity) => void): void;
130
140
  /** Create a new unsaved entity into the `item` slot (for an edit form). */
131
141
  create(...args: Parameters<Repository<Entity>['create']>): Entity;
132
- insert(...args: Parameters<Repository<Entity>['insert']>): Promise<Entity>;
133
- update(...args: Parameters<Repository<Entity>['update']>): Promise<Entity>;
134
- /** Save. With no argument, saves the current `item` (e.g. a bound `one`/`create` form). */
135
- save(...args: [] | Parameters<Repository<Entity>['save']>): Promise<Entity>;
136
- /** Delete. With no argument, deletes the current `item`. */
137
- delete(...args: [] | Parameters<Repository<Entity>['delete']>): Promise<void>;
138
- deleteMany(...args: Parameters<Repository<Entity>['deleteMany']>): Promise<number>;
142
+ /** Save the current `item` (from `one` / `create()`). To save a specific row, use `.repo.save(row)`. */
143
+ save(): Promise<Entity>;
144
+ /** Delete the current `item`. To delete a specific row/id, use `.repo.delete(idOrRow)`. */
145
+ delete(): Promise<void>;
139
146
  /** Insert into `items` at `top` (default) / `bottom` / an index (`-1` = last). +1 to `$count`. */
140
147
  addItem(item: Entity, options?: {
141
148
  at?: 'top' | 'bottom' | number;
@@ -154,20 +161,20 @@ declare class FF_RepoHandle<Entity, O extends FF_RepoOptions<Entity> = FF_RepoOp
154
161
  /** Escape hatch to the underlying repo (count, findId, upsert, projections, ...). */
155
162
  get repo(): Repository<Entity>;
156
163
  }
157
- /** load: one-shot list; `refresh()` to re-run. No paging / aggregates. */
158
- export type FF_RepoLoad<Entity, O extends FF_RepoOptions<Entity> = FF_RepoOptions<Entity>> = Omit<FF_RepoHandle<Entity, O>, 'more' | 'hasNextPage' | 'aggregates'>;
159
- /** live: reactive subscription, auto-updates. No manual refresh / paging / aggregates / list reconcilers (the liveQuery does it). */
160
- export type FF_RepoLive<Entity, O extends FF_RepoOptions<Entity> = FF_RepoOptions<Entity>> = Omit<FF_RepoHandle<Entity, O>, 'refresh' | 'more' | 'hasNextPage' | 'aggregates' | 'addItem' | 'updateItem' | 'removeItem'>;
161
- /** paginate: `more()` / `hasNextPage` / `aggregates`. No `first`/`firstOnce`/`draft` (paged ≠ latest). */
162
- export type FF_RepoPaginate<Entity, O extends FF_RepoOptions<Entity> = FF_RepoOptions<Entity>> = Omit<FF_RepoHandle<Entity, O>, 'first' | 'firstOnce' | 'draft'>;
163
- /** one: a single reactive record in `item` (+ `first`). No paging / aggregates / list reconcilers. */
164
+ /** load: one-shot list (`refresh()` to re-run) - a read+reconcile view. No paging/aggregates, and no `item`/`save`/`delete`/`create` (edit via `one`, write via `.repo`). */
165
+ export type FF_RepoLoad<Entity, O extends FF_RepoOptions<Entity> = FF_RepoOptions<Entity>> = Omit<FF_RepoHandle<Entity, O>, 'more' | 'hasNextPage' | 'aggregates' | 'item' | 'save' | 'delete' | 'create'>;
166
+ /** live: reactive subscription, auto-updates - a read view. No refresh/paging/aggregates/reconcilers (the liveQuery does it), and no `item`/`save`/`delete`/`create`. */
167
+ export type FF_RepoLive<Entity, O extends FF_RepoOptions<Entity> = FF_RepoOptions<Entity>> = Omit<FF_RepoHandle<Entity, O>, 'refresh' | 'more' | 'hasNextPage' | 'aggregates' | 'addItem' | 'updateItem' | 'removeItem' | 'item' | 'save' | 'delete' | 'create'>;
168
+ /** paginate: `more()` / `hasNextPage` / `aggregates` - a read+reconcile view. No `onFirst` (paged ≠ latest), and no `item`/`save`/`delete`/`create`. */
169
+ export type FF_RepoPaginate<Entity, O extends FF_RepoOptions<Entity> = FF_RepoOptions<Entity>> = Omit<FF_RepoHandle<Entity, O>, 'onFirst' | 'item' | 'save' | 'delete' | 'create'>;
170
+ /** one: a single reactive record in `item`. No paging / aggregates / list reconcilers. */
164
171
  export type FF_RepoOne<Entity, O extends FF_RepoOptions<Entity> = FF_RepoOptions<Entity>> = Omit<FF_RepoHandle<Entity, O>, 'more' | 'hasNextPage' | 'aggregates' | 'addItem' | 'updateItem' | 'removeItem'>;
165
172
  /**
166
173
  * Umbrella handle type - any mode. Use for a component prop that accepts a
167
174
  * `load`/`listen`/`paginate`/`one` handle (`r: FF_Repo<T>`). It exposes the surface
168
- * common to every mode (`items`/`item`/`loading`/`error`/`meta`/`repo` + the writes);
169
- * mode-specific members (`more`/`hasNextPage`/`aggregates`/`refresh`/`first`/`firstOnce`/
170
- * `draft`/`addItem`/`updateItem`/`removeItem`) require the matching per-mode type.
175
+ * common to every mode (`items`/`loading`/`error`/`meta`/`repo`); mode-specific members
176
+ * (`item`/`save`/`delete`/`create` on `one`; `more`/`hasNextPage`/`aggregates`/`refresh`/
177
+ * `onFirst`/`addItem`/`updateItem`/`removeItem`) require the matching per-mode type.
171
178
  */
172
179
  export type FF_Repo<Entity, O extends FF_RepoOptions<Entity> = FF_RepoOptions<Entity>> = FF_RepoLoad<Entity, O> | FF_RepoLive<Entity, O> | FF_RepoPaginate<Entity, O> | FF_RepoOne<Entity, O>;
173
180
  type StrictGetter<Entity, O extends FF_RepoOptions<Entity>> = () => O & Record<Exclude<keyof O, keyof FF_RepoOptions<Entity>>, never>;
@@ -25,7 +25,6 @@ class FF_RepoHandle {
25
25
  hasNextPage = $state(false);
26
26
  /** Aggregations for the whole query (paginate mode). `aggregates.$count` is the total row count. */
27
27
  aggregates = $state(undefined);
28
- first = $derived(this.items[0] ?? null);
29
28
  constructor(r, opts, mode) {
30
29
  this.#repo = r;
31
30
  this.#opts = opts;
@@ -39,7 +38,7 @@ class FF_RepoHandle {
39
38
  }
40
39
  if (mode === 'live') {
41
40
  // Pass orderBy so liveQuery re-sorts incrementally-added rows too;
42
- // without it a freshly inserted row is appended and `first` goes stale.
41
+ // without it a freshly inserted row is appended and `items[0]` (the latest) goes stale.
43
42
  const unsub = this.#repo
44
43
  .liveQuery({ where: o.where, orderBy: o.orderBy, limit: o.limit, include: o.include })
45
44
  .subscribe({
@@ -146,28 +145,35 @@ class FF_RepoHandle {
146
145
  }
147
146
  }
148
147
  /**
149
- * Run `fn` once, the first time a row is known (first non-null `first`).
150
- * Never fires while the result is empty - so it's robust to liveQuery emitting
151
- * an empty snapshot before the data lands. Nothing to seed from an empty result.
148
+ * Run `fn` once - the first time a row exists (`items[0]`).
149
+ *
150
+ * The point: seed editable UI state from the latest row WITHOUT a live query
151
+ * clobbering in-progress edits. It fires a single time, on the first non-empty
152
+ * result, and never again - later ticks (an edit, a delete, a re-sort) are
153
+ * ignored. Empty snapshots are skipped (a liveQuery often emits one before the
154
+ * data lands; there is nothing to seed from an empty result).
155
+ *
156
+ * For pure derived state prefer `$derived`; reach for `onFirst` only when the
157
+ * seed must become independently editable (a draft the user then mutates).
158
+ *
159
+ * ```svelte
160
+ * const list = ffRepo(Plan).listen(() => ({ where: { ownerDid } }))
161
+ * let draft = $state({ title: '' })
162
+ * list.onFirst((latest) => (draft.title = latest.title)) // seed once, then edit freely
163
+ * ```
152
164
  */
153
- firstOnce(fn) {
165
+ onFirst(fn) {
154
166
  let done = false;
155
167
  $effect(() => {
156
168
  if (done)
157
169
  return;
158
- const latest = this.first;
170
+ const latest = this.items[0];
159
171
  if (latest == null)
160
172
  return;
161
173
  fn(latest);
162
174
  done = true;
163
175
  });
164
176
  }
165
- /** Reactive, bindable editor state seeded once from the latest row. */
166
- draft(seed) {
167
- const values = $state(seed(null));
168
- this.firstOnce((latest) => Object.assign(values, seed(latest)));
169
- return values;
170
- }
171
177
  /** Create a new unsaved entity into the `item` slot (for an edit form). */
172
178
  create(...args) {
173
179
  this.item = this.#repo.create(...args);
@@ -194,24 +200,14 @@ class FF_RepoHandle {
194
200
  this.loading[flag] = false;
195
201
  }
196
202
  }
197
- insert(...args) {
198
- return this.#write('saving', () => this.#repo.insert(...args), () => this.#resync());
199
- }
200
- update(...args) {
201
- return this.#write('saving', () => this.#repo.update(...args), () => this.#resync());
203
+ /** Save the current `item` (from `one` / `create()`). To save a specific row, use `.repo.save(row)`. */
204
+ save() {
205
+ return this.#write('saving', () => this.#repo.save(this.#requireItem()), () => this.#resync());
202
206
  }
203
- /** Save. With no argument, saves the current `item` (e.g. a bound `one`/`create` form). */
204
- save(...args) {
205
- return this.#write('saving', () => this.#repo.save(...(args.length ? args : [this.#requireItem()])), () => this.#resync());
206
- }
207
- /** Delete. With no argument, deletes the current `item`. */
208
- delete(...args) {
209
- let target;
210
- return this.#write('deleting', () => {
211
- const a = (args.length ? args : [this.#requireItem()]);
212
- target = a[0];
213
- return this.#repo.delete(...a);
214
- }, () => {
207
+ /** Delete the current `item`. To delete a specific row/id, use `.repo.delete(idOrRow)`. */
208
+ delete() {
209
+ const target = this.#requireItem();
210
+ return this.#write('deleting', () => this.#repo.delete(target), () => {
215
211
  // live: liveQuery removes it. one: re-fetch (likely empty now).
216
212
  // load/paginate: drop it locally (no refetch).
217
213
  if (this.#mode === 'live')
@@ -221,15 +217,12 @@ class FF_RepoHandle {
221
217
  this.#removeLocal(target);
222
218
  });
223
219
  }
224
- /** The current `item` (or throw) - backs the no-arg `save()`/`delete()` forms. */
220
+ /** The current `item` (or throw) - backs the argless `save()`/`delete()`. */
225
221
  #requireItem() {
226
222
  if (this.item === undefined)
227
- throw new Error('FF_Repo: no `item` to save/delete - pass an explicit argument, or load one first (`one` mode or `create()`).');
223
+ throw new Error('FF_Repo: no `item` to save/delete - load one first (`one` mode or `create()`), or write a specific row through `.repo`.');
228
224
  return this.item;
229
225
  }
230
- deleteMany(...args) {
231
- return this.#write('deleting', () => this.#repo.deleteMany(...args), () => this.#resync());
232
- }
233
226
  // Client-side list reconcilers (no server I/O) - reflect a change you made
234
227
  // elsewhere (e.g. via `.repo`) in the reactive `items`. `load`/`paginate` only;
235
228
  // `listen` reconciles itself via the liveQuery. `add`/`remove` also adjust
@@ -5,5 +5,6 @@ export type { InfiniteScrollOptions } from './infiniteScroll.js';
5
5
  export { SP } from './class/SP.svelte';
6
6
  export type { ParamDefinition } from './class/SP.svelte';
7
7
  export { initRemultSvelteReactivity } from './initRemultSvelteReactivity';
8
+ export { default as DemoGrid } from './DemoGrid.svelte';
8
9
  export { default as Icon } from './ui/Icon.svelte';
9
10
  export { LibIcon_Empty, LibIcon_Forbidden, LibIcon_ChevronDown, LibIcon_ChevronUp, LibIcon_ChevronLeft, LibIcon_ChevronRight, LibIcon_Search, LibIcon_Check, LibIcon_MultiCheck, LibIcon_Add, LibIcon_MultiAdd, LibIcon_Edit, LibIcon_Eye, LibIcon_EyeOff, LibIcon_Delete, LibIcon_Cross, LibIcon_Save, LibIcon_Man, LibIcon_Woman, LibIcon_Send, LibIcon_Load, LibIcon_Settings, LibIcon_Sort, LibIcon_SortAsc, LibIcon_SortDesc, } from './ui/LibIcon.js';
@@ -2,5 +2,6 @@ export { ffRepo } from './FF_Repo.svelte.js';
2
2
  export { infiniteScroll } from './infiniteScroll.js';
3
3
  export { SP } from './class/SP.svelte';
4
4
  export { initRemultSvelteReactivity } from './initRemultSvelteReactivity';
5
+ export { default as DemoGrid } from './DemoGrid.svelte';
5
6
  export { default as Icon } from './ui/Icon.svelte';
6
7
  export { LibIcon_Empty, LibIcon_Forbidden, LibIcon_ChevronDown, LibIcon_ChevronUp, LibIcon_ChevronLeft, LibIcon_ChevronRight, LibIcon_Search, LibIcon_Check, LibIcon_MultiCheck, LibIcon_Add, LibIcon_MultiAdd, LibIcon_Edit, LibIcon_Eye, LibIcon_EyeOff, LibIcon_Delete, LibIcon_Cross, LibIcon_Save, LibIcon_Man, LibIcon_Woman, LibIcon_Send, LibIcon_Load, LibIcon_Settings, LibIcon_Sort, LibIcon_SortAsc, LibIcon_SortDesc, } from './ui/LibIcon.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firstly",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "description": "Firstly, an opinionated Remult setup!",
6
6
  "funding": "https://github.com/sponsors/jycouet",
@@ -40,8 +40,8 @@
40
40
  "nodemailer": "8.0.5",
41
41
  "tailwind-merge": "3.5.0",
42
42
  "tailwindcss": "4.2.2",
43
- "vite-plugin-kit-routes": "1.0.5",
44
- "vite-plugin-stripper": "0.10.3"
43
+ "vite-plugin-kit-routes": "1.0.6",
44
+ "vite-plugin-stripper": "0.10.4"
45
45
  },
46
46
  "sideEffects": false,
47
47
  "exports": {