@sveltejs/kit 3.0.0-next.24 → 3.0.0-next.27
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/package.json +16 -10
- package/src/constants.js +6 -0
- package/src/core/adapt/builder.js +128 -33
- package/src/core/adapt/index.js +3 -0
- package/src/core/config/index.js +4 -7
- package/src/core/env.js +39 -14
- package/src/core/generate_manifest/index.js +42 -43
- package/src/core/postbuild/analyse.js +4 -4
- package/src/core/postbuild/fallback.js +1 -1
- package/src/core/postbuild/prerender.js +4 -4
- package/src/core/sync/create_manifest_data/index.js +0 -1
- package/src/core/sync/write_app_manifest.js +3 -2
- package/src/core/sync/write_server.js +3 -17
- package/src/core/utils.js +10 -0
- package/src/exports/adapter.js +22 -0
- package/src/exports/public.d.ts +101 -49
- package/src/exports/vite/build/index.js +1173 -0
- package/src/exports/vite/build/remote.js +4 -4
- package/src/exports/vite/build/service-worker.js +98 -0
- package/src/exports/vite/dev/generate_manifest.js +308 -0
- package/src/exports/vite/dev/index.js +40 -282
- package/src/exports/vite/index.js +156 -1717
- package/src/exports/vite/plugins/env-vars.js +53 -34
- package/src/exports/vite/plugins/guard.js +217 -0
- package/src/exports/vite/plugins/remote.js +237 -0
- package/src/exports/vite/preview/index.js +8 -8
- package/src/exports/vite/static_analysis/index.js +15 -15
- package/src/exports/vite/utils.js +98 -1
- package/src/runner.js +4 -3
- package/src/runtime/app/paths/server.js +2 -2
- package/src/runtime/app/server/index.js +3 -3
- package/src/runtime/app/server/public.d.ts +114 -57
- package/src/runtime/app/server/remote/query.js +2 -2
- package/src/runtime/app/server/remote/requested.js +31 -15
- package/src/runtime/app/state/client.svelte.js +3 -1
- package/src/runtime/client/client.js +43 -198
- package/src/runtime/client/fetcher.js +6 -6
- package/src/runtime/client/focus.js +120 -0
- package/src/runtime/client/remote-functions/command.svelte.js +20 -9
- package/src/runtime/client/remote-functions/form.svelte.js +12 -7
- package/src/runtime/client/remote-functions/query/instance.svelte.js +19 -5
- package/src/runtime/client/remote-functions/shared.svelte.js +22 -1
- package/src/runtime/client/scroll.js +39 -0
- package/src/runtime/client/utils.js +28 -0
- package/src/runtime/form-utils.js +254 -264
- package/src/runtime/server/data/index.js +14 -36
- package/src/runtime/server/errors.js +7 -10
- package/src/runtime/server/fetch.js +21 -25
- package/src/runtime/server/index.js +35 -31
- package/src/runtime/server/internal.js +34 -5
- package/src/runtime/server/page/actions.js +6 -8
- package/src/runtime/server/page/data_serializer.js +6 -10
- package/src/runtime/server/page/index.js +18 -28
- package/src/runtime/server/page/load_data.js +2 -2
- package/src/runtime/server/page/render.js +22 -40
- package/src/runtime/server/page/respond_with_error.js +10 -13
- package/src/runtime/server/page/server_routing.js +21 -27
- package/src/runtime/server/remote-functions.js +136 -136
- package/src/runtime/server/respond.js +66 -64
- package/src/runtime/server/state.js +2 -0
- package/src/runtime/server/utils.js +4 -11
- package/src/runtime/shared.js +9 -0
- package/src/runtime/utils.js +41 -0
- package/src/types/ambient-private.d.ts +1 -2
- package/src/types/global-private.d.ts +8 -0
- package/src/types/internal.d.ts +56 -17
- package/src/utils/filesystem.js +44 -28
- package/src/utils/streaming.js +5 -15
- package/src/version.js +1 -1
- package/types/index.d.ts +235 -93
- package/types/index.d.ts.map +9 -2
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { DEV } from 'esm-env';
|
|
5
5
|
import * as devalue from 'devalue';
|
|
6
|
-
import { text_decoder, text_encoder } from './utils.js';
|
|
6
|
+
import { stream_from_iterable, text_decoder, text_encoder } from './utils.js';
|
|
7
7
|
import { noop } from '../utils/functions.js';
|
|
8
8
|
import { SvelteKitError } from '@sveltejs/kit/internal';
|
|
9
9
|
|
|
@@ -26,6 +26,12 @@ export function set_nested_value(object, field, value) {
|
|
|
26
26
|
export function parse_form_key(form_id, key) {
|
|
27
27
|
const suffix = '/' + form_id;
|
|
28
28
|
let name = key;
|
|
29
|
+
let image_coordinate = '';
|
|
30
|
+
|
|
31
|
+
if (name.startsWith('i:') && (name.endsWith(suffix + '.x') || name.endsWith(suffix + '.y'))) {
|
|
32
|
+
image_coordinate = name[name.length - 1];
|
|
33
|
+
name = name.slice(0, -2);
|
|
34
|
+
}
|
|
29
35
|
|
|
30
36
|
if (!name.endsWith(suffix)) {
|
|
31
37
|
throw new Error(`Form contained a field that wasn't created with form.fields.as(...): ${name}`);
|
|
@@ -42,10 +48,14 @@ export function parse_form_key(form_id, key) {
|
|
|
42
48
|
} else if (name.startsWith('b:')) {
|
|
43
49
|
name = name.slice(2);
|
|
44
50
|
type = 'boolean';
|
|
51
|
+
} else if (name.startsWith('i:')) {
|
|
52
|
+
name = name.slice(2);
|
|
53
|
+
type = 'number';
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
const is_array = name.endsWith('[]');
|
|
48
57
|
if (is_array) name = name.slice(0, -2);
|
|
58
|
+
if (image_coordinate) name += '.' + image_coordinate;
|
|
49
59
|
|
|
50
60
|
return { name, type, is_array };
|
|
51
61
|
}
|
|
@@ -441,25 +451,17 @@ class LazyFile {
|
|
|
441
451
|
}
|
|
442
452
|
stream() {
|
|
443
453
|
const range = read_range(this.#get_chunk, this.#offset, this.size);
|
|
444
|
-
|
|
445
|
-
return
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
} else {
|
|
452
|
-
controller.close();
|
|
453
|
-
}
|
|
454
|
-
return;
|
|
454
|
+
const size = this.size;
|
|
455
|
+
return stream_from_iterable(
|
|
456
|
+
(async function* () {
|
|
457
|
+
let cursor = 0;
|
|
458
|
+
for await (const chunk of range) {
|
|
459
|
+
cursor += chunk.byteLength;
|
|
460
|
+
yield chunk;
|
|
455
461
|
}
|
|
456
|
-
cursor
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
controller.close();
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
});
|
|
462
|
+
if (cursor < size) throw new Error('incomplete file data');
|
|
463
|
+
})()
|
|
464
|
+
);
|
|
463
465
|
}
|
|
464
466
|
async text() {
|
|
465
467
|
return text_decoder.decode(await this.arrayBuffer());
|
|
@@ -473,7 +475,12 @@ const path_regex = /^[a-zA-Z_$]\w*(\.[a-zA-Z_$]\w*|\[\d+\])*$/;
|
|
|
473
475
|
*/
|
|
474
476
|
export function split_path(path) {
|
|
475
477
|
if (!path_regex.test(path)) {
|
|
476
|
-
throw new Error(
|
|
478
|
+
throw new Error(
|
|
479
|
+
`Invalid field name ${path}` +
|
|
480
|
+
(DEV
|
|
481
|
+
? ': field names are written in JS object notation, so keys that would need quoting are not supported. See https://svelte.dev/docs/kit/remote-functions#form-Fields'
|
|
482
|
+
: '')
|
|
483
|
+
);
|
|
477
484
|
}
|
|
478
485
|
|
|
479
486
|
return path.split(/\.|\[|\]/).filter(Boolean);
|
|
@@ -600,32 +607,51 @@ export function flatten_issues(issues) {
|
|
|
600
607
|
* @param {(string | number)[]} path
|
|
601
608
|
* @returns {any}
|
|
602
609
|
*/
|
|
603
|
-
|
|
604
610
|
export function deep_get(object, path) {
|
|
605
611
|
let current = object;
|
|
606
612
|
for (const key of path) {
|
|
607
|
-
if (current
|
|
608
|
-
return current;
|
|
609
|
-
}
|
|
613
|
+
if (current === null || typeof current !== 'object') return undefined;
|
|
610
614
|
current = current[key];
|
|
611
615
|
}
|
|
612
616
|
return current;
|
|
613
617
|
}
|
|
614
618
|
|
|
619
|
+
/** name prefixes that tell the server which type to coerce a submitted string to */
|
|
620
|
+
const type_prefixes = /** @type {Record<string, string | undefined>} */ ({
|
|
621
|
+
number: 'n:',
|
|
622
|
+
boolean: 'b:'
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* adds props; a function becomes a getter that is computed each time it is read
|
|
627
|
+
* @param {Record<string, any>} base_props
|
|
628
|
+
* @param {Record<string, unknown>} props
|
|
629
|
+
*/
|
|
630
|
+
function add_props(base_props, props) {
|
|
631
|
+
for (const prop in props) {
|
|
632
|
+
const value = props[prop];
|
|
633
|
+
if (typeof value === 'function') {
|
|
634
|
+
Object.defineProperty(base_props, prop, {
|
|
635
|
+
enumerable: true,
|
|
636
|
+
get: /** @type {() => unknown} */ (value)
|
|
637
|
+
});
|
|
638
|
+
} else {
|
|
639
|
+
base_props[prop] = value;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return base_props;
|
|
643
|
+
}
|
|
644
|
+
|
|
615
645
|
/**
|
|
616
|
-
*
|
|
617
|
-
* @param {string} field_type
|
|
646
|
+
* @param {string} type
|
|
618
647
|
* @param {boolean} is_array
|
|
619
648
|
* @param {unknown} input_value
|
|
620
649
|
*/
|
|
621
|
-
function get_type_prefix(
|
|
622
|
-
if (
|
|
623
|
-
if (
|
|
624
|
-
if (
|
|
625
|
-
|
|
626
|
-
if (input_type === 'number') return 'n:';
|
|
627
|
-
if (input_type === 'boolean') return 'b:';
|
|
628
|
-
}
|
|
650
|
+
function get_type_prefix(type, is_array, input_value) {
|
|
651
|
+
if (type === 'number' || type === 'range') return 'n:';
|
|
652
|
+
if (type === 'image') return 'i:';
|
|
653
|
+
if (type === 'checkbox' && !is_array) return 'b:';
|
|
654
|
+
if (type === 'hidden' || type === 'submit') return type_prefixes[typeof input_value] ?? '';
|
|
629
655
|
return '';
|
|
630
656
|
}
|
|
631
657
|
|
|
@@ -661,270 +687,234 @@ function deep_clone(value) {
|
|
|
661
687
|
return value;
|
|
662
688
|
}
|
|
663
689
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
690
|
+
const warned_sites = new Set();
|
|
691
|
+
|
|
692
|
+
// keyed by the stack, so every place that enumerates warns once with its call site
|
|
693
|
+
const warn_no_keys = () => {
|
|
694
|
+
const error = new Error(
|
|
695
|
+
'The properties of `form.fields` are virtual, so operators like `in` and `Object.keys` are meaningless. If you need the current value of a form field, use `form.fields.x.value()`'
|
|
696
|
+
);
|
|
697
|
+
if (warned_sites.has(error.stack)) return;
|
|
698
|
+
warned_sites.add(error.stack);
|
|
699
|
+
console.warn(error);
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
// fields are created as they are accessed, so there is nothing to enumerate
|
|
703
|
+
/** @type {ProxyHandler<object> | null} */
|
|
704
|
+
const dev_traps = DEV
|
|
705
|
+
? {
|
|
706
|
+
has(target, prop) {
|
|
707
|
+
if (typeof prop !== 'symbol') warn_no_keys();
|
|
708
|
+
return prop in target;
|
|
709
|
+
},
|
|
710
|
+
ownKeys(target) {
|
|
711
|
+
warn_no_keys();
|
|
712
|
+
return Reflect.ownKeys(target);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
: null;
|
|
716
|
+
|
|
717
|
+
/** @param {InternalRemoteFormIssue} issue */
|
|
718
|
+
const public_issue = (issue) => ({ path: issue.path, message: issue.message });
|
|
719
|
+
|
|
720
|
+
/** @typedef {{
|
|
667
721
|
* form_id: string,
|
|
668
722
|
* get: () => Record<string, any>,
|
|
669
723
|
* set: (path: (string | number)[], value: any) => void,
|
|
670
724
|
* get_issues: (path?: (string | number)[], all?: boolean) => Record<string, InternalRemoteFormIssue[]>,
|
|
671
725
|
* get_touched: () => Record<string, boolean>,
|
|
672
726
|
* get_dirty: () => Record<string, boolean>
|
|
673
|
-
* }}
|
|
674
|
-
* @param {any} target - Function or empty POJO
|
|
675
|
-
* @param {(string | number)[]} path - Current access path
|
|
676
|
-
* @returns {any} Proxy object with name(), value(), and issues() methods
|
|
677
|
-
*/
|
|
678
|
-
export function create_field_proxy(context, target = {}, path = []) {
|
|
679
|
-
const get_value = () => {
|
|
680
|
-
const value = deep_get(context.get(), path);
|
|
681
|
-
return deep_clone(value);
|
|
682
|
-
};
|
|
683
|
-
|
|
684
|
-
return new Proxy(target, {
|
|
685
|
-
get(target, prop) {
|
|
686
|
-
if (typeof prop === 'symbol') return target[prop];
|
|
687
|
-
|
|
688
|
-
// Handle array access like jobs[0]
|
|
689
|
-
if (/^\d+$/.test(prop)) {
|
|
690
|
-
return create_field_proxy(context, {}, [...path, parseInt(prop, 10)]);
|
|
691
|
-
}
|
|
727
|
+
* }} FieldContext */
|
|
692
728
|
|
|
729
|
+
/**
|
|
730
|
+
* @param {FieldContext} context
|
|
731
|
+
* @param {(string | number)[]} path
|
|
732
|
+
* @param {string} prop
|
|
733
|
+
* @returns {any} a method of the field at `path`, or undefined for a nested field
|
|
734
|
+
*/
|
|
735
|
+
function create_field_method(context, path, prop) {
|
|
736
|
+
switch (prop) {
|
|
737
|
+
case 'set':
|
|
738
|
+
return (/** @type {any} */ value) => {
|
|
739
|
+
context.set(path, value);
|
|
740
|
+
return value;
|
|
741
|
+
};
|
|
742
|
+
case 'value':
|
|
743
|
+
return () => deep_clone(deep_get(context.get(), path));
|
|
744
|
+
case 'issues':
|
|
745
|
+
case 'allIssues': {
|
|
746
|
+
const key = build_path_string(path);
|
|
747
|
+
const all = prop === 'allIssues';
|
|
748
|
+
|
|
749
|
+
return () => {
|
|
750
|
+
const issues = context.get_issues(path, all)[key === '' ? '$' : key];
|
|
751
|
+
if (all) return issues?.map(public_issue);
|
|
752
|
+
const own = issues?.filter((issue) => issue.name === key).map(public_issue);
|
|
753
|
+
return own?.length ? own : undefined;
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
case 'touched':
|
|
757
|
+
case 'dirty': {
|
|
693
758
|
const key = build_path_string(path);
|
|
694
|
-
const next = [...path, prop];
|
|
695
|
-
|
|
696
|
-
if (prop === 'set') {
|
|
697
|
-
const set_func = function (/** @type {any} */ newValue) {
|
|
698
|
-
context.set(path, newValue);
|
|
699
|
-
return newValue;
|
|
700
|
-
};
|
|
701
|
-
return create_field_proxy(context, set_func, next);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
if (prop === 'value') {
|
|
705
|
-
return create_field_proxy(context, get_value, next);
|
|
706
|
-
}
|
|
707
759
|
|
|
708
|
-
|
|
709
|
-
const
|
|
710
|
-
|
|
760
|
+
return () => {
|
|
761
|
+
const object = prop === 'dirty' ? context.get_dirty() : context.get_touched();
|
|
762
|
+
if (Object.hasOwn(object, key)) return true;
|
|
763
|
+
for (const candidate in object) {
|
|
764
|
+
if (!Object.hasOwn(object, candidate)) continue;
|
|
765
|
+
if (key === '') return true;
|
|
766
|
+
if (!candidate.startsWith(key)) continue;
|
|
767
|
+
const next = candidate[key.length];
|
|
768
|
+
if (next === '.' || next === '[') return true;
|
|
769
|
+
}
|
|
770
|
+
return false;
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
case 'as': {
|
|
774
|
+
const key = build_path_string(path);
|
|
711
775
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
776
|
+
/**
|
|
777
|
+
* the field's value, or `fallback` until the field has been edited
|
|
778
|
+
* (without a fallback there is nothing to suppress, so `dirty` is not read)
|
|
779
|
+
* @param {unknown} [fallback]
|
|
780
|
+
*/
|
|
781
|
+
const read = (fallback) =>
|
|
782
|
+
deep_get(context.get(), path) ??
|
|
783
|
+
(fallback !== undefined && Object.hasOwn(context.get_dirty(), key) ? undefined : fallback);
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* @param {string} type
|
|
787
|
+
* @param {unknown} [input_value]
|
|
788
|
+
* @param {boolean} [checked]
|
|
789
|
+
*/
|
|
790
|
+
return (type, input_value, checked) => {
|
|
791
|
+
const is_array =
|
|
792
|
+
type === 'file multiple' ||
|
|
793
|
+
type === 'select multiple' ||
|
|
794
|
+
(type === 'checkbox' && typeof input_value === 'string');
|
|
795
|
+
|
|
796
|
+
const type_prefix = get_type_prefix(type, is_array, input_value);
|
|
797
|
+
|
|
798
|
+
// Base properties for all input types
|
|
799
|
+
/** @type {Record<string, any>} */
|
|
800
|
+
const base_props = {
|
|
801
|
+
name: type_prefix + key + (is_array ? '[]' : '') + '/' + context.form_id,
|
|
802
|
+
get 'aria-invalid'() {
|
|
803
|
+
const issues = context.get_issues();
|
|
804
|
+
return key in issues ? 'true' : undefined;
|
|
717
805
|
}
|
|
718
|
-
|
|
719
|
-
const issues = all_issues
|
|
720
|
-
?.filter((issue) => issue.name === key)
|
|
721
|
-
?.map((issue) => ({
|
|
722
|
-
path: issue.path,
|
|
723
|
-
message: issue.message
|
|
724
|
-
}));
|
|
725
|
-
|
|
726
|
-
return issues?.length ? issues : undefined;
|
|
727
806
|
};
|
|
728
807
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const fn = () => {
|
|
734
|
-
const object = prop === 'dirty' ? context.get_dirty() : context.get_touched();
|
|
735
|
-
|
|
736
|
-
if (key === '') {
|
|
737
|
-
return Object.keys(object).length > 0;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
if (Object.hasOwn(object, key)) {
|
|
741
|
-
return true;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
for (const candidate in object) {
|
|
745
|
-
if (!Object.hasOwn(object, candidate)) continue;
|
|
746
|
-
if (!candidate.startsWith(key)) continue;
|
|
808
|
+
// Add type attribute only for non-text inputs and non-select elements
|
|
809
|
+
if (type !== 'text' && type !== 'select' && type !== 'select multiple') {
|
|
810
|
+
base_props.type = type === 'file multiple' ? 'file' : type;
|
|
811
|
+
}
|
|
747
812
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
813
|
+
// Handle submit and hidden inputs
|
|
814
|
+
if (type === 'submit' || type === 'hidden') {
|
|
815
|
+
if (DEV) {
|
|
816
|
+
if (input_value === null || input_value === undefined) {
|
|
817
|
+
throw new Error(`\`${type}\` inputs must have a value`);
|
|
751
818
|
}
|
|
752
819
|
}
|
|
753
820
|
|
|
754
|
-
return
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
if (prop === 'as') {
|
|
761
|
-
/**
|
|
762
|
-
* @param {string} type
|
|
763
|
-
* @param {unknown} [input_value]
|
|
764
|
-
*/
|
|
765
|
-
const as_func = (type, input_value) => {
|
|
766
|
-
const is_array =
|
|
767
|
-
type === 'file multiple' ||
|
|
768
|
-
type === 'select multiple' ||
|
|
769
|
-
(type === 'checkbox' && typeof input_value === 'string');
|
|
770
|
-
|
|
771
|
-
const type_prefix = get_type_prefix(type, is_array, input_value);
|
|
772
|
-
|
|
773
|
-
// Base properties for all input types
|
|
774
|
-
/** @type {Record<string, any>} */
|
|
775
|
-
const base_props = {
|
|
776
|
-
name: type_prefix + key + (is_array ? '[]' : '') + '/' + context.form_id,
|
|
777
|
-
get 'aria-invalid'() {
|
|
778
|
-
const issues = context.get_issues();
|
|
779
|
-
return key in issues ? 'true' : undefined;
|
|
780
|
-
}
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
// Add type attribute only for non-text inputs and non-select elements
|
|
784
|
-
if (type !== 'text' && type !== 'select' && type !== 'select multiple') {
|
|
785
|
-
base_props.type = type === 'file multiple' ? 'file' : type;
|
|
786
|
-
}
|
|
821
|
+
return add_props(base_props, {
|
|
822
|
+
value: typeof input_value === 'boolean' ? (input_value ? 'on' : 'off') : input_value
|
|
823
|
+
});
|
|
824
|
+
}
|
|
787
825
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
826
|
+
// Handle select inputs
|
|
827
|
+
if (type === 'select' || type === 'select multiple') {
|
|
828
|
+
return add_props(base_props, {
|
|
829
|
+
multiple: is_array,
|
|
830
|
+
value: () => {
|
|
831
|
+
const value = read(input_value);
|
|
832
|
+
// copied, so the state array can't be edited through the props
|
|
833
|
+
return Array.isArray(value) ? [...value] : value;
|
|
794
834
|
}
|
|
835
|
+
});
|
|
836
|
+
}
|
|
795
837
|
|
|
796
|
-
|
|
797
|
-
|
|
838
|
+
// Handle checkbox inputs
|
|
839
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
840
|
+
// radio and checkbox array inputs take their option as the second argument
|
|
841
|
+
// and whether it is checked as the third, a single checkbox only the latter
|
|
842
|
+
const has_option = type === 'radio' || is_array;
|
|
798
843
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
844
|
+
if (DEV && has_option && !input_value) {
|
|
845
|
+
throw new Error(
|
|
846
|
+
`${type === 'radio' ? 'Radio' : 'Checkbox array'} inputs must have a value`
|
|
847
|
+
);
|
|
802
848
|
}
|
|
803
849
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
value: {
|
|
809
|
-
enumerable: true,
|
|
810
|
-
get() {
|
|
811
|
-
return get_value() ?? input_value;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
});
|
|
850
|
+
if (has_option) {
|
|
851
|
+
base_props.value = input_value ?? 'on';
|
|
852
|
+
} else {
|
|
853
|
+
checked = /** @type {boolean | undefined} */ (input_value);
|
|
815
854
|
}
|
|
816
855
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
856
|
+
return add_props(base_props, {
|
|
857
|
+
defaultChecked: checked,
|
|
858
|
+
checked: () => {
|
|
859
|
+
const value = read();
|
|
860
|
+
if (value == null) return read(checked);
|
|
861
|
+
if (type === 'radio') return value === input_value;
|
|
862
|
+
if (is_array) return /** @type {unknown[]} */ (value).includes(input_value);
|
|
863
|
+
return value;
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
}
|
|
823
867
|
|
|
824
|
-
|
|
825
|
-
|
|
868
|
+
// Handle file inputs
|
|
869
|
+
if (type === 'file' || type === 'file multiple') {
|
|
870
|
+
return add_props(base_props, {
|
|
871
|
+
multiple: is_array,
|
|
872
|
+
files: () => {
|
|
873
|
+
const value = read();
|
|
874
|
+
const files = value instanceof File ? [value] : value;
|
|
875
|
+
if (!Array.isArray(files) || !files.every((f) => f instanceof File)) return null;
|
|
876
|
+
|
|
877
|
+
// a FileList-like object where DataTransfer does not exist
|
|
878
|
+
if (typeof DataTransfer === 'undefined') {
|
|
879
|
+
return Object.assign({ length: files.length }, files);
|
|
826
880
|
}
|
|
827
|
-
}
|
|
828
881
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
enumerable: true,
|
|
833
|
-
get() {
|
|
834
|
-
return input_value;
|
|
835
|
-
}
|
|
836
|
-
},
|
|
837
|
-
checked: {
|
|
838
|
-
enumerable: true,
|
|
839
|
-
get() {
|
|
840
|
-
return get_value() ?? input_value;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
});
|
|
882
|
+
const transfer = new DataTransfer();
|
|
883
|
+
for (const file of files) transfer.items.add(file);
|
|
884
|
+
return transfer.files;
|
|
844
885
|
}
|
|
886
|
+
});
|
|
887
|
+
}
|
|
845
888
|
|
|
846
|
-
|
|
847
|
-
value: { value: input_value ?? 'on', enumerable: true },
|
|
848
|
-
checked: {
|
|
849
|
-
enumerable: true,
|
|
850
|
-
get() {
|
|
851
|
-
const value = get_value();
|
|
852
|
-
|
|
853
|
-
if (type === 'radio') {
|
|
854
|
-
return value === input_value;
|
|
855
|
-
}
|
|
889
|
+
if (type === 'image') return base_props;
|
|
856
890
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
multiple: { value: is_array, enumerable: true },
|
|
867
|
-
files: {
|
|
868
|
-
enumerable: true,
|
|
869
|
-
get() {
|
|
870
|
-
const value = get_value();
|
|
871
|
-
|
|
872
|
-
// Convert File/File[] to FileList-like object
|
|
873
|
-
if (value instanceof File) {
|
|
874
|
-
// In browsers, we can create a proper FileList using DataTransfer
|
|
875
|
-
if (typeof DataTransfer !== 'undefined') {
|
|
876
|
-
const fileList = new DataTransfer();
|
|
877
|
-
fileList.items.add(value);
|
|
878
|
-
return fileList.files;
|
|
879
|
-
}
|
|
880
|
-
// Fallback for environments without DataTransfer
|
|
881
|
-
return { 0: value, length: 1 };
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
if (Array.isArray(value) && value.every((f) => f instanceof File)) {
|
|
885
|
-
if (typeof DataTransfer !== 'undefined') {
|
|
886
|
-
const fileList = new DataTransfer();
|
|
887
|
-
value.forEach((file) => fileList.items.add(file));
|
|
888
|
-
return fileList.files;
|
|
889
|
-
}
|
|
890
|
-
// Fallback for environments without DataTransfer
|
|
891
|
-
/** @type {any} */
|
|
892
|
-
const fileListLike = { length: value.length };
|
|
893
|
-
value.forEach((file, index) => {
|
|
894
|
-
fileListLike[index] = file;
|
|
895
|
-
});
|
|
896
|
-
return fileListLike;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
return null;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
});
|
|
903
|
-
}
|
|
891
|
+
// Handle all other input types (text, number, etc.)
|
|
892
|
+
return add_props(base_props, {
|
|
893
|
+
defaultValue: input_value,
|
|
894
|
+
value: () => String(read(input_value) ?? '')
|
|
895
|
+
});
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
904
900
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
return value != null ? String(value) : '';
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
});
|
|
921
|
-
};
|
|
901
|
+
/**
|
|
902
|
+
* Creates a proxy-based field accessor for form data
|
|
903
|
+
* @param {FieldContext} context
|
|
904
|
+
* @param {any} target - Function or empty POJO
|
|
905
|
+
* @param {(string | number)[]} path - Current access path
|
|
906
|
+
* @returns {any} Proxy object with name(), value(), and issues() methods
|
|
907
|
+
*/
|
|
908
|
+
export function create_field_proxy(context, target = {}, path = []) {
|
|
909
|
+
return new Proxy(target, {
|
|
910
|
+
...dev_traps,
|
|
911
|
+
get(target, prop) {
|
|
912
|
+
if (typeof prop === 'symbol') return target[prop];
|
|
922
913
|
|
|
923
|
-
|
|
924
|
-
|
|
914
|
+
// array access like jobs[0]
|
|
915
|
+
const next = [...path, /^\d+$/.test(prop) ? parseInt(prop, 10) : prop];
|
|
925
916
|
|
|
926
|
-
|
|
927
|
-
return create_field_proxy(context, {}, next);
|
|
917
|
+
return create_field_proxy(context, create_field_method(context, path, prop), next);
|
|
928
918
|
}
|
|
929
919
|
});
|
|
930
920
|
}
|