Consolidate Soma file upload runtime

active-uix
dev 5 months ago
parent 3097a209f0
commit e1df915add

@ -9,7 +9,7 @@ import { FieldProvider } from '../field/field-provider.svelte';
import type { FileRejection, FileRejectionReason } from './types';
import { fileUploadMorfo } from '../../../morfo/components/file-upload';
import type { SomaRuntimePart } from '../../runtime.svelte';
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
// ── Helpers ──────────────────────────────────────────────────────────────────
@ -65,6 +65,8 @@ interface FileUploadOpts
export class FileUploadProvider {
readonly opts: FileUploadOpts;
readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
readonly runtime: SomaRuntime;
static readonly ctx = context<FileUploadProvider>('FileUpload');
static get(): FileUploadProvider | undefined {
return this.ctx.getOr(undefined) as FileUploadProvider | undefined;
@ -77,7 +79,6 @@ export class FileUploadProvider {
return new FileUploadProvider(opts);
}
readonly soma = Soma.get();
readonly field = FieldProvider.get();
/** Set by HiddenInput so Dropzone/Trigger/Label can target it. */
@ -87,14 +88,14 @@ export class FileUploadProvider {
private constructor(opts: FileUploadOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: FileUploadProvider.ctx
});
this.soma = Soma.require();
this.runtime = this.soma.runtime(fileUploadMorfo, {});
this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: FileUploadProvider.ctx
});
// When embedded inside a Field, register the hidden input id so
// Field.Label `for=` targets the file input and Field flags propagate.
@ -238,10 +239,12 @@ export class FileUploadLabelProvider {
private constructor(opts: FileUploadLabelOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('label', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FileUploadProvider.require();
this.runtimePart = this.provider.runtime.part('label', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly props = $derived.by(() =>
@ -269,10 +272,12 @@ export class FileUploadDropzoneProvider {
private constructor(opts: FileUploadDropzoneOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('dropzone', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FileUploadProvider.require();
this.runtimePart = this.provider.runtime.part('dropzone', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly onclick = () => {
@ -325,7 +330,7 @@ export class FileUploadDropzoneProvider {
...this.runtimePart.props,
role: 'button' as const,
tabindex: this.provider.isDisabled ? -1 : 0,
'aria-label': this.provider.soma?.langs.ts(FILE_UPLOAD_LANGS.DROPZONE),
'aria-label': this.provider.soma.langs.ts(FILE_UPLOAD_LANGS.DROPZONE),
'aria-disabled': boolToStr(this.provider.isDisabled),
'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled),
'data-dragging': boolToEmptyStrOrUndef(this.dragging),
@ -355,10 +360,12 @@ export class FileUploadTriggerProvider {
private constructor(opts: FileUploadTriggerOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('trigger', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FileUploadProvider.require();
this.runtimePart = this.provider.runtime.part('trigger', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly onclick = (e: SomaMouseEvent<HTMLButtonElement>) => {
@ -372,7 +379,7 @@ export class FileUploadTriggerProvider {
this.runtimePart.assert({
...this.runtimePart.props,
type: 'button' as const,
'aria-label': this.provider.soma?.langs.ts(FILE_UPLOAD_LANGS.TRIGGER),
'aria-label': this.provider.soma.langs.ts(FILE_UPLOAD_LANGS.TRIGGER),
'aria-disabled': boolToStr(this.provider.isDisabled),
disabled: this.provider.isDisabled || undefined,
'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled),
@ -400,17 +407,15 @@ export class FileUploadHiddenInputProvider {
// child→parent reactive edge the old `$effect` created was pure
// overhead.
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('hidden-input', {
id: opts.id,
ref: opts.ref,
owner: this,
onRefChange: (el) => {
this.provider.hiddenInputRef.current = el as HTMLInputElement | null;
}
});
this.provider = FileUploadProvider.require();
this.runtimePart = this.provider.runtime.part('hidden-input', {
id: opts.id,
ref: opts.ref,
owner: this,
onRefChange: (el) => {
this.provider.hiddenInputRef.current = el as HTMLInputElement | null;
}
});
this.provider.hiddenInputId.current = opts.id.current;
}
@ -454,17 +459,19 @@ export class FileUploadFileListProvider {
private constructor(opts: FileUploadFileListOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('file-list', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FileUploadProvider.require();
this.runtimePart = this.provider.runtime.part('file-list', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,
role: 'list' as const,
'aria-label': this.provider.soma?.langs.ts(FILE_UPLOAD_LANGS.LIST)
'aria-label': this.provider.soma.langs.ts(FILE_UPLOAD_LANGS.LIST)
} as const)
);
}
@ -492,15 +499,13 @@ export class FileUploadItemProvider {
private constructor(opts: FileUploadItemOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('item', {
id: opts.id,
ref: opts.ref,
owner: this,
context: FileUploadItemProvider.ctx
});
this.provider = FileUploadProvider.require();
this.runtimePart = this.provider.runtime.part('item', {
id: opts.id,
ref: opts.ref,
owner: this,
context: FileUploadItemProvider.ctx
});
}
readonly snippetProps = $derived.by(() => ({
@ -533,10 +538,12 @@ export class FileUploadItemNameProvider {
private constructor(opts: FileUploadItemNameOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('item-name', { id: opts.id, ref: opts.ref, owner: this });
this.item = FileUploadItemProvider.require();
this.runtimePart = this.item.provider.runtime.part('item-name', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly defaultContent = $derived.by(() => this.item.opts.file.current.name);
@ -561,10 +568,12 @@ export class FileUploadItemSizeProvider {
private constructor(opts: FileUploadItemSizeOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('item-size', { id: opts.id, ref: opts.ref, owner: this });
this.item = FileUploadItemProvider.require();
this.runtimePart = this.item.provider.runtime.part('item-size', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly defaultContent = $derived.by(() => formatBytes(this.item.opts.file.current.size));
@ -592,10 +601,12 @@ export class FileUploadItemPreviewProvider {
private constructor(opts: FileUploadItemPreviewOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('item-preview', { id: opts.id, ref: opts.ref, owner: this });
this.item = FileUploadItemProvider.require();
this.runtimePart = this.item.provider.runtime.part('item-preview', {
id: opts.id,
ref: opts.ref,
owner: this
});
$effect(() => {
if (!this.matches) {
@ -642,10 +653,12 @@ export class FileUploadItemRemoveProvider {
private constructor(opts: FileUploadItemRemoveOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('item-remove', { id: opts.id, ref: opts.ref, owner: this });
this.item = FileUploadItemProvider.require();
this.runtimePart = this.item.provider.runtime.part('item-remove', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly onclick = (_e: SomaMouseEvent<HTMLButtonElement>) => {
@ -659,7 +672,7 @@ export class FileUploadItemRemoveProvider {
type: 'button' as const,
disabled: this.item.provider.isDisabled || undefined,
'aria-label': [
this.item.provider.soma?.langs.ts(FILE_UPLOAD_LANGS.REMOVE),
this.item.provider.soma.langs.ts(FILE_UPLOAD_LANGS.REMOVE),
this.item.opts.file.current.name
]
.filter(Boolean)
@ -685,10 +698,12 @@ export class FileUploadItemProgressProvider {
private constructor(opts: FileUploadItemProgressOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('item-progress', { id: opts.id, ref: opts.ref, owner: this });
this.item = FileUploadItemProvider.require();
this.runtimePart = this.item.provider.runtime.part('item-progress', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly percent = $derived.by(() => Math.min(100, Math.max(0, this.opts.value.current ?? 0)));
@ -721,10 +736,12 @@ export class FileUploadClearTriggerProvider {
private constructor(opts: FileUploadClearTriggerOpts) {
this.opts = opts;
this.runtimePart = Soma.require()
.runtime(fileUploadMorfo, {})
.part('clear-trigger', { id: opts.id, ref: opts.ref, owner: this });
this.provider = FileUploadProvider.require();
this.runtimePart = this.provider.runtime.part('clear-trigger', {
id: opts.id,
ref: opts.ref,
owner: this
});
}
readonly isDisabled = $derived.by(() => this.provider.isDisabled || this.provider.isEmpty);
@ -739,7 +756,7 @@ export class FileUploadClearTriggerProvider {
...this.runtimePart.props,
type: 'button' as const,
disabled: this.isDisabled || undefined,
'aria-label': this.provider.soma?.langs.ts(FILE_UPLOAD_LANGS.CLEAR),
'aria-label': this.provider.soma.langs.ts(FILE_UPLOAD_LANGS.CLEAR),
'data-disabled': boolToEmptyStrOrUndef(this.isDisabled),
onclick: this.onclick
} as const)

Loading…
Cancel
Save

Powered by TurnKey Linux.