@svadmin/lite 0.5.0 → 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 (66) hide show
  1. package/README.md +190 -9
  2. package/dist/compatibility.d.ts +23 -0
  3. package/dist/compatibility.js +95 -0
  4. package/dist/components/LiteDynamicFormList.svelte +169 -0
  5. package/dist/components/LiteDynamicFormList.svelte.d.ts +45 -0
  6. package/dist/components/LiteFilterBuilder.svelte +205 -0
  7. package/dist/components/LiteFilterBuilder.svelte.d.ts +19 -0
  8. package/dist/components/LiteShowField.svelte +52 -22
  9. package/dist/components/LiteShowField.svelte.d.ts +3 -2
  10. package/dist/components/LiteTable.svelte +89 -36
  11. package/dist/components/LiteTable.svelte.d.ts +3 -2
  12. package/dist/components/LiteTransfer.svelte +185 -0
  13. package/dist/components/LiteTransfer.svelte.d.ts +18 -0
  14. package/dist/components/compatibility/LiteCapabilityBoundary.svelte +51 -0
  15. package/dist/components/compatibility/LiteCapabilityBoundary.svelte.d.ts +13 -0
  16. package/dist/components/compatibility/LiteClipboardFallback.svelte +16 -0
  17. package/dist/components/compatibility/LiteClipboardFallback.svelte.d.ts +8 -0
  18. package/dist/components/compatibility/LiteComputeFallback.svelte +50 -0
  19. package/dist/components/compatibility/LiteComputeFallback.svelte.d.ts +14 -0
  20. package/dist/components/compatibility/LiteDirectoryUpload.svelte +44 -0
  21. package/dist/components/compatibility/LiteDirectoryUpload.svelte.d.ts +11 -0
  22. package/dist/components/compatibility/LiteOrderedList.svelte +47 -0
  23. package/dist/components/compatibility/LiteOrderedList.svelte.d.ts +14 -0
  24. package/dist/components/compatibility/LiteRealtimeStatus.svelte +43 -0
  25. package/dist/components/compatibility/LiteRealtimeStatus.svelte.d.ts +11 -0
  26. package/dist/components/compatibility/LiteVisualFallback.svelte +80 -0
  27. package/dist/components/compatibility/LiteVisualFallback.svelte.d.ts +18 -0
  28. package/dist/components/compatibility/index.d.ts +7 -0
  29. package/dist/components/compatibility/index.js +7 -0
  30. package/dist/components/fields/LiteAvatarField.svelte +127 -0
  31. package/dist/components/fields/LiteAvatarField.svelte.d.ts +21 -0
  32. package/dist/components/fields/LiteCascader.svelte +119 -0
  33. package/dist/components/fields/LiteCascader.svelte.d.ts +24 -0
  34. package/dist/components/fields/LiteCodeField.svelte +71 -0
  35. package/dist/components/fields/LiteCodeField.svelte.d.ts +16 -0
  36. package/dist/components/fields/LiteCopyField.svelte +74 -0
  37. package/dist/components/fields/LiteCopyField.svelte.d.ts +18 -0
  38. package/dist/components/fields/LiteCurrencyField.svelte +126 -0
  39. package/dist/components/fields/LiteCurrencyField.svelte.d.ts +21 -0
  40. package/dist/components/fields/LiteDateRangeField.svelte +147 -0
  41. package/dist/components/fields/LiteDateRangeField.svelte.d.ts +26 -0
  42. package/dist/components/fields/LitePercentField.svelte +124 -0
  43. package/dist/components/fields/LitePercentField.svelte.d.ts +17 -0
  44. package/dist/components/fields/LitePhoneField.svelte +81 -0
  45. package/dist/components/fields/LitePhoneField.svelte.d.ts +17 -0
  46. package/dist/components/fields/LiteRatingField.svelte +105 -0
  47. package/dist/components/fields/LiteRatingField.svelte.d.ts +16 -0
  48. package/dist/components/fields/LiteTreeSelect.svelte +143 -0
  49. package/dist/components/fields/LiteTreeSelect.svelte.d.ts +23 -0
  50. package/dist/components/fields/index.d.ts +12 -0
  51. package/dist/components/fields/index.js +10 -0
  52. package/dist/components/pages/LiteCreatePage.svelte +15 -6
  53. package/dist/components/pages/LiteCreatePage.svelte.d.ts +1 -1
  54. package/dist/components/pages/LiteEditPage.svelte +18 -9
  55. package/dist/components/pages/LiteEditPage.svelte.d.ts +1 -1
  56. package/dist/components/pages/LiteListPage.svelte +99 -27
  57. package/dist/components/pages/LiteListPage.svelte.d.ts +4 -2
  58. package/dist/components/pages/LiteShowPage.svelte +19 -10
  59. package/dist/components/pages/LiteShowPage.svelte.d.ts +1 -1
  60. package/dist/index.d.ts +10 -2
  61. package/dist/index.js +8 -1
  62. package/dist/lite.css +444 -0
  63. package/dist/schema-generator.js +15 -4
  64. package/dist/server-adapter.d.ts +18 -1
  65. package/dist/server-adapter.js +73 -16
  66. package/package.json +5 -4
package/dist/lite.css CHANGED
@@ -174,6 +174,21 @@ a:hover {
174
174
  background: #f8fafc;
175
175
  }
176
176
 
177
+ .lite-page-header {
178
+ display: flex;
179
+ align-items: flex-start;
180
+ justify-content: space-between;
181
+ margin-bottom: 20px;
182
+ }
183
+
184
+ .lite-page-title {
185
+ margin: 0;
186
+ font-size: 24px;
187
+ line-height: 1.25;
188
+ font-weight: 600;
189
+ color: #0f172a;
190
+ }
191
+
177
192
  .lite-header {
178
193
  display: flex;
179
194
  align-items: center;
@@ -189,6 +204,40 @@ a:hover {
189
204
  letter-spacing: -0.025em;
190
205
  }
191
206
 
207
+
208
+ /* ─── Breadcrumbs ──────────────────────────────────────────── */
209
+ .lite-breadcrumbs {
210
+ margin-bottom: 16px;
211
+ }
212
+ .lite-breadcrumbs ol {
213
+ display: flex;
214
+ align-items: center;
215
+ flex-wrap: wrap;
216
+ list-style: none;
217
+ padding: 0;
218
+ margin: 0;
219
+ }
220
+ .lite-breadcrumbs li {
221
+ display: flex;
222
+ align-items: center;
223
+ font-size: 13px;
224
+ color: #64748b;
225
+ }
226
+ .lite-breadcrumbs a {
227
+ color: #64748b;
228
+ }
229
+ .lite-breadcrumbs a:hover {
230
+ color: #0f172a;
231
+ }
232
+ .lite-breadcrumbs .current {
233
+ color: #0f172a;
234
+ font-weight: 500;
235
+ }
236
+ .lite-breadcrumb-sep {
237
+ margin: 0 8px;
238
+ color: #cbd5e1;
239
+ }
240
+
192
241
  /* ─── Cards ────────────────────────────────────────────────── */
193
242
  .lite-card {
194
243
  background: #fff;
@@ -747,6 +796,174 @@ textarea.lite-input {
747
796
  justify-content: flex-end;
748
797
  }
749
798
 
799
+ /* ─── Compatibility Fallbacks ─────────────────────────────── */
800
+ .lite-compatibility {
801
+ border: 1px solid #e2e8f0;
802
+ border-radius: 8px;
803
+ background: #fff;
804
+ margin-bottom: 16px;
805
+ }
806
+
807
+ .lite-compatibility-header,
808
+ .lite-section-header,
809
+ .lite-realtime-status {
810
+ display: flex;
811
+ align-items: flex-start;
812
+ justify-content: space-between;
813
+ }
814
+
815
+ .lite-compatibility-header {
816
+ padding: 14px 16px;
817
+ border-bottom: 1px solid #e2e8f0;
818
+ }
819
+
820
+ .lite-compatibility-header h2,
821
+ .lite-section-header h2,
822
+ .lite-ordered-list h2 {
823
+ margin: 0;
824
+ font-size: 15px;
825
+ font-weight: 600;
826
+ color: #0f172a;
827
+ }
828
+
829
+ .lite-compatibility-header p,
830
+ .lite-section-header p {
831
+ margin: 4px 0 0;
832
+ color: #64748b;
833
+ font-size: 12px;
834
+ }
835
+
836
+ .lite-compatibility-body {
837
+ padding: 16px;
838
+ }
839
+
840
+ .lite-visual-fallback {
841
+ padding: 0;
842
+ }
843
+
844
+ .lite-visual-fallback .lite-section-header {
845
+ padding: 14px 16px;
846
+ border-bottom: 1px solid #e2e8f0;
847
+ }
848
+
849
+ .lite-visual-snapshot {
850
+ padding: 16px;
851
+ border-bottom: 1px solid #e2e8f0;
852
+ text-align: center;
853
+ }
854
+
855
+ .lite-visual-snapshot img {
856
+ display: inline-block;
857
+ max-width: 100%;
858
+ height: auto;
859
+ }
860
+
861
+ .lite-table-scroll {
862
+ overflow-x: auto;
863
+ }
864
+
865
+ .lite-muted {
866
+ color: #64748b;
867
+ font-size: 12px;
868
+ }
869
+
870
+ .lite-realtime-status {
871
+ align-items: center;
872
+ padding: 10px 12px;
873
+ border: 1px solid #e2e8f0;
874
+ border-radius: 6px;
875
+ background: #fff;
876
+ }
877
+
878
+ .lite-realtime-status > div > * + * {
879
+ margin-left: 8px;
880
+ }
881
+
882
+ .lite-directory-upload {
883
+ border: 1px solid #e2e8f0;
884
+ border-radius: 8px;
885
+ padding: 16px;
886
+ background: #fff;
887
+ }
888
+
889
+ .lite-directory-upload legend {
890
+ padding: 0 6px;
891
+ font-size: 14px;
892
+ font-weight: 600;
893
+ }
894
+
895
+ .lite-ordered-list {
896
+ padding: 16px;
897
+ }
898
+
899
+ .lite-ordered-list ol {
900
+ margin: 12px 0 0;
901
+ padding-left: 24px;
902
+ }
903
+
904
+ .lite-ordered-list li {
905
+ padding: 10px 0 10px 4px;
906
+ border-bottom: 1px solid #f1f5f9;
907
+ }
908
+
909
+ .lite-ordered-list li > div:first-child {
910
+ margin-bottom: 8px;
911
+ }
912
+
913
+ .lite-ordered-list li span {
914
+ display: block;
915
+ margin-top: 2px;
916
+ color: #64748b;
917
+ font-size: 12px;
918
+ }
919
+
920
+ .lite-compute-fallback {
921
+ padding: 16px;
922
+ }
923
+
924
+ .lite-clipboard-fallback {
925
+ margin-bottom: 0;
926
+ }
927
+
928
+ .lite-dashboard-stats,
929
+ .lite-dashboard-split,
930
+ .lite-resource-links {
931
+ display: flex;
932
+ flex-wrap: wrap;
933
+ margin-left: -8px;
934
+ margin-right: -8px;
935
+ margin-bottom: 24px;
936
+ }
937
+
938
+ .lite-dashboard-stats > *,
939
+ .lite-dashboard-split > *,
940
+ .lite-resource-links > * {
941
+ margin-left: 8px;
942
+ margin-right: 8px;
943
+ margin-bottom: 16px;
944
+ }
945
+
946
+ .lite-dashboard-stats > * {
947
+ flex: 1 1 220px;
948
+ }
949
+
950
+ .lite-dashboard-split > *:first-child {
951
+ flex: 2 1 480px;
952
+ }
953
+
954
+ .lite-dashboard-split > *:last-child {
955
+ flex: 1 1 240px;
956
+ }
957
+
958
+ .lite-resource-links {
959
+ padding: 16px 8px 0;
960
+ margin-bottom: 0;
961
+ }
962
+
963
+ .lite-resource-links > * {
964
+ flex: 1 1 200px;
965
+ }
966
+
750
967
  /* ─── Login Page ───────────────────────────────────────────── */
751
968
  .lite-login-wrapper {
752
969
  display: flex;
@@ -849,6 +1066,211 @@ textarea.lite-input {
849
1066
  .lite-mt-0 { margin-top: 0; }
850
1067
  .lite-mb-lg { margin-bottom: 24px; }
851
1068
 
1069
+ /* ─── Avatar ───────────────────────────────────────────────── */
1070
+ .lite-avatar-wrapper {
1071
+ display: inline-flex;
1072
+ align-items: center;
1073
+ }
1074
+ .lite-avatar-container {
1075
+ position: relative;
1076
+ display: inline-block;
1077
+ vertical-align: middle;
1078
+ flex-shrink: 0;
1079
+ }
1080
+ .lite-avatar {
1081
+ display: inline-flex;
1082
+ align-items: center;
1083
+ justify-content: center;
1084
+ background: #f1f5f9;
1085
+ color: #64748b;
1086
+ font-weight: 500;
1087
+ overflow: hidden;
1088
+ vertical-align: middle;
1089
+ }
1090
+ .lite-avatar-circle { border-radius: 50%; }
1091
+ .lite-avatar-square { border-radius: 6px; }
1092
+
1093
+ .lite-avatar-xs { width: 20px; height: 20px; font-size: 10px; }
1094
+ .lite-avatar-sm { width: 28px; height: 28px; font-size: 12px; }
1095
+ .lite-avatar-default { width: 36px; height: 36px; font-size: 14px; }
1096
+ .lite-avatar-lg { width: 44px; height: 44px; font-size: 16px; }
1097
+
1098
+ .lite-avatar-img {
1099
+ width: 100%;
1100
+ height: 100%;
1101
+ object-fit: cover;
1102
+ }
1103
+ .lite-avatar-dot {
1104
+ position: absolute;
1105
+ bottom: 0;
1106
+ right: 0;
1107
+ border-radius: 50%;
1108
+ border: 2px solid #ffffff;
1109
+ }
1110
+ .lite-avatar-dot-xs { width: 6px; height: 6px; }
1111
+ .lite-avatar-dot-sm { width: 8px; height: 8px; }
1112
+ .lite-avatar-dot-default { width: 10px; height: 10px; }
1113
+ .lite-avatar-dot-lg { width: 12px; height: 12px; }
1114
+
1115
+ .lite-avatar-status-online, .lite-avatar-status-success { background: #22c55e; }
1116
+ .lite-avatar-status-busy, .lite-avatar-status-error { background: #ef4444; }
1117
+ .lite-avatar-status-warning, .lite-avatar-status-away { background: #f59e0b; }
1118
+ .lite-avatar-status-offline, .lite-avatar-status-neutral { background: #94a3b8; }
1119
+
1120
+ .lite-avatar-text-block {
1121
+ display: inline-flex;
1122
+ flex-direction: column;
1123
+ margin-left: 10px;
1124
+ text-align: left;
1125
+ vertical-align: middle;
1126
+ }
1127
+ .lite-avatar-name {
1128
+ font-size: 13px;
1129
+ font-weight: 500;
1130
+ color: #0f172a;
1131
+ line-height: 1.2;
1132
+ }
1133
+ .lite-avatar-subtitle {
1134
+ font-size: 11px;
1135
+ color: #64748b;
1136
+ line-height: 1.2;
1137
+ margin-top: 2px;
1138
+ }
1139
+
1140
+ /* ─── Code Block ───────────────────────────────────────────── */
1141
+ .lite-code-block {
1142
+ border: 1px solid #e2e8f0;
1143
+ border-radius: 6px;
1144
+ background: #f8fafc;
1145
+ overflow: hidden;
1146
+ margin: 4px 0;
1147
+ }
1148
+ .lite-code-header {
1149
+ padding: 4px 10px;
1150
+ background: #f1f5f9;
1151
+ border-bottom: 1px solid #e2e8f0;
1152
+ font-size: 11px;
1153
+ }
1154
+ .lite-code-pre {
1155
+ margin: 0;
1156
+ padding: 10px 12px;
1157
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
1158
+ font-size: 12px;
1159
+ line-height: 1.5;
1160
+ overflow-x: auto;
1161
+ overflow-y: auto;
1162
+ color: #0f172a;
1163
+ }
1164
+
1165
+ /* ─── Copy Field ───────────────────────────────────────────── */
1166
+ .lite-copy-field {
1167
+ display: inline-flex;
1168
+ align-items: center;
1169
+ }
1170
+ .lite-copy-text {
1171
+ font-size: 13px;
1172
+ color: #0f172a;
1173
+ }
1174
+
1175
+ /* ─── Currency & Numbers ───────────────────────────────────── */
1176
+ .lite-currency-field {
1177
+ display: inline-flex;
1178
+ align-items: center;
1179
+ font-size: 13px;
1180
+ }
1181
+
1182
+ /* ─── Date Range ───────────────────────────────────────────── */
1183
+ .lite-date-range {
1184
+ display: inline-flex;
1185
+ align-items: center;
1186
+ font-size: 13px;
1187
+ }
1188
+
1189
+ /* ─── Percentage & Progress ────────────────────────────────── */
1190
+ .lite-percent-container {
1191
+ display: inline-flex;
1192
+ align-items: center;
1193
+ }
1194
+ .lite-progress-track {
1195
+ display: inline-block;
1196
+ width: 64px;
1197
+ height: 6px;
1198
+ background: #e2e8f0;
1199
+ border-radius: 3px;
1200
+ overflow: hidden;
1201
+ margin-left: 8px;
1202
+ vertical-align: middle;
1203
+ }
1204
+ .lite-progress-fill {
1205
+ display: block;
1206
+ height: 100%;
1207
+ background: #4f46e5;
1208
+ border-radius: 3px;
1209
+ }
1210
+ .lite-progress-success { background: #22c55e; }
1211
+ .lite-progress-warning { background: #f59e0b; }
1212
+ .lite-progress-danger { background: #ef4444; }
1213
+ .lite-progress-info { background: #3b82f6; }
1214
+
1215
+ /* ─── Phone Field ──────────────────────────────────────────── */
1216
+ .lite-phone-container {
1217
+ display: inline-flex;
1218
+ align-items: center;
1219
+ font-size: 13px;
1220
+ }
1221
+ .lite-phone-icon {
1222
+ margin-right: 6px;
1223
+ font-size: 12px;
1224
+ }
1225
+ .lite-phone-link {
1226
+ color: #4f46e5;
1227
+ font-weight: 500;
1228
+ text-decoration: none;
1229
+ }
1230
+ .lite-phone-link:hover {
1231
+ text-decoration: underline;
1232
+ }
1233
+
1234
+ /* ─── Rating Field ─────────────────────────────────────────── */
1235
+ .lite-rating-container {
1236
+ display: inline-flex;
1237
+ align-items: center;
1238
+ }
1239
+ .lite-rating {
1240
+ display: inline-flex;
1241
+ align-items: center;
1242
+ }
1243
+ .lite-rating-star {
1244
+ display: inline-block;
1245
+ margin-right: 2px;
1246
+ line-height: 1;
1247
+ }
1248
+ .lite-rating-star-full, .lite-rating-star-half {
1249
+ color: #f59e0b;
1250
+ }
1251
+ .lite-rating-star-empty {
1252
+ color: #cbd5e1;
1253
+ }
1254
+ .lite-rating-sm { font-size: 12px; }
1255
+ .lite-rating-default { font-size: 14px; }
1256
+ .lite-rating-lg { font-size: 18px; }
1257
+ .lite-rating-value {
1258
+ margin-left: 6px;
1259
+ font-size: 13px;
1260
+ color: #0f172a;
1261
+ font-weight: 500;
1262
+ }
1263
+
1264
+ /* ─── Semantic Tone Utilities ──────────────────────────────── */
1265
+ .lite-font-mono {
1266
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
1267
+ }
1268
+ .lite-text-success { color: #16a34a; font-weight: 500; }
1269
+ .lite-text-warning { color: #d97706; font-weight: 500; }
1270
+ .lite-text-danger { color: #dc2626; font-weight: 500; }
1271
+ .lite-text-info { color: #2563eb; font-weight: 500; }
1272
+ .lite-text-primary { color: #4f46e5; font-weight: 500; }
1273
+
852
1274
  /* ─── Responsive ───────────────────────────────────────────── */
853
1275
  @media (max-width: 768px) {
854
1276
  .lite-wrapper {
@@ -860,6 +1282,11 @@ textarea.lite-input {
860
1282
  .lite-mobile-nav {
861
1283
  display: block;
862
1284
  }
1285
+ .lite-mobile-nav-links {
1286
+ max-height: 240px;
1287
+ overflow-y: auto;
1288
+ border-top: 1px solid #f1f5f9;
1289
+ }
863
1290
  .lite-main {
864
1291
  padding: 16px;
865
1292
  }
@@ -873,6 +1300,23 @@ textarea.lite-input {
873
1300
  .lite-header h1 {
874
1301
  margin-bottom: 12px;
875
1302
  }
1303
+ .lite-page-header {
1304
+ display: block;
1305
+ }
1306
+ .lite-page-header > .lite-btn {
1307
+ margin-top: 12px;
1308
+ }
1309
+ .lite-compatibility-header,
1310
+ .lite-section-header,
1311
+ .lite-realtime-status {
1312
+ display: block;
1313
+ }
1314
+ .lite-compatibility-header .lite-badge,
1315
+ .lite-section-header > .lite-badge,
1316
+ .lite-section-header > .lite-btn,
1317
+ .lite-realtime-status > .lite-btn {
1318
+ margin-top: 10px;
1319
+ }
876
1320
  }
877
1321
 
878
1322
  /* ─── Print ────────────────────────────────────────────────── */
@@ -49,6 +49,7 @@ function coerceAndValidateField(field, value, mode, withinArray = false, path =
49
49
  const issues = [];
50
50
  let coerced = value;
51
51
  switch (field.type) {
52
+ case "currency":
52
53
  case "number": {
53
54
  if (coerced === undefined || coerced === null || coerced === "") {
54
55
  coerced = undefined;
@@ -57,7 +58,7 @@ function coerceAndValidateField(field, value, mode, withinArray = false, path =
57
58
  }
58
59
  }
59
60
  else if (typeof coerced === "string") {
60
- const trimmed = coerced.trim();
61
+ const trimmed = coerced.trim().replace(/^[$€£¥₹]/u, "");
61
62
  if (trimmed === "") {
62
63
  coerced = undefined;
63
64
  if (field.required) {
@@ -251,7 +252,7 @@ function coerceAndValidateField(field, value, mode, withinArray = false, path =
251
252
  }
252
253
  case "phone": {
253
254
  if (typeof coerced === "string" && coerced.length > 0) {
254
- if (!/^[+\d\s()-]*$/.test(coerced)) {
255
+ if (!/^[+\d\s()-]*$/u.test(coerced)) {
255
256
  issues.push({ path, message: `${field.label} must be a valid phone number` });
256
257
  }
257
258
  }
@@ -309,7 +310,13 @@ function coerceAndValidateField(field, value, mode, withinArray = false, path =
309
310
  break;
310
311
  }
311
312
  // Check required
312
- if (field.type !== "number" && field.type !== "boolean" && field.type !== "array" && field.type !== "file" && field.type !== "image" && field.type !== "images") {
313
+ if (field.type !== "number"
314
+ && field.type !== "currency"
315
+ && field.type !== "boolean"
316
+ && field.type !== "array"
317
+ && field.type !== "file"
318
+ && field.type !== "image"
319
+ && field.type !== "images") {
313
320
  if (field.required && !hasRequiredValue(coerced)) {
314
321
  issues.push({ path, message: `${field.label} is required` });
315
322
  }
@@ -339,6 +346,7 @@ export function fieldsToTypeBoxSchema(fields, mode = "create") {
339
346
  activeFields.push(field);
340
347
  switch (field.type) {
341
348
  case "number":
349
+ case "currency":
342
350
  shape[field.key] = field.required ? Type.Number() : Type.Optional(Type.Number());
343
351
  break;
344
352
  case "boolean":
@@ -434,7 +442,9 @@ export const resourceToZodSchema = resourceToTypeBoxSchema;
434
442
  */
435
443
  export function fieldToInputType(field) {
436
444
  switch (field.type) {
437
- case "number": return "text";
445
+ case "number":
446
+ case "currency": return "text";
447
+ case "color": return "color";
438
448
  case "email": return "text";
439
449
  case "url": return "text";
440
450
  case "phone": return "tel";
@@ -464,6 +474,7 @@ export function fieldToPlaceholder(field) {
464
474
  case "url": return "https://example.com";
465
475
  case "phone": return "+1 (555) 000-0000";
466
476
  case "number": return "0";
477
+ case "currency": return "0.00";
467
478
  default: return "";
468
479
  }
469
480
  }
@@ -24,6 +24,7 @@ export interface ListLoaderResult {
24
24
  currentSort?: string;
25
25
  currentOrder?: 'asc' | 'desc';
26
26
  currentSearch?: string;
27
+ currentFilters?: Record<string, string>;
27
28
  resource: ResourceDefinition;
28
29
  }
29
30
  /**
@@ -157,11 +158,27 @@ export declare function createAuthActions(authProvider: AuthProvider): {
157
158
  * This helper does not imply that Svelte 5 or the consuming app supports IE11.
158
159
  */
159
160
  export declare function isLegacyBrowser(userAgent: string): boolean;
161
+ export interface LegacyRedirectOptions {
162
+ /** Route prefix that contains the standalone SSR Lite application. */
163
+ litePrefix?: string;
164
+ /** Optional route prefix occupied by the modern SPA, for example `/admin`. */
165
+ spaPrefix?: string;
166
+ /** Paths that must remain outside automatic legacy routing. */
167
+ exclude?: string[];
168
+ /** Override the SPA-to-Lite path mapping without changing SPA code. */
169
+ mapPath?: (pathname: string) => string;
170
+ status?: 302 | 307;
171
+ }
172
+ /**
173
+ * Computes a legacy redirect without importing or modifying the modern SPA.
174
+ * This pure helper can be used from non-SvelteKit server middleware.
175
+ */
176
+ export declare function getLegacyRedirectLocation(request: Pick<Request, 'method' | 'headers'>, url: URL, options?: string | LegacyRedirectOptions): string | undefined;
160
177
  /**
161
178
  * Creates an opt-in SvelteKit hook that redirects detected IE11 user agents.
162
179
  * Consumers remain responsible for their own transpilation and browser support.
163
180
  */
164
- export declare function createLegacyRedirectHook(litePrefix?: string): ({ event, resolve }: {
181
+ export declare function createLegacyRedirectHook(options?: string | LegacyRedirectOptions): ({ event, resolve }: {
165
182
  event: RequestEvent;
166
183
  resolve: (event: RequestEvent) => Promise<Response>;
167
184
  }) => Promise<Response>;
@@ -19,6 +19,16 @@ function listRequestState(url, resource) {
19
19
  : resource.defaultSort?.order ?? 'asc';
20
20
  return { page, pageSize, sort, order, search: url.searchParams.get('q') ?? undefined };
21
21
  }
22
+ function listRequestFilterValues(resource, url) {
23
+ const values = {};
24
+ for (const field of resource.fields) {
25
+ const raw = url.searchParams.get(`filter_${field.key}`) ?? (field.key !== "q" && field.key !== "sort" && field.key !== "order" && field.key !== "page" ? url.searchParams.get(field.key) : null);
26
+ if (raw != null && raw.trim() !== "") {
27
+ values[field.key] = raw.trim();
28
+ }
29
+ }
30
+ return values;
31
+ }
22
32
  function listRequestFilters(resource, url) {
23
33
  const filters = [];
24
34
  const search = url.searchParams.get('q') ?? undefined;
@@ -76,6 +86,7 @@ export function createListLoader(dp, resource) {
76
86
  currentSort: sort,
77
87
  currentOrder: order,
78
88
  currentSearch: search,
89
+ currentFilters: listRequestFilterValues(resource, url),
79
90
  resource,
80
91
  };
81
92
  };
@@ -343,28 +354,74 @@ export function createAuthActions(authProvider) {
343
354
  export function isLegacyBrowser(userAgent) {
344
355
  return /MSIE|Trident|rv:11/.test(userAgent);
345
356
  }
357
+ function normalizeRoutePrefix(prefix) {
358
+ const segments = prefix.split('/').filter(Boolean);
359
+ return segments.length > 0 ? `/${segments.join('/')}` : '/';
360
+ }
361
+ function pathWithinPrefix(pathname, prefix) {
362
+ return prefix === '/' || pathname === prefix || pathname.startsWith(`${prefix}/`);
363
+ }
364
+ function redirectOptions(options) {
365
+ const configured = typeof options === 'string' ? { litePrefix: options } : options;
366
+ return {
367
+ litePrefix: normalizeRoutePrefix(configured.litePrefix ?? '/lite'),
368
+ spaPrefix: normalizeRoutePrefix(configured.spaPrefix ?? '/'),
369
+ exclude: (configured.exclude ?? []).map(normalizeRoutePrefix),
370
+ mapPath: configured.mapPath,
371
+ status: configured.status ?? 302,
372
+ };
373
+ }
374
+ /**
375
+ * Computes a legacy redirect without importing or modifying the modern SPA.
376
+ * This pure helper can be used from non-SvelteKit server middleware.
377
+ */
378
+ export function getLegacyRedirectLocation(request, url, options = '/lite') {
379
+ const configured = redirectOptions(options);
380
+ const userAgent = request.headers.get('user-agent') ?? '';
381
+ const acceptsHtml = request.headers.get('accept')?.includes('text/html') === true;
382
+ const isDocumentRequest = (request.method === 'GET' || request.method === 'HEAD') && acceptsHtml;
383
+ if (!isLegacyBrowser(userAgent) || !isDocumentRequest)
384
+ return undefined;
385
+ if (pathWithinPrefix(url.pathname, configured.litePrefix))
386
+ return undefined;
387
+ if (configured.exclude.some((prefix) => pathWithinPrefix(url.pathname, prefix)))
388
+ return undefined;
389
+ if (!configured.mapPath
390
+ && configured.spaPrefix !== '/'
391
+ && !pathWithinPrefix(url.pathname, configured.spaPrefix))
392
+ return undefined;
393
+ let mappedPath;
394
+ if (configured.mapPath) {
395
+ mappedPath = configured.mapPath(url.pathname);
396
+ }
397
+ else if (configured.spaPrefix !== '/' && pathWithinPrefix(url.pathname, configured.spaPrefix)) {
398
+ mappedPath = url.pathname.slice(configured.spaPrefix.length) || '/';
399
+ }
400
+ else {
401
+ mappedPath = url.pathname;
402
+ }
403
+ const normalizedMappedPath = mappedPath.startsWith('/') ? mappedPath : `/${mappedPath}`;
404
+ const location = configured.litePrefix === '/'
405
+ ? normalizedMappedPath
406
+ : `${configured.litePrefix}${normalizedMappedPath === '/' ? '' : normalizedMappedPath}`;
407
+ return `${location}${url.search}`;
408
+ }
346
409
  /**
347
410
  * Creates an opt-in SvelteKit hook that redirects detected IE11 user agents.
348
411
  * Consumers remain responsible for their own transpilation and browser support.
349
412
  */
350
- export function createLegacyRedirectHook(litePrefix = '/lite') {
351
- const prefixSegments = litePrefix.split('/').filter(Boolean);
352
- const normalizedPrefix = prefixSegments.length > 0 ? `/${prefixSegments.join('/')}` : '/';
413
+ export function createLegacyRedirectHook(options = '/lite') {
414
+ const configured = redirectOptions(options);
353
415
  return async ({ event, resolve }) => {
354
- const ua = event.request.headers.get('user-agent') ?? '';
355
- const acceptsHtml = event.request.headers.get('accept')?.includes('text/html') === true;
356
- const isDocumentRequest = (event.request.method === 'GET' || event.request.method === 'HEAD')
357
- && acceptsHtml;
358
- const isWithinLite = normalizedPrefix === '/'
359
- || event.url.pathname === normalizedPrefix
360
- || event.url.pathname.startsWith(`${normalizedPrefix}/`);
361
- if (isLegacyBrowser(ua) && isDocumentRequest && !isWithinLite) {
362
- const targetPath = normalizedPrefix === '/'
363
- ? event.url.pathname
364
- : `${normalizedPrefix}${event.url.pathname}`;
416
+ const location = getLegacyRedirectLocation(event.request, event.url, configured);
417
+ if (location) {
365
418
  return new Response(null, {
366
- status: 302,
367
- headers: { Location: `${targetPath}${event.url.search}` },
419
+ status: configured.status,
420
+ headers: {
421
+ Location: location,
422
+ 'Cache-Control': 'private, no-store',
423
+ Vary: 'User-Agent',
424
+ },
368
425
  });
369
426
  }
370
427
  return resolve(event);