firstly 0.2.0 → 0.3.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 (135) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/esm/bin/cmd.js +3 -158
  3. package/esm/changeLog/index.d.ts +1 -6
  4. package/esm/feedback/FeedbackController.d.ts +6 -2
  5. package/esm/feedback/FeedbackController.js +145 -143
  6. package/esm/feedback/server/index.d.ts +2 -2
  7. package/esm/feedback/server/index.js +3 -3
  8. package/esm/feedback/types.d.ts +5 -0
  9. package/esm/feedback/ui/DialogIssue.svelte +5 -5
  10. package/esm/feedback/ui/DialogIssues.svelte +5 -5
  11. package/esm/feedback/ui/DialogMilestones.svelte +1 -1
  12. package/esm/internals/BaseEnum.d.ts +2 -1
  13. package/esm/internals/FF_Entity.js +1 -17
  14. package/esm/internals/FF_Fields.d.ts +4 -3
  15. package/esm/internals/FF_Fields.js +14 -55
  16. package/esm/internals/cellsBuildor.d.ts +2 -1
  17. package/esm/internals/cellsBuildor.js +5 -4
  18. package/esm/internals/index.d.ts +7 -12
  19. package/esm/internals/storeItem.d.ts +12 -20
  20. package/esm/internals/storeItem.js +20 -6
  21. package/esm/mail/server/index.d.ts +8 -2
  22. package/esm/mail/server/index.js +35 -7
  23. package/esm/server/index.d.ts +1 -1
  24. package/esm/svelte/FF_Cell.svelte +3 -5
  25. package/esm/svelte/FF_Cell.svelte.d.ts +4 -2
  26. package/esm/svelte/FF_Form.svelte +4 -5
  27. package/esm/svelte/FF_Grid.svelte +2 -2
  28. package/esm/svelte/FF_Layout.svelte +3 -3
  29. package/esm/svelte/FF_Repo.svelte.d.ts +9 -0
  30. package/esm/svelte/FF_Repo.svelte.js +39 -0
  31. package/esm/svelte/class/SP.svelte.js +14 -2
  32. package/esm/svelte/dialog/DialogManagement.svelte +2 -5
  33. package/esm/svelte/dialog/DialogPrimitive.svelte +1 -2
  34. package/esm/svelte/dialog/dialog.js +2 -2
  35. package/esm/svelte/ff_Config.svelte.js +2 -2
  36. package/esm/svelte/index.d.ts +2 -7
  37. package/esm/svelte/index.js +2 -7
  38. package/esm/ui/Button.svelte +34 -66
  39. package/esm/ui/Button.svelte.d.ts +9 -35
  40. package/esm/ui/Clipboardable.svelte +13 -17
  41. package/esm/ui/Clipboardable.svelte.d.ts +9 -33
  42. package/esm/ui/Field.svelte +48 -9
  43. package/esm/ui/FieldGroup.svelte.d.ts +1 -1
  44. package/esm/ui/Grid.svelte +13 -87
  45. package/esm/ui/Grid.svelte.d.ts +0 -1
  46. package/esm/ui/Grid2.svelte +26 -90
  47. package/esm/ui/Grid2.svelte.d.ts +1 -2
  48. package/esm/ui/GridPaginate.svelte +1 -1
  49. package/esm/ui/GridPaginate2.svelte +2 -2
  50. package/esm/ui/Icon.svelte +2 -18
  51. package/esm/ui/Icon.svelte.d.ts +0 -2
  52. package/esm/ui/LibIcon.js +2 -2
  53. package/esm/ui/Loading.svelte +1 -1
  54. package/esm/ui/dialog/DialogManagement.svelte +14 -5
  55. package/esm/ui/dialog/DialogPrimitive.svelte +3 -3
  56. package/esm/ui/dialog/FormEditAction.svelte +4 -4
  57. package/esm/ui/dialog/dialog.d.ts +5 -2
  58. package/esm/ui/dialog/dialog.js +2 -2
  59. package/esm/ui/index.d.ts +1 -0
  60. package/esm/ui/index.js +1 -0
  61. package/esm/ui/internals/FieldContainer.svelte +25 -14
  62. package/esm/ui/internals/FieldContainer.svelte.d.ts +9 -30
  63. package/esm/ui/internals/Input.svelte.d.ts +1 -1
  64. package/esm/ui/internals/Textarea.svelte +2 -5
  65. package/esm/ui/internals/select/MultiSelectMelt.svelte +10 -8
  66. package/esm/ui/internals/select/MultiSelectMelt.svelte.d.ts +1 -1
  67. package/esm/ui/internals/select/Select2.svelte +88 -0
  68. package/esm/ui/internals/select/Select2.svelte.d.ts +12 -0
  69. package/esm/ui/internals/select/SelectMelt.svelte +33 -24
  70. package/esm/ui/internals/select/SelectMelt.svelte.d.ts +1 -1
  71. package/esm/ui/internals/select/SelectRadio.svelte +2 -2
  72. package/esm/ui/internals/select/SelectRadio.svelte.d.ts +1 -1
  73. package/esm/ui/link/Link.svelte +1 -1
  74. package/esm/ui/link/LinkPlus.svelte +9 -5
  75. package/esm/ui/link/LinkPlus.svelte.d.ts +5 -19
  76. package/esm/virtual/Customer.js +1 -2
  77. package/esm/virtual/UIEntity.js +9 -5
  78. package/package.json +11 -25
  79. package/esm/auth/AuthController.d.ts +0 -58
  80. package/esm/auth/AuthController.js +0 -114
  81. package/esm/auth/Entities.d.ts +0 -47
  82. package/esm/auth/Entities.js +0 -182
  83. package/esm/auth/README.md +0 -3
  84. package/esm/auth/index.d.ts +0 -5
  85. package/esm/auth/index.js +0 -5
  86. package/esm/auth/server/AuthController.server.d.ts +0 -58
  87. package/esm/auth/server/AuthController.server.js +0 -498
  88. package/esm/auth/server/handleAuth.d.ts +0 -4
  89. package/esm/auth/server/handleAuth.js +0 -142
  90. package/esm/auth/server/handleGuard.d.ts +0 -22
  91. package/esm/auth/server/handleGuard.js +0 -34
  92. package/esm/auth/server/helperDb.d.ts +0 -10
  93. package/esm/auth/server/helperDb.js +0 -56
  94. package/esm/auth/server/helperFirstly.d.ts +0 -1
  95. package/esm/auth/server/helperFirstly.js +0 -9
  96. package/esm/auth/server/helperOslo.d.ts +0 -7
  97. package/esm/auth/server/helperOslo.js +0 -24
  98. package/esm/auth/server/helperRemultServer.d.ts +0 -5
  99. package/esm/auth/server/helperRemultServer.js +0 -44
  100. package/esm/auth/server/helperRole.d.ts +0 -19
  101. package/esm/auth/server/helperRole.js +0 -57
  102. package/esm/auth/server/index.d.ts +0 -8
  103. package/esm/auth/server/index.js +0 -8
  104. package/esm/auth/server/module.d.ts +0 -300
  105. package/esm/auth/server/module.js +0 -230
  106. package/esm/auth/server/providers/github.d.ts +0 -33
  107. package/esm/auth/server/providers/github.js +0 -87
  108. package/esm/auth/server/providers/helperProvider.d.ts +0 -1
  109. package/esm/auth/server/providers/helperProvider.js +0 -25
  110. package/esm/auth/static/assets/Page-9Ytj29NS.d.ts +0 -2
  111. package/esm/auth/static/assets/Page-9Ytj29NS.js +0 -1
  112. package/esm/auth/static/assets/Page-BHW08QWz.css +0 -1
  113. package/esm/auth/static/assets/Page-C1pM-UDt.d.ts +0 -2
  114. package/esm/auth/static/assets/Page-C1pM-UDt.js +0 -20
  115. package/esm/auth/static/assets/Page-CPz6KCw_.d.ts +0 -2
  116. package/esm/auth/static/assets/Page-CPz6KCw_.js +0 -1
  117. package/esm/auth/static/assets/index-AoBb9Ds5.d.ts +0 -232
  118. package/esm/auth/static/assets/index-AoBb9Ds5.js +0 -2
  119. package/esm/auth/static/assets/index-DKWpA6v7.css +0 -4
  120. package/esm/auth/static/favicon.svg +0 -79
  121. package/esm/auth/static/index.html +0 -13
  122. package/esm/auth/types.d.ts +0 -73
  123. package/esm/auth/types.js +0 -1
  124. package/esm/svelte/FF_Display.svelte +0 -51
  125. package/esm/svelte/FF_Display.svelte.d.ts +0 -29
  126. package/esm/svelte/FF_Edit.svelte +0 -104
  127. package/esm/svelte/FF_Edit.svelte.d.ts +0 -32
  128. package/esm/svelte/FF_Error.svelte +0 -23
  129. package/esm/svelte/FF_Error.svelte.d.ts +0 -29
  130. package/esm/svelte/FF_Field.svelte +0 -62
  131. package/esm/svelte/FF_Field.svelte.d.ts +0 -29
  132. package/esm/svelte/FF_Hint.svelte +0 -21
  133. package/esm/svelte/FF_Hint.svelte.d.ts +0 -29
  134. package/esm/svelte/FF_Label.svelte +0 -23
  135. package/esm/svelte/FF_Label.svelte.d.ts +0 -29
@@ -1,4 +1,4 @@
1
- import type { SvelteComponent } from 'svelte';
1
+ import type { Component, SvelteComponent } from 'svelte';
2
2
  import { type ClassType, type EntityFilter, type FieldMetadata, type Repository } from 'remult';
3
3
  import type { UnArray } from '../utils/types.js';
4
4
  export type VisibilityMode = 'view' | 'edit' | 'hide';
@@ -13,6 +13,7 @@ type CellInternal<Entity> = {
13
13
  clipboardable?: boolean;
14
14
  clearable?: boolean;
15
15
  component?: new (...args: any[]) => SvelteComponent;
16
+ componentS5?: Component;
16
17
  props?: any;
17
18
  rowToProps?: (row: any) => any;
18
19
  };
@@ -98,9 +98,10 @@ export const buildWhere = (entity, defaultWhere, fields_filter, fields_search, o
98
98
  and.push(...buildSearchWhere(entity, fields_search, obj.search));
99
99
  }
100
100
  for (const field of fields_filter) {
101
- // if there is a value
102
- if (obj && obj[field.key]) {
103
- const rfi = getRelationFieldInfo(field);
101
+ const rfi = getRelationFieldInfo(field);
102
+ // For relation fields, allow null as valid filter value (filters for NULL in DB)
103
+ const hasValue = rfi?.type === 'toOne' ? obj && obj[field.key] !== undefined : obj && obj[field.key];
104
+ if (hasValue) {
104
105
  if (field.inputType === 'checkbox') {
105
106
  // @ts-ignore
106
107
  and.push({ [field.key]: obj[field.key] });
@@ -127,7 +128,7 @@ export const buildWhere = (entity, defaultWhere, fields_filter, fields_search, o
127
128
  }
128
129
  }
129
130
  else if (rfi?.type === 'toOne') {
130
- // @ts-ignore (setting the id of the relation)
131
+ // @ts-ignore (setting the id of the relation, null = filter for NULL)
131
132
  and.push({ [field.key]: obj[field.key] });
132
133
  }
133
134
  else {
@@ -18,20 +18,19 @@ import { default as Link } from '../ui/link/Link.svelte';
18
18
  import { default as LinkPlus } from '../ui/link/LinkPlus.svelte';
19
19
  import { default as Loading } from '../ui/Loading.svelte';
20
20
  import { default as Tooltip } from '../ui/Tooltip.svelte';
21
- import type { BaseEnum, BaseItem, FF_Icon } from './BaseEnum.js';
22
- import type { CellsInput as CellsInput_ForExport } from './cellsBuildor.js';
21
+ import type { BaseItem, BaseItemLight, FF_Icon } from './BaseEnum.js';
22
+ import type { CellsInput } from './cellsBuildor.js';
23
23
  import { FF_Role } from './common.js';
24
- import { storeItem } from './storeItem.js';
24
+ import { storeItem, type StoreItem } from './storeItem.js';
25
25
  import { storeList } from './storeList.js';
26
26
  export { Field, FormEditAction, Grid, Grid2, GridPaginate, GridPaginate2, FieldGroup, Icon, Link, LinkPlus, Loading, Button, Tooltip, DialogManagement, FieldContainer, SelectMelt, Clipboardable, };
27
27
  export type { BaseEnumOptions } from './BaseEnum.js';
28
- export type { BaseItem };
29
- export type BaseItemLight = Partial<BaseItem>;
28
+ export type { BaseItem, BaseItemLight };
30
29
  export type { DialogMetaDataInternal } from '../ui/dialog/dialog.js';
31
- export type CellsInput<entityType> = CellsInput_ForExport<entityType>;
30
+ export type { CellsInput };
32
31
  export type { Cell, VisibilityMode } from './cellsBuildor.js';
33
32
  export type { FF_FindOptions } from './storeList.js';
34
- export type StoreItem<T> = ReturnType<typeof storeItem<T>>;
33
+ export type { StoreItem };
35
34
  export type StoreList<T> = ReturnType<typeof storeList<T>>;
36
35
  export type { ResolvedType, UnArray, RecursivePartial } from '../utils/types.js';
37
36
  export { FF_Fields } from './FF_Fields.js';
@@ -68,16 +67,12 @@ declare module 'remult' {
68
67
  };
69
68
  default_select_if_one_item?: boolean;
70
69
  multiSelect?: boolean;
70
+ filterNobodyLabel?: string;
71
71
  skipForDefaultField?: boolean;
72
72
  }
73
73
  interface EntityOptions<entityType> {
74
74
  searchableFind?: (str: string) => FindOptionsBase<entityType>;
75
75
  displayValue?: (item: entityType) => BaseItem;
76
- permissionApiCrud?: BaseEnum[] | BaseEnum;
77
- permissionApiDelete?: BaseEnum[] | BaseEnum;
78
- permissionApiInsert?: BaseEnum[] | BaseEnum;
79
- permissionApiRead?: BaseEnum[] | BaseEnum;
80
- permissionApiUpdate?: BaseEnum[] | BaseEnum;
81
76
  changeLog?: false | ColumnDeciderArgs<entityType>;
82
77
  }
83
78
  }
@@ -1,27 +1,19 @@
1
- import type { ErrorInfo, FindOptions, Repository } from 'remult';
2
- type TheStoreItem<T> = {
3
- item: T | undefined;
4
- loading?: boolean;
5
- errors: ErrorInfo<T> | undefined;
6
- globalError?: string | undefined;
7
- };
8
- export declare const storeItem: <T>(repo: Repository<T>, initValues?: TheStoreItem<T>) => {
9
- subscribe: (this: void, run: import("svelte/store").Subscriber<TheStoreItem<T>>, invalidate?: () => void) => import("svelte/store").Unsubscriber;
1
+ import type { EntityFilter, ErrorInfo, FindOptions, Repository } from 'remult';
2
+ export interface StoreItem<T> {
3
+ subscribe: (run: (value: TheStoreItem<T>) => void) => () => void;
10
4
  create: (item: Partial<T>) => void;
11
5
  set: (newItem: TheStoreItem<T>) => void;
12
- /**
13
- * if you have keys, build the id with
14
- * ```ts
15
- * const id = repo.metadata.idMetadata.getId({a:1,b:2})
16
- * store.fetch(id)
17
- * ```
18
- */
19
- fetch: (id: Parameters<Repository<T>["findId"]>[0], options?: FindOptions<T>, onNewData?: (item: T) => void) => Promise<void>;
20
- /**
21
- * `.save()` will `update` or `insert` the current item.
22
- */
6
+ fetch: (idOrWhere: string | number | EntityFilter<T>, options?: FindOptions<T>, onNewData?: (item: T | undefined) => void) => Promise<void>;
23
7
  save: () => Promise<T | undefined>;
24
8
  delete: () => Promise<void>;
25
9
  onChange: (prop: keyof T, callback: (newValue: any, oldValue: any) => void) => void;
10
+ }
11
+ type TheStoreItem<T> = {
12
+ item: T | undefined;
13
+ loading?: boolean;
14
+ initLoading?: boolean;
15
+ errors: ErrorInfo<T> | undefined;
16
+ globalError?: string | undefined;
26
17
  };
18
+ export declare const storeItem: <T>(repo: Repository<T>, initValues?: TheStoreItem<T>) => StoreItem<T>;
27
19
  export {};
@@ -5,6 +5,7 @@ import { isError } from './helper.js';
5
5
  export const storeItem = (repo, initValues = {
6
6
  item: undefined,
7
7
  loading: true,
8
+ initLoading: true,
8
9
  errors: undefined,
9
10
  globalError: undefined,
10
11
  }) => {
@@ -30,38 +31,50 @@ export const storeItem = (repo, initValues = {
30
31
  internalStore.set({
31
32
  item: repo.create(item),
32
33
  loading: false,
34
+ initLoading: false,
33
35
  errors: {},
34
36
  globalError: undefined,
35
37
  });
36
38
  },
37
39
  // set: internalStore.set,
38
40
  set: (newItem) => {
39
- internalStore.update((s) => {
41
+ internalStore.update(() => {
40
42
  return { ...newItem };
41
43
  });
42
44
  },
43
45
  /**
44
- * if you have keys, build the id with
46
+ * Fetch by ID or WHERE clause
45
47
  * ```ts
48
+ * // By ID (string or number)
49
+ * store.fetch(123)
50
+ * store.fetch('abc')
51
+ *
52
+ * // By WHERE clause (object)
53
+ * store.fetch({ siteId: 123 })
54
+ *
55
+ * // With composite keys, build the id with
46
56
  * const id = repo.metadata.idMetadata.getId({a:1,b:2})
47
57
  * store.fetch(id)
48
58
  * ```
49
59
  */
50
- fetch: async (id, options, onNewData) => {
60
+ fetch: async (idOrWhere, options, onNewData) => {
51
61
  if (BROWSER) {
52
62
  internalStore.update((s) => ({ ...s, loading: true }));
53
63
  try {
54
- const item = await repo.findId(id, options);
55
- // lastOptions = options
64
+ const isId = typeof idOrWhere === 'string' || typeof idOrWhere === 'number';
65
+ const item = isId
66
+ ? await repo.findId(idOrWhere, options)
67
+ : await repo.findFirst(idOrWhere, options);
56
68
  internalStore.update((s) => ({
57
69
  ...s,
58
70
  loading: false,
71
+ initLoading: false,
59
72
  item: item ?? {},
60
73
  errors: undefined,
61
74
  globalError: undefined,
62
75
  }));
63
76
  if (onNewData) {
64
- onNewData(item ?? {});
77
+ onNewData(item ?? undefined);
65
78
  }
66
79
  }
67
80
  catch (error) {
@@ -69,6 +82,7 @@ export const storeItem = (repo, initValues = {
69
82
  internalStore.update((s) => ({
70
83
  ...s,
71
84
  loading: false,
85
+ initLoading: false,
72
86
  item: {},
73
87
  errors: {},
74
88
  // @ts-ignore
@@ -29,8 +29,14 @@ export type MailOptions = GlobalEasyOptions & {
29
29
  defaults?: DefaultOptions;
30
30
  };
31
31
  };
32
- declare let transporter: ReturnType<typeof typeNodemailer.createTransport>;
33
32
  export type SendMail = typeof sendMail;
33
+ export type SendMailResult = {
34
+ data: SMTPTransport.SentMessageInfo;
35
+ error?: undefined;
36
+ } | {
37
+ error: any;
38
+ data?: undefined;
39
+ };
34
40
  export declare const sendMail: (
35
41
  /** usefull for logs, it has NO impact on the mail itself */
36
42
  topic: string, easyOptions: GlobalEasyOptions & {
@@ -46,6 +52,6 @@ topic: string, easyOptions: GlobalEasyOptions & {
46
52
  }[];
47
53
  }, options?: {
48
54
  nodemailer?: MailOptions['nodemailer'];
49
- }) => ReturnType<typeof transporter.sendMail>;
55
+ }) => Promise<SendMailResult>;
50
56
  export declare const mail: (o?: MailOptions) => Module<unknown>;
51
57
  export {};
@@ -99,10 +99,10 @@ export const sendMail = async (topic, easyOptions, options) => {
99
99
  }
100
100
  try {
101
101
  if (!globalOptions?.nodemailer?.transport) {
102
- const info = await transporter.sendMail({ ...nodemailerOptions.defaults });
102
+ const data = await transporter.sendMail({ ...nodemailerOptions.defaults });
103
103
  log.error(`${magenta(`[${topic}]`)} - ⚠️ ${red(`mail not configured`)} ⚠️
104
104
  We are still nice and generated you an email preview link (the mail we not really sent):
105
- 👉 ${cyan(String(nodemailer.getTestMessageUrl(info)))}
105
+ 👉 ${cyan(String(nodemailer.getTestMessageUrl(data)))}
106
106
 
107
107
  To really send mails, check out the doc ${white(`https://firstly.fun/modules/mail`)}.
108
108
  `);
@@ -113,10 +113,10 @@ export const sendMail = async (topic, easyOptions, options) => {
113
113
  topic,
114
114
  metadata,
115
115
  });
116
- return info;
116
+ return { data };
117
117
  }
118
118
  else {
119
- const info = await transporter.sendMail({ ...nodemailerOptions.defaults });
119
+ const data = await transporter.sendMail({ ...nodemailerOptions.defaults });
120
120
  log.success(`${magenta(`[${topic}]`)} - Sent to ${typeof nodemailerOptions.defaults?.to === 'string' ? green(nodemailerOptions.defaults?.to) : nodemailerOptions.defaults?.to}`);
121
121
  await repo(mailEntities.Mail).insert({
122
122
  status: 'sent',
@@ -125,7 +125,7 @@ export const sendMail = async (topic, easyOptions, options) => {
125
125
  topic,
126
126
  metadata,
127
127
  });
128
- return info;
128
+ return { data };
129
129
  }
130
130
  }
131
131
  catch (error) {
@@ -138,15 +138,43 @@ ${cyan(JSON.stringify(globalOptions?.nodemailer?.transport, null, 2))}
138
138
  else {
139
139
  log.error(`${magenta(`[${topic}]`)} - Error`, error);
140
140
  }
141
+ // TODO
142
+ // Build comprehensive error info for JSON storage
143
+ const errorInfoJSON = {
144
+ message: error instanceof Error ? error.message : String(error),
145
+ name: error instanceof Error ? error.name : 'Unknown',
146
+ // stack: error instanceof Error ? error.stack : undefined,
147
+ code: error?.code,
148
+ errno: error?.errno,
149
+ syscall: error?.syscall,
150
+ hostname: error?.hostname,
151
+ port: error?.port,
152
+ address: error?.address,
153
+ response: error?.response,
154
+ responseCode: error?.responseCode,
155
+ command: error?.command,
156
+ // Capture any other enumerable properties
157
+ ...Object.getOwnPropertyNames(error).reduce((acc, key) => {
158
+ if (!['message', 'name', 'stack'].includes(key)) {
159
+ try {
160
+ acc[key] = error[key];
161
+ }
162
+ catch (e) {
163
+ // Ignore properties that can't be accessed
164
+ }
165
+ }
166
+ return acc;
167
+ }, {}),
168
+ };
141
169
  await repo(mailEntities.Mail).insert({
142
170
  status: 'error',
143
- errorInfo: JSON.stringify(error),
171
+ errorInfo: JSON.stringify(errorInfoJSON),
144
172
  to: JSON.stringify(to),
145
173
  html: easyOptionsToUse.saveHtml ? html : '',
146
174
  topic,
147
175
  metadata,
148
176
  });
149
- throw error;
177
+ return { error };
150
178
  }
151
179
  };
152
180
  const mailModule = new Module({
@@ -27,7 +27,7 @@ export declare class ModuleFF {
27
27
  modulesFF?: ModuleFF[];
28
28
  constructor(input: ModuleInput);
29
29
  }
30
- type Options = RemultServerOptions<RequestEvent<Partial<Record<string, string>>, string | null>> & {
30
+ type Options = RemultServerOptions<RequestEvent<Partial<Record<string, string>>, any>> & {
31
31
  /** @deprecated use `remult` modules instead */
32
32
  modulesFF?: ModuleFF[] | undefined;
33
33
  };
@@ -13,10 +13,11 @@
13
13
  cell: CellMetadata<valueType, entityType>
14
14
  r?: FF_Repo<entityType>
15
15
  class?: string
16
+ value?: valueType
17
+ error?: string
16
18
  }
17
19
 
18
- // eslint-disable-next-line svelte/no-unused-props
19
- let props: Props<valueType, entityType> = $props()
20
+ let { value = $bindable(), error, ...props }: Props<valueType, entityType> = $props()
20
21
 
21
22
  // let classes = $derived(getClasses('field', props.classes))
22
23
 
@@ -25,9 +26,6 @@
25
26
  let hint = $derived(props.cell.field?.options.ui?.hint ?? props.cell.ui?.hint)
26
27
  // @ts-ignore
27
28
  let ui = $derived(deepMerge(props.cell.field?.options.ui ?? {}, props.cell.ui ?? {}))
28
-
29
- let error = ''
30
- let value: any = $state('')
31
29
  </script>
32
30
 
33
31
  <!-- Snippets sections -->
@@ -4,11 +4,13 @@ interface Props<valueType = unknown, entityType = unknown> {
4
4
  cell: CellMetadata<valueType, entityType>;
5
5
  r?: FF_Repo<entityType>;
6
6
  class?: string;
7
+ value?: valueType;
8
+ error?: string;
7
9
  }
8
10
  declare function $$render<valueType = unknown, entityType = unknown>(): {
9
11
  props: Props<valueType, entityType>;
10
12
  exports: {};
11
- bindings: "";
13
+ bindings: "value";
12
14
  slots: {};
13
15
  events: {};
14
16
  };
@@ -16,7 +18,7 @@ declare class __sveltets_Render<valueType = unknown, entityType = unknown> {
16
18
  props(): ReturnType<typeof $$render<valueType, entityType>>['props'];
17
19
  events(): ReturnType<typeof $$render<valueType, entityType>>['events'];
18
20
  slots(): ReturnType<typeof $$render<valueType, entityType>>['slots'];
19
- bindings(): "";
21
+ bindings(): "value";
20
22
  exports(): {};
21
23
  }
22
24
  interface $$IsomorphicComponent {
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" generics="entityType = unknown">
2
2
  import { EntityError, getEntityRef } from 'remult'
3
3
 
4
- import { FF_Field, getClasses } from './'
4
+ import { FF_Cell, getClasses } from './'
5
5
  import type { FF_Repo, FieldGroup, FormTheme } from './'
6
6
 
7
7
  const default_uid = $props.id()
@@ -91,11 +91,10 @@
91
91
  {/if}
92
92
  <div data-ff-form-fields class="{classes?.fields} {group.class}">
93
93
  {#each group.fields as field}
94
- <FF_Field
95
- uid="{ToUse}-{field.key}"
96
- {field}
94
+ <FF_Cell
95
+ cell={{ field, mode: 'edit' }}
97
96
  bind:value={valuesToUse[field.key as keyof entityType]}
98
- error={errors[field.key]}
97
+ error={errors[field.key ?? '']}
99
98
  />
100
99
  {/each}
101
100
  </div>
@@ -3,7 +3,7 @@
3
3
 
4
4
  import Icon from '../ui/Icon.svelte'
5
5
  import { LibIcon_Add, LibIcon_Delete, LibIcon_Edit } from '../ui/LibIcon'
6
- import { dialog, FF_Display, FF_Repo, getClasses, type GridTheme } from './'
6
+ import { dialog, FF_Cell_Display, FF_Repo, getClasses, type GridTheme } from './'
7
7
 
8
8
  interface Props<entityType> {
9
9
  // uid?: string
@@ -100,7 +100,7 @@
100
100
  <tr data-ff-grid-row class={classes?.row}>
101
101
  {#each fields as f (f.key)}
102
102
  <td data-ff-grid-row-cell class="{classes?.rowCell} td-{f.inputType}">
103
- <FF_Display field={f} value={item[f.key as keyof entityType]}></FF_Display>
103
+ <FF_Cell_Display field={f} value={item[f.key as keyof entityType]}></FF_Cell_Display>
104
104
  </td>
105
105
  {/each}
106
106
  {#if showActions}
@@ -28,7 +28,7 @@
28
28
  {:else if layoutToUse.type === 'grid'}
29
29
  <FF_Grid {r}></FF_Grid>
30
30
  {:else if layoutToUse.type === 'tab'}
31
- <div role="tablist" class="tabs tabs-lifted">
31
+ <div role="tablist" class="tabs-lift tabs">
32
32
  {#each layoutToUse.groups ?? [] as group (group.key)}
33
33
  <input
34
34
  type="radio"
@@ -39,14 +39,14 @@
39
39
  onchange={() => (selectedThing = group.key)}
40
40
  aria-label={group.caption}
41
41
  />
42
- <div role="tabpanel" class="tab-content rounded-box border-base-300 bg-base-100 p-6">
42
+ <div role="tabpanel" class="tab-content border-base-300 bg-base-100 p-6">
43
43
  <FF_Form {r} groups={[group]} show={{ title: false }}></FF_Form>
44
44
  </div>
45
45
  {/each}
46
46
  </div>
47
47
  {:else if layoutToUse.type === 'accordion'}
48
48
  {#each layoutToUse.groups ?? [] as group (group.key)}
49
- <div class="collapse bg-base-100">
49
+ <div class="collapse-arrow collapse bg-base-100">
50
50
  <input
51
51
  type="radio"
52
52
  name="my-accordion"
@@ -62,6 +62,15 @@ export declare class FF_Repo<Entity, QueryOptions extends QueryOptionsHelper<Ent
62
62
  aggregates: ExtractAggregateResult<Entity, QueryOptions>;
63
63
  hasNextPage: boolean;
64
64
  } | undefined>;
65
+ /**
66
+ * Refresh query keeping current items count (BIG refresh)
67
+ * Useful after edit/delete to stay at current scroll position
68
+ */
69
+ queryRefresh(options: Pick<QueryOptionsHelper<Entity>, 'where' | 'orderBy'>): Promise<{
70
+ items: Entity[];
71
+ aggregates: ExtractAggregateResult<Entity, QueryOptions>;
72
+ hasNextPage: boolean;
73
+ } | undefined>;
65
74
  create(...args: Parameters<Repository<Entity>['create']>): Entity;
66
75
  delete(...args: Parameters<Repository<Entity>['delete']>): Promise<undefined>;
67
76
  getLayout: getLayoutStrict<Entity>;
@@ -132,6 +132,45 @@ export class FF_Repo {
132
132
  hasNextPage: this.hasNextPage,
133
133
  });
134
134
  }
135
+ /**
136
+ * Refresh query keeping current items count (BIG refresh)
137
+ * Useful after edit/delete to stay at current scroll position
138
+ */
139
+ async queryRefresh(options) {
140
+ const currentCount = this.items?.length ?? this.#queryOptions?.pageSize ?? 25;
141
+ this.loading = {
142
+ ...this.loading,
143
+ fetching: true,
144
+ init: this.items === undefined,
145
+ };
146
+ const { data: queryResult, error: queryResultError } = tryCatchSync(() => this.#repo.query({
147
+ ...this.#queryOptions,
148
+ ...options,
149
+ pageSize: currentCount,
150
+ aggregate: {
151
+ ...this.#queryOptions?.aggregate,
152
+ },
153
+ }));
154
+ if (queryResultError) {
155
+ this.globalError = queryResultError.message;
156
+ return this.loadingEnd();
157
+ }
158
+ const { data: paginator, error: paginatorError } = await tryCatch(queryResult.paginator());
159
+ if (paginatorError) {
160
+ this.globalError = paginatorError.message;
161
+ return this.loadingEnd();
162
+ }
163
+ this.#paginator = paginator;
164
+ this.items = this.#paginator.items;
165
+ // @ts-expect-error - We know the structure will match due to how we define the types
166
+ this.aggregates = this.#paginator.aggregates;
167
+ this.hasNextPage = this.#paginator.hasNextPage && this.aggregates.$count > this.items.length;
168
+ return this.loadingEnd({
169
+ items: this.items,
170
+ aggregates: this.aggregates,
171
+ hasNextPage: this.hasNextPage,
172
+ });
173
+ }
135
174
  create(...args) {
136
175
  this.item = this.#repo.create(...args);
137
176
  return this.item;
@@ -7,6 +7,7 @@ import { goto } from '$app/navigation';
7
7
  import { page } from '$app/state';
8
8
  import { debounce } from '../helpers/debounce.js';
9
9
  const CONFIG_DELIMITER = ';';
10
+ const NULL_URL_VALUE = '__null__';
10
11
  /**
11
12
  * SearchParams class for handling URL search parameters with Svelte 5 runes
12
13
  * Provides automatic binding and URL updates
@@ -257,6 +258,12 @@ export class SP {
257
258
  const urlKey = this.keyMap[propKey]; // Get the URL parameter key
258
259
  const paramValue = params.get(urlKey);
259
260
  if (paramValue !== null) {
261
+ // Decode null sentinel from URL
262
+ if (paramValue === NULL_URL_VALUE) {
263
+ this.paramValues[propKey] = null;
264
+ this.debouncedValues[propKey] = null;
265
+ continue;
266
+ }
260
267
  // If there is a decode function, always use it to get the proper object
261
268
  if (def.decode) {
262
269
  const decodedValue = def.decode(paramValue);
@@ -314,11 +321,16 @@ export class SP {
314
321
  }
315
322
  const params = new URLSearchParams(window.location.search);
316
323
  for (const [propKey, value] of Object.entries(this.debouncedValues)) {
317
- // Skip undefined or null values
318
- if (value === undefined || value === null) {
324
+ // Skip undefined values
325
+ if (value === undefined) {
319
326
  params.delete(this.keyMap[propKey]);
320
327
  continue;
321
328
  }
329
+ // Encode null as special URL value
330
+ if (value === null) {
331
+ params.set(this.keyMap[propKey], NULL_URL_VALUE);
332
+ continue;
333
+ }
322
334
  // Get the definition and URL key
323
335
  const def = this.config[propKey];
324
336
  if (!def)
@@ -16,7 +16,7 @@
16
16
  >
17
17
  {@html toShow.children}
18
18
  <svelte:fragment slot="actions">
19
- <Button class="text-white" on:click={() => dialog.close(toShow.id, { success: true })}
19
+ <Button class="text-white" onclick={() => dialog.close(toShow.id, { success: true })}
20
20
  >Confirmer</Button
21
21
  >
22
22
  </svelte:fragment>
@@ -29,10 +29,7 @@
29
29
  >
30
30
  {@html toShow.children}
31
31
  <svelte:fragment slot="actions">
32
- <Button
33
- class="btn-outline btn-error"
34
- on:click={() => dialog.close(toShow.id, { success: true })}
35
- >
32
+ <Button class="btn-error" onclick={() => dialog.close(toShow.id, { success: true })}>
36
33
  Confirmer
37
34
  </Button>
38
35
  </svelte:fragment>
@@ -91,8 +91,7 @@
91
91
  inset: 0;
92
92
  z-index: 40;
93
93
  background-color: var(--ff-dialog-overlay);
94
- -webkit-backdrop-filter: blur(2px);
95
- backdrop-filter: blur(2px);
94
+ backdrop-filter: blur(2px);
96
95
  }
97
96
 
98
97
  [data-ff-dialog-content] {
@@ -18,7 +18,7 @@ const createDialogManagement = () => {
18
18
  confirm: (topic, text, icon) => {
19
19
  const detail = {
20
20
  detail: {
21
- caption: 'Confirmez',
21
+ caption: 'A Confirmer',
22
22
  icon: { data: icon },
23
23
  },
24
24
  children: `
@@ -34,7 +34,7 @@ const createDialogManagement = () => {
34
34
  confirmDelete: (topic) => {
35
35
  const detail = {
36
36
  detail: {
37
- caption: 'Supprimer',
37
+ caption: 'A Supprimer',
38
38
  icon: { data: LibIcon_Delete },
39
39
  },
40
40
  children: topic
@@ -40,8 +40,8 @@ export const daisyTheme = {
40
40
  },
41
41
  edit: {
42
42
  checkbox: 'checkbox',
43
- input: 'input input-bordered',
44
- select: 'select select-bordered',
43
+ input: 'input',
44
+ select: 'select',
45
45
  },
46
46
  grid: {
47
47
  root: 'table',
@@ -1,18 +1,13 @@
1
1
  import type { CellMetadata, getLayout } from './customField';
2
+ import { default as FF_Cell_Display } from './FF_Cell_Display.svelte';
2
3
  import { default as FF_Cell } from './FF_Cell.svelte';
3
4
  import { default as FF_Config } from './FF_Config.svelte';
4
- import { default as FF_Display } from './FF_Display.svelte';
5
- import { default as FF_Edit } from './FF_Edit.svelte';
6
- import { default as FF_Error } from './FF_Error.svelte';
7
- import { default as FF_Field } from './FF_Field.svelte';
8
5
  import { default as FF_Form } from './FF_Form.svelte';
9
6
  import { default as FF_Grid } from './FF_Grid.svelte';
10
- import { default as FF_Hint } from './FF_Hint.svelte';
11
- import { default as FF_Label } from './FF_Label.svelte';
12
7
  import { default as FF_Layout } from './FF_Layout.svelte';
13
8
  export type { FieldTheme, FormTheme, GridTheme, Theme, EditTheme, DisplayTheme, } from './ff_Config.svelte.js';
14
9
  export { getDynamicCustomField, getTheme, setDynamicCustomField, setTheme, getClasses, daisyTheme, defaultTheme, emptyTheme, FF_Theme, } from './ff_Config.svelte.js';
15
- export { FF_Grid, FF_Form, FF_Field, FF_Edit, FF_Config, FF_Display, FF_Label, FF_Error, FF_Hint, FF_Layout, FF_Cell, };
10
+ export { FF_Grid, FF_Form, FF_Config, FF_Layout, FF_Cell, FF_Cell_Display };
16
11
  export type { DynamicCustomField, FieldGroup } from './customField';
17
12
  export { FF_Repo } from './FF_Repo.svelte.js';
18
13
  export { tryCatch, tryCatchSync } from './tryCatch';
@@ -1,16 +1,11 @@
1
+ import { default as FF_Cell_Display } from './FF_Cell_Display.svelte';
1
2
  import { default as FF_Cell } from './FF_Cell.svelte';
2
3
  import { default as FF_Config } from './FF_Config.svelte';
3
- import { default as FF_Display } from './FF_Display.svelte';
4
- import { default as FF_Edit } from './FF_Edit.svelte';
5
- import { default as FF_Error } from './FF_Error.svelte';
6
- import { default as FF_Field } from './FF_Field.svelte';
7
4
  import { default as FF_Form } from './FF_Form.svelte';
8
5
  import { default as FF_Grid } from './FF_Grid.svelte';
9
- import { default as FF_Hint } from './FF_Hint.svelte';
10
- import { default as FF_Label } from './FF_Label.svelte';
11
6
  import { default as FF_Layout } from './FF_Layout.svelte';
12
7
  export { getDynamicCustomField, getTheme, setDynamicCustomField, setTheme, getClasses, daisyTheme, defaultTheme, emptyTheme, FF_Theme, } from './ff_Config.svelte.js';
13
- export { FF_Grid, FF_Form, FF_Field, FF_Edit, FF_Config, FF_Display, FF_Label, FF_Error, FF_Hint, FF_Layout, FF_Cell, };
8
+ export { FF_Grid, FF_Form, FF_Config, FF_Layout, FF_Cell, FF_Cell_Display };
14
9
  export { FF_Repo } from './FF_Repo.svelte.js';
15
10
  export { tryCatch, tryCatchSync } from './tryCatch';
16
11
  export { overwriteOptions, deepMerge, isOfType } from './helpers';