diff --git a/valdi_modules/playground/src/WidgetsCatalog.tsx b/valdi_modules/playground/src/WidgetsCatalog.tsx
index a9d43b2..0e6d3e9 100644
--- a/valdi_modules/playground/src/WidgetsCatalog.tsx
+++ b/valdi_modules/playground/src/WidgetsCatalog.tsx
@@ -3,6 +3,7 @@ import { WithInsets } from 'widgets/src/components/util/WithInsets';
import { DatePicker } from 'widgets/src/components/pickers/DatePicker';
import { TimePicker, TimePickerTime } from 'widgets/src/components/pickers/TimePicker';
import { IndexPicker } from 'widgets/src/components/pickers/IndexPicker';
+import { FilePicker, FilePickerOnSelectEvent } from 'widgets/src/components/pickers/FilePicker';
import { EmojiLabel } from 'widgets/src/components/text/EmojiLabel';
import { Section } from 'widgets/src/components/section/Section';
import { SectionSeparator } from 'widgets/src/components/section/SectionSeparator';
@@ -15,6 +16,7 @@ interface CatalogState {
date: Date;
time: TimePickerTime;
fruitIndex: number;
+ selectedFileName: string;
}
export class WidgetsCatalog extends StatefulComponent<{}, CatalogState, {}> {
@@ -22,10 +24,11 @@ export class WidgetsCatalog extends StatefulComponent<{}, CatalogState, {}> {
date: new Date(),
time: { hourOfDay: new Date().getHours(), minuteOfHour: 0 },
fruitIndex: 0,
+ selectedFileName: '',
};
onRender(): void {
- const { date, time, fruitIndex } = this.state;
+ const { date, time, fruitIndex, selectedFileName } = this.state;
const dateStr = date.toLocaleDateString();
const timeStr = `${String(time.hourOfDay).padStart(2, '0')}:${String(time.minuteOfHour).padStart(2, '0')}`;
const fruitLabel = FRUIT_LABELS[fruitIndex];
@@ -79,6 +82,20 @@ export class WidgetsCatalog extends StatefulComponent<{}, CatalogState, {}> {
+
+ this.setState({ selectedFileName: e.fileName })}
+ />
+
+
+
+
+
diff --git a/valdi_modules/widgets/BUILD.bazel b/valdi_modules/widgets/BUILD.bazel
index b355c2b..1313934 100644
--- a/valdi_modules/widgets/BUILD.bazel
+++ b/valdi_modules/widgets/BUILD.bazel
@@ -2,7 +2,7 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
load("@valdi//bzl/valdi:valdi_module.bzl", "valdi_module")
load("@valdi//bzl/valdi:valdi_android_library.bzl", "valdi_android_library")
-# Web polyglot implementations (DatePicker, TimePicker, IndexPicker, EmojiLabel).
+# Web polyglot implementations (DatePicker, TimePicker, IndexPicker, FilePicker, EmojiLabel).
# The build system's generate_register_native_modules picks up the compiled JS
# and checks for webPolyglotViews exports to register custom view factories.
ts_project(
@@ -13,30 +13,30 @@ ts_project(
visibility = ["//visibility:public"],
)
-# Android attribute binders for polyglot custom views (IndexPicker, DatePicker, TimePicker).
+# Android attribute binders for polyglot custom views (IndexPicker, DatePicker, TimePicker, FilePicker).
valdi_android_library(
name = "widgets_android",
srcs = glob(["android/*.kt"]),
deps = ["@valdi//valdi:valdi_java"],
)
-# iOS native views for polyglot custom views (IndexPicker, DatePicker, TimePicker, EmojiLabel).
+# iOS native views for polyglot custom views (IndexPicker, DatePicker, TimePicker, FilePicker, EmojiLabel).
objc_library(
name = "widgets_ios_impl",
srcs = glob(["ios/**/*.m"]),
hdrs = glob(["ios/**/*.h"]),
- sdk_frameworks = ["UIKit"],
+ sdk_frameworks = ["UIKit", "UniformTypeIdentifiers"],
deps = [
"@valdi//valdi_core:valdi_core_objc",
],
)
-# macOS native views for polyglot custom views (DatePicker, TimePicker, IndexPicker, EmojiLabel).
+# macOS native views for polyglot custom views (DatePicker, TimePicker, IndexPicker, FilePicker, EmojiLabel).
objc_library(
name = "widgets_macos_impl",
srcs = glob(["macos/**/*.m"]),
hdrs = glob(["macos/**/*.h"]),
- sdk_frameworks = ["Cocoa"],
+ sdk_frameworks = ["Cocoa", "UniformTypeIdentifiers"],
deps = ["@valdi//valdi:valdi_macos"],
)
diff --git a/valdi_modules/widgets/android/ValdiFilePicker.kt b/valdi_modules/widgets/android/ValdiFilePicker.kt
new file mode 100644
index 0000000..dc1476a
--- /dev/null
+++ b/valdi_modules/widgets/android/ValdiFilePicker.kt
@@ -0,0 +1,82 @@
+package com.snap.widgets.pickers
+
+import android.app.Activity
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.graphics.Color
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.LinearLayout
+import com.snap.valdi.callable.ValdiFunction
+import com.snap.valdi.views.ValdiTouchEventResult
+import com.snap.valdi.views.ValdiTouchTarget
+
+class ValdiFilePicker(context: Context) : LinearLayout(context), ValdiTouchTarget {
+
+ var onSelectFunction: ValdiFunction? = null
+ var allowMultiple: Boolean = false
+ var accept: String? = null
+
+ private val pickButton: Button
+
+ init {
+ orientation = HORIZONTAL
+ gravity = Gravity.CENTER
+ layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ minimumHeight = 56
+ setBackgroundColor(Color.TRANSPARENT)
+
+ pickButton = Button(context).apply {
+ text = "Choose fileβ¦"
+ setOnClickListener { launchFilePicker() }
+ }
+ addView(pickButton)
+ }
+
+ private fun resolveActivity(): Activity? {
+ var ctx: Context? = context
+ while (ctx != null) {
+ if (ctx is Activity) return ctx
+ ctx = (ctx as? ContextWrapper)?.baseContext
+ }
+ var v: View? = this
+ while (v != null) {
+ var c: Context? = v.context
+ while (c != null) {
+ if (c is Activity) return c
+ c = (c as? ContextWrapper)?.baseContext
+ }
+ v = (v.parent as? View)
+ }
+ return null
+ }
+
+ private fun launchFilePicker() {
+ val activity = resolveActivity() ?: return
+ try {
+ val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
+ type = accept ?: "*/*"
+ addCategory(Intent.CATEGORY_OPENABLE)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple)
+ }
+ activity.startActivity(Intent.createChooser(intent, "Select file"))
+ } catch (_: Exception) { }
+ }
+
+ override fun processTouchEvent(event: MotionEvent): ValdiTouchEventResult {
+ val consumed = dispatchTouchEvent(event)
+ return if (consumed) {
+ ValdiTouchEventResult.ConsumeEventAndCancelOtherGestures
+ } else {
+ ValdiTouchEventResult.IgnoreEvent
+ }
+ }
+}
diff --git a/valdi_modules/widgets/android/ValdiFilePickerAttributesBinder.kt b/valdi_modules/widgets/android/ValdiFilePickerAttributesBinder.kt
new file mode 100644
index 0000000..c13bdbe
--- /dev/null
+++ b/valdi_modules/widgets/android/ValdiFilePickerAttributesBinder.kt
@@ -0,0 +1,33 @@
+package com.snap.widgets.pickers
+
+import android.content.Context
+import com.snap.valdi.attributes.AttributesBinder
+import com.snap.valdi.attributes.AttributesBindingContext
+import com.snap.valdi.attributes.RegisterAttributesBinder
+
+@RegisterAttributesBinder
+class ValdiFilePickerAttributesBinder(private val context: Context) : AttributesBinder {
+
+ override val viewClass: Class
+ get() = ValdiFilePicker::class.java
+
+ override fun bindAttributes(attributesBindingContext: AttributesBindingContext) {
+ attributesBindingContext.bindFunctionAttribute("onSelect", { view, fn ->
+ view.onSelectFunction = fn
+ }, { view ->
+ view.onSelectFunction = null
+ })
+
+ attributesBindingContext.bindBooleanAttribute("allowMultiple", false, { view, value, _ ->
+ view.allowMultiple = value
+ }, { view, _ ->
+ view.allowMultiple = false
+ })
+
+ attributesBindingContext.bindStringAttribute("accept", false, { view, value, _ ->
+ view.accept = value
+ }, { view, _ ->
+ view.accept = null
+ })
+ }
+}
diff --git a/valdi_modules/widgets/ios/SCWidgetsFilePicker.h b/valdi_modules/widgets/ios/SCWidgetsFilePicker.h
new file mode 100644
index 0000000..79e3967
--- /dev/null
+++ b/valdi_modules/widgets/ios/SCWidgetsFilePicker.h
@@ -0,0 +1,9 @@
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SCWidgetsFilePicker : UIView
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/valdi_modules/widgets/ios/SCWidgetsFilePicker.m b/valdi_modules/widgets/ios/SCWidgetsFilePicker.m
new file mode 100644
index 0000000..4516dda
--- /dev/null
+++ b/valdi_modules/widgets/ios/SCWidgetsFilePicker.m
@@ -0,0 +1,125 @@
+#import "SCWidgetsFilePicker.h"
+
+#import "valdi_core/SCValdiAttributesBinderBase.h"
+#import "valdi_core/SCValdiFunction.h"
+#import "valdi_core/SCValdiMarshaller.h"
+
+#import
+
+@interface SCWidgetsFilePicker ()
+@property (nonatomic) UIButton *pickButton;
+@property (nonatomic) id onSelect;
+@property (nonatomic) BOOL allowMultiple;
+@property (nonatomic, copy, nullable) NSString *accept;
+@end
+
+@implementation SCWidgetsFilePicker
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+ if (self) {
+ _pickButton = [UIButton buttonWithType:UIButtonTypeSystem];
+ [_pickButton setTitle:@"Choose fileβ¦" forState:UIControlStateNormal];
+ [_pickButton addTarget:self action:@selector(_openPicker) forControlEvents:UIControlEventTouchUpInside];
+ _pickButton.translatesAutoresizingMaskIntoConstraints = NO;
+ [self addSubview:_pickButton];
+
+ [NSLayoutConstraint activateConstraints:@[
+ [_pickButton.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
+ [_pickButton.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
+ ]];
+ }
+ return self;
+}
+
+- (CGSize)sizeThatFits:(CGSize)size {
+ return CGSizeMake(size.width, MAX(56, size.height));
+}
+
+#pragma mark - File picker
+
+- (void)_openPicker {
+ NSArray *types = [self _resolveContentTypes];
+ UIDocumentPickerViewController *picker =
+ [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:types];
+ picker.allowsMultipleSelection = self.allowMultiple;
+ picker.delegate = self;
+
+ UIViewController *vc = [self _findViewController];
+ if (vc) {
+ [vc presentViewController:picker animated:YES completion:nil];
+ }
+}
+
+- (NSArray *)_resolveContentTypes {
+ if (self.accept.length == 0) {
+ return @[UTTypeItem];
+ }
+ NSMutableArray *types = [NSMutableArray array];
+ for (NSString *raw in [self.accept componentsSeparatedByString:@","]) {
+ NSString *trimmed = [raw stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet];
+ UTType *type = [UTType typeWithMIMEType:trimmed];
+ if (type) {
+ [types addObject:type];
+ }
+ }
+ return types.count > 0 ? types : @[UTTypeItem];
+}
+
+- (UIViewController *)_findViewController {
+ UIResponder *responder = self;
+ while (responder) {
+ if ([responder isKindOfClass:[UIViewController class]]) {
+ return (UIViewController *)responder;
+ }
+ responder = responder.nextResponder;
+ }
+ return nil;
+}
+
+#pragma mark - UIDocumentPickerDelegate
+
+- (void)documentPicker:(UIDocumentPickerViewController *)controller
+ didPickDocumentsAtURLs:(NSArray *)urls {
+ if (!self.onSelect) return;
+ for (NSURL *url in urls) {
+ SCValdiMarshallerScoped(marshaller, {
+ NSInteger objectIndex = SCValdiMarshallerPushMap(marshaller, 1);
+ SCValdiMarshallerPushString(marshaller, url.lastPathComponent);
+ SCValdiMarshallerPutMapPropertyUninterned(marshaller, @"fileName", objectIndex);
+ [self.onSelect performWithMarshaller:marshaller];
+ });
+ }
+}
+
+#pragma mark - Attribute binding
+
++ (void)bindAttributes:(id)attributesBinder {
+ [attributesBinder bindAttribute:@"onSelect"
+ withFunctionBlock:^(SCWidgetsFilePicker *view, id fn) {
+ view.onSelect = fn;
+ }
+ resetBlock:^(SCWidgetsFilePicker *view) {
+ view.onSelect = nil;
+ }];
+
+ [attributesBinder bindAttribute:@"allowMultiple"
+ invalidateLayoutOnChange:NO
+ withBoolBlock:^BOOL(SCWidgetsFilePicker *view, BOOL value, id animator) {
+ view.allowMultiple = value;
+ return YES;
+ } resetBlock:^(SCWidgetsFilePicker *view, id animator) {
+ view.allowMultiple = NO;
+ }];
+
+ [attributesBinder bindAttribute:@"accept"
+ invalidateLayoutOnChange:NO
+ withStringBlock:^BOOL(SCWidgetsFilePicker *view, NSString *value, id animator) {
+ view.accept = value;
+ return YES;
+ } resetBlock:^(SCWidgetsFilePicker *view, id animator) {
+ view.accept = nil;
+ }];
+}
+
+@end
diff --git a/valdi_modules/widgets/macos/SCWidgetsMacOSFilePicker.h b/valdi_modules/widgets/macos/SCWidgetsMacOSFilePicker.h
new file mode 100644
index 0000000..8c49f6e
--- /dev/null
+++ b/valdi_modules/widgets/macos/SCWidgetsMacOSFilePicker.h
@@ -0,0 +1,12 @@
+#import "valdi/macos/SCValdiMacOSAttributesBinder.h"
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SCWidgetsMacOSFilePicker : NSView
+
++ (void)bindAttributes:(SCValdiMacOSAttributesBinder *)attributesBinder;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/valdi_modules/widgets/macos/SCWidgetsMacOSFilePicker.m b/valdi_modules/widgets/macos/SCWidgetsMacOSFilePicker.m
new file mode 100644
index 0000000..e1a06a7
--- /dev/null
+++ b/valdi_modules/widgets/macos/SCWidgetsMacOSFilePicker.m
@@ -0,0 +1,82 @@
+#import "SCWidgetsMacOSFilePicker.h"
+#import "valdi/macos/SCValdiMacOSFunction.h"
+
+#import
+
+@implementation SCWidgetsMacOSFilePicker {
+ NSButton *_pickButton;
+ SCValdiMacOSFunction *_onSelect;
+ BOOL _allowMultiple;
+ NSString *_accept;
+}
+
+- (instancetype)initWithFrame:(NSRect)frameRect {
+ self = [super initWithFrame:frameRect];
+ if (self) {
+ _pickButton = [NSButton buttonWithTitle:@"Choose fileβ¦" target:self action:@selector(_openPanel:)];
+ _pickButton.bezelStyle = NSBezelStyleRounded;
+ _pickButton.translatesAutoresizingMaskIntoConstraints = NO;
+ [self addSubview:_pickButton];
+
+ [NSLayoutConstraint activateConstraints:@[
+ [_pickButton.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
+ [_pickButton.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
+ ]];
+ }
+ return self;
+}
+
+- (void)_openPanel:(id)sender {
+ (void)sender;
+ NSOpenPanel *panel = [NSOpenPanel openPanel];
+ panel.allowsMultipleSelection = _allowMultiple;
+ panel.canChooseDirectories = NO;
+ panel.canChooseFiles = YES;
+ panel.canCreateDirectories = NO;
+ panel.title = @"Choose a file";
+
+ if (_accept.length > 0) {
+ NSMutableArray *types = [NSMutableArray array];
+ for (NSString *raw in [_accept componentsSeparatedByString:@","]) {
+ NSString *trimmed = [raw stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet];
+ UTType *type = [UTType typeWithMIMEType:trimmed];
+ if (type) [types addObject:type];
+ }
+ if (types.count > 0) {
+ panel.allowedContentTypes = types;
+ }
+ }
+
+ NSInteger result = [panel runModal];
+ if (result != NSModalResponseOK || !_onSelect) return;
+
+ for (NSURL *url in panel.URLs) {
+ [_onSelect performWithParameters:@[@{@"fileName": url.lastPathComponent ?: @""}]];
+ }
+}
+
+- (void)valdi_setOnSelect:(id)value {
+ _onSelect = value;
+}
+
+- (void)valdi_setAllowMultiple:(id)value {
+ _allowMultiple = [value boolValue];
+}
+
+- (void)valdi_setAccept:(id)value {
+ _accept = [value isKindOfClass:[NSString class]] ? value : nil;
+}
+
++ (void)bindAttributes:(SCValdiMacOSAttributesBinder *)attributesBinder {
+ [attributesBinder bindUntypedAttribute:@"onSelect"
+ invalidateLayoutOnChange:NO
+ selector:@selector(valdi_setOnSelect:)];
+ [attributesBinder bindUntypedAttribute:@"allowMultiple"
+ invalidateLayoutOnChange:NO
+ selector:@selector(valdi_setAllowMultiple:)];
+ [attributesBinder bindUntypedAttribute:@"accept"
+ invalidateLayoutOnChange:NO
+ selector:@selector(valdi_setAccept:)];
+}
+
+@end
diff --git a/valdi_modules/widgets/src/components/pickers/FilePicker.tsx b/valdi_modules/widgets/src/components/pickers/FilePicker.tsx
new file mode 100644
index 0000000..8fdde82
--- /dev/null
+++ b/valdi_modules/widgets/src/components/pickers/FilePicker.tsx
@@ -0,0 +1,27 @@
+import { Component } from 'valdi_core/src/Component';
+
+export interface FilePickerOnSelectEvent {
+ fileName: string;
+}
+
+export interface FilePickerViewModel {
+ onSelect?: (event: FilePickerOnSelectEvent) => void;
+ allowMultiple?: boolean;
+ accept?: string;
+}
+
+export class FilePicker extends Component {
+ onRender(): void {
+ ;
+ }
+}
diff --git a/valdi_modules/widgets/test/components/pickers/FilePickerTest.ts b/valdi_modules/widgets/test/components/pickers/FilePickerTest.ts
new file mode 100644
index 0000000..31db72a
--- /dev/null
+++ b/valdi_modules/widgets/test/components/pickers/FilePickerTest.ts
@@ -0,0 +1,60 @@
+import { FilePicker } from 'widgets/src/components/pickers/FilePicker';
+import { componentGetElements } from 'foundation/test/util/componentGetElements';
+import { untilRenderComplete } from 'foundation/test/util/untilRenderComplete';
+import 'jasmine/src/jasmine';
+import { createComponent } from 'valdi_test/test/JSXTestUtils';
+
+describe('FilePicker', () => {
+ it('exports FilePicker class', () => {
+ expect(FilePicker).toBeDefined();
+ expect(typeof FilePicker).toBe('function');
+ });
+
+ it('has onRender on prototype', () => {
+ expect(FilePicker.prototype.onRender).toBeDefined();
+ });
+
+ it('renders a custom-view element', async () => {
+ const component = createComponent(
+ FilePicker,
+ {},
+ {},
+ ).getComponent();
+
+ await untilRenderComplete(component);
+
+ const elements = componentGetElements(component);
+ expect(elements.length).toBeGreaterThan(0);
+ });
+
+ it('renders with onSelect callback', async () => {
+ const onSelect = jasmine.createSpy('onSelect');
+ const component = createComponent(
+ FilePicker,
+ { onSelect },
+ {},
+ ).getComponent();
+
+ await untilRenderComplete(component);
+
+ const elements = componentGetElements(component);
+ expect(elements.length).toBeGreaterThan(0);
+ });
+
+ it('renders with allowMultiple and accept', async () => {
+ const component = createComponent(
+ FilePicker,
+ {
+ onSelect: () => {},
+ allowMultiple: true,
+ accept: 'image/*',
+ },
+ {},
+ ).getComponent();
+
+ await untilRenderComplete(component);
+
+ const elements = componentGetElements(component);
+ expect(elements.length).toBeGreaterThan(0);
+ });
+});
diff --git a/valdi_modules/widgets/web/src/WidgetsWeb.ts b/valdi_modules/widgets/web/src/WidgetsWeb.ts
index 9e6b5ef..1d97bec 100644
--- a/valdi_modules/widgets/web/src/WidgetsWeb.ts
+++ b/valdi_modules/widgets/web/src/WidgetsWeb.ts
@@ -8,6 +8,7 @@
* DatePicker β SCWidgetsDatePickerWeb
* TimePicker β SCWidgetsTimePickerWeb
* IndexPicker β SCWidgetsIndexPickerWeb
+ * FilePicker β SCWidgetsFilePickerWeb
* EmojiLabel β SCWidgetsLabelWeb
*
* Each factory returns an object with a changeAttribute(name, value) method so the
@@ -178,6 +179,75 @@ function createIndexPickerFactory(): ViewFactory {
};
}
+// βββ FilePicker βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+function createFilePickerFactory(): ViewFactory {
+ return (container: HTMLElement): AttributeHandler => {
+ container.style.display = 'flex';
+ container.style.alignItems = 'center';
+ container.style.justifyContent = 'center';
+ container.style.pointerEvents = 'auto';
+
+ const wrapper = document.createElement('div');
+ wrapper.style.position = 'relative';
+ wrapper.style.display = 'inline-flex';
+ wrapper.style.alignItems = 'center';
+
+ const button = document.createElement('button');
+ button.textContent = 'Choose fileβ¦';
+ button.style.fontSize = '14px';
+ button.style.padding = '8px 16px';
+ button.style.border = '1px solid #ccc';
+ button.style.borderRadius = '6px';
+ button.style.cursor = 'pointer';
+ button.style.backgroundColor = '#f8f9fa';
+ wrapper.appendChild(button);
+
+ const label = document.createElement('span');
+ label.style.marginLeft = '8px';
+ label.style.fontSize = '13px';
+ label.style.color = '#555';
+ wrapper.appendChild(label);
+
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.style.display = 'none';
+ wrapper.appendChild(input);
+
+ container.appendChild(wrapper);
+
+ let onSelect: ((event: { fileName: string }) => void) | null = null;
+
+ button.addEventListener('click', () => {
+ input.click();
+ });
+
+ input.addEventListener('change', () => {
+ const files = Array.from(input.files ?? []);
+ if (files.length === 0) return;
+ label.textContent =
+ files.length === 1 ? files[0].name : `${files.length} files selected`;
+ if (onSelect) {
+ for (const file of files) {
+ onSelect({ fileName: file.name });
+ }
+ }
+ });
+
+ return {
+ changeAttribute(name: string, value: unknown): void {
+ if (name === 'onSelect') {
+ onSelect = typeof value === 'function' ? (value as (event: { fileName: string }) => void) : null;
+ } else if (name === 'allowMultiple' && typeof value === 'boolean') {
+ input.multiple = value;
+ } else if (name === 'accept' && (typeof value === 'string' || value == null)) {
+ input.accept = (value as string) ?? '';
+ }
+ },
+ };
+ };
+}
+
// βββ EmojiLabel ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function createLabelFactory(): ViewFactory {
@@ -224,5 +294,6 @@ export const webPolyglotViews: Record = {
SCWidgetsDatePickerWeb: createDatePickerFactory(),
SCWidgetsTimePickerWeb: createTimePickerFactory(),
SCWidgetsIndexPickerWeb: createIndexPickerFactory(),
+ SCWidgetsFilePickerWeb: createFilePickerFactory(),
SCWidgetsLabelWeb: createLabelFactory(),
};