bard-attachment_field 0.1.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.
- checksums.yaml +7 -0
- data/.nvmrc +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Appraisals +17 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +39 -0
- data/Rakefile +10 -0
- data/app/assets/javascripts/input-attachment.js +6021 -0
- data/app/controllers/bard/attachment_field/blobs_controller.rb +11 -0
- data/bard-attachment_field.gemspec +56 -0
- data/config/cucumber.yml +1 -0
- data/config/routes.rb +6 -0
- data/gemfiles/rails_7.1.gemfile +7 -0
- data/gemfiles/rails_7.2.gemfile +7 -0
- data/gemfiles/rails_8.0.gemfile +7 -0
- data/gemfiles/rails_8.1.gemfile +7 -0
- data/input-attachment/.editorconfig +15 -0
- data/input-attachment/.github/workflows/test.yml +21 -0
- data/input-attachment/.gitignore +27 -0
- data/input-attachment/.prettierrc.json +13 -0
- data/input-attachment/CLAUDE.md +63 -0
- data/input-attachment/LICENSE +21 -0
- data/input-attachment/README.md +288 -0
- data/input-attachment/bin/log +2 -0
- data/input-attachment/bin/server +1 -0
- data/input-attachment/bin/setup +4 -0
- data/input-attachment/bun.lockb +0 -0
- data/input-attachment/bundle.js +3 -0
- data/input-attachment/jest-setup.js +24 -0
- data/input-attachment/package.json +56 -0
- data/input-attachment/src/components/attachment-file/accepts.ts +32 -0
- data/input-attachment/src/components/attachment-file/attachment-file.css +89 -0
- data/input-attachment/src/components/attachment-file/attachment-file.e2e.ts +11 -0
- data/input-attachment/src/components/attachment-file/attachment-file.spec.tsx +20 -0
- data/input-attachment/src/components/attachment-file/attachment-file.tsx +157 -0
- data/input-attachment/src/components/attachment-file/direct-upload-controller.tsx +100 -0
- data/input-attachment/src/components/attachment-file/extensions.ts +13 -0
- data/input-attachment/src/components/attachment-file/max.ts +46 -0
- data/input-attachment/src/components/attachment-file/readme.md +55 -0
- data/input-attachment/src/components/attachment-preview/attachment-preview.css +8 -0
- data/input-attachment/src/components/attachment-preview/attachment-preview.e2e.ts +11 -0
- data/input-attachment/src/components/attachment-preview/attachment-preview.spec.tsx +19 -0
- data/input-attachment/src/components/attachment-preview/attachment-preview.tsx +42 -0
- data/input-attachment/src/components/attachment-preview/readme.md +31 -0
- data/input-attachment/src/components/input-attachment/form-controller.tsx +146 -0
- data/input-attachment/src/components/input-attachment/input-attachment.css +100 -0
- data/input-attachment/src/components/input-attachment/input-attachment.e2e.ts +11 -0
- data/input-attachment/src/components/input-attachment/input-attachment.spec.tsx +37 -0
- data/input-attachment/src/components/input-attachment/input-attachment.tsx +353 -0
- data/input-attachment/src/components/input-attachment/readme.md +45 -0
- data/input-attachment/src/components.d.ts +175 -0
- data/input-attachment/src/global.d.ts +3 -0
- data/input-attachment/src/images/example.jpg +0 -0
- data/input-attachment/src/index.html +36 -0
- data/input-attachment/src/index.ts +1 -0
- data/input-attachment/src/utils/utils.spec.ts +19 -0
- data/input-attachment/src/utils/utils.ts +14 -0
- data/input-attachment/stencil.config.ts +43 -0
- data/input-attachment/test-mocks/file-drop.cjs +7 -0
- data/input-attachment/test-mocks/progress-bar.cjs +9 -0
- data/input-attachment/tsconfig.json +32 -0
- data/lib/bard/attachment_field/cucumber.rb +277 -0
- data/lib/bard/attachment_field/field.rb +33 -0
- data/lib/bard/attachment_field/form_builder.rb +12 -0
- data/lib/bard/attachment_field/version.rb +7 -0
- data/lib/bard/attachment_field.rb +20 -0
- data/lib/bard-attachment_field.rb +1 -0
- metadata +409 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/bard/attachment_field/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "bard-attachment_field"
|
|
7
|
+
spec.version = Bard::AttachmentField::VERSION
|
|
8
|
+
spec.authors = ["Micah Geisel"]
|
|
9
|
+
spec.email = ["micah@botandrose.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Enhanced file upload field for Rails forms with drag-and-drop and previews"
|
|
12
|
+
spec.description = "An enhanced file upload field for Rails forms, powered by web components. Provides drag-and-drop uploads, image/video previews, and seamless ActiveStorage integration."
|
|
13
|
+
spec.homepage = "https://github.com/botandrose/bard-attachment_field"
|
|
14
|
+
spec.required_ruby_version = ">= 3.0.0"
|
|
15
|
+
|
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/botandrose/bard-attachment_field"
|
|
18
|
+
|
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
21
|
+
spec.files = Dir.chdir(__dir__) do
|
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
23
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
spec.bindir = "exe"
|
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
28
|
+
spec.require_paths = ["lib"]
|
|
29
|
+
|
|
30
|
+
spec.add_dependency "activestorage", ">=7.1.0"
|
|
31
|
+
|
|
32
|
+
# Development dependencies
|
|
33
|
+
spec.add_development_dependency "debug"
|
|
34
|
+
spec.add_development_dependency "rails"
|
|
35
|
+
spec.add_development_dependency "sqlite3"
|
|
36
|
+
spec.add_development_dependency "cucumber"
|
|
37
|
+
spec.add_development_dependency "cucumber-rails"
|
|
38
|
+
spec.add_development_dependency "capybara"
|
|
39
|
+
spec.add_development_dependency "cuprite"
|
|
40
|
+
spec.add_development_dependency "cuprite-downloads"
|
|
41
|
+
spec.add_development_dependency "chop"
|
|
42
|
+
spec.add_development_dependency "rspec"
|
|
43
|
+
spec.add_development_dependency "capybara-shadowdom"
|
|
44
|
+
spec.add_development_dependency "capybara-screenshot"
|
|
45
|
+
spec.add_development_dependency "database_cleaner"
|
|
46
|
+
spec.add_development_dependency "puma"
|
|
47
|
+
spec.add_development_dependency "sprockets-rails"
|
|
48
|
+
spec.add_development_dependency "importmap-rails"
|
|
49
|
+
spec.add_development_dependency "turbo-rails"
|
|
50
|
+
spec.add_development_dependency "stimulus-rails"
|
|
51
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
52
|
+
spec.add_development_dependency "appraisal"
|
|
53
|
+
|
|
54
|
+
# For more information and examples about making a new gem, check out our
|
|
55
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
56
|
+
end
|
data/config/cucumber.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
default: --strict <%= "--retry 2 --no-strict-flaky" if ENV["CI"] %> --format pretty --guess --require features/support --publish-quiet --tags 'not @pending'
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# http://editorconfig.org
|
|
2
|
+
|
|
3
|
+
root = true
|
|
4
|
+
|
|
5
|
+
[*]
|
|
6
|
+
charset = utf-8
|
|
7
|
+
indent_style = space
|
|
8
|
+
indent_size = 2
|
|
9
|
+
end_of_line = lf
|
|
10
|
+
insert_final_newline = true
|
|
11
|
+
trim_trailing_whitespace = true
|
|
12
|
+
|
|
13
|
+
[*.md]
|
|
14
|
+
insert_final_newline = false
|
|
15
|
+
trim_trailing_whitespace = false
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v4
|
|
11
|
+
|
|
12
|
+
- name: Setup Bun
|
|
13
|
+
uses: oven-sh/setup-bun@v1
|
|
14
|
+
with:
|
|
15
|
+
bun-version: latest
|
|
16
|
+
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
run: bun install
|
|
19
|
+
|
|
20
|
+
- name: Run tests
|
|
21
|
+
run: NODE_OPTIONS=--experimental-vm-modules bun run test
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
www/
|
|
2
|
+
dist/
|
|
3
|
+
loader/
|
|
4
|
+
|
|
5
|
+
*~
|
|
6
|
+
*.sw[mnpcod]
|
|
7
|
+
*.log
|
|
8
|
+
*.lock
|
|
9
|
+
*.tmp
|
|
10
|
+
*.tmp.*
|
|
11
|
+
log.txt
|
|
12
|
+
*.sublime-project
|
|
13
|
+
*.sublime-workspace
|
|
14
|
+
|
|
15
|
+
.stencil/
|
|
16
|
+
.idea/
|
|
17
|
+
.vscode/
|
|
18
|
+
.sass-cache/
|
|
19
|
+
.versions/
|
|
20
|
+
node_modules/
|
|
21
|
+
$RECYCLE.BIN/
|
|
22
|
+
|
|
23
|
+
.DS_Store
|
|
24
|
+
Thumbs.db
|
|
25
|
+
UserInterfaceState.xcuserstate
|
|
26
|
+
.env
|
|
27
|
+
PLAN
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"arrowParens": "avoid",
|
|
3
|
+
"bracketSpacing": true,
|
|
4
|
+
"jsxBracketSameLine": false,
|
|
5
|
+
"jsxSingleQuote": false,
|
|
6
|
+
"quoteProps": "consistent",
|
|
7
|
+
"printWidth": 180,
|
|
8
|
+
"semi": true,
|
|
9
|
+
"singleQuote": true,
|
|
10
|
+
"tabWidth": 2,
|
|
11
|
+
"trailingComma": "all",
|
|
12
|
+
"useTabs": false
|
|
13
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Development Commands
|
|
6
|
+
|
|
7
|
+
**Note: This project uses Bun instead of npm for package management and script execution.**
|
|
8
|
+
|
|
9
|
+
- **Build**: `bun run build` - Compiles Stencil components with docs generation
|
|
10
|
+
- **Development server**: `bun start` - Watch mode with live reload
|
|
11
|
+
- **Tests**: `bun run test` - Run unit tests (Jest via Stencil)
|
|
12
|
+
- **E2E tests**: `bun run test:e2e` - Run unit + e2e
|
|
13
|
+
- **Spec tests only**: `bun run spec` - Run unit tests without e2e
|
|
14
|
+
- **Watch tests**: `bun run test.watch` - Run tests in watch mode
|
|
15
|
+
- **Generate component**: `bun run generate` - Create new Stencil component
|
|
16
|
+
|
|
17
|
+
## Architecture Overview
|
|
18
|
+
|
|
19
|
+
This is a Stencil-based Web Components library for advanced file upload functionality, specifically designed to work with Rails Active Storage direct uploads.
|
|
20
|
+
|
|
21
|
+
### Core Components Structure
|
|
22
|
+
|
|
23
|
+
- **`<input-attachment>`** - Main file upload component with drag/drop support
|
|
24
|
+
- Acts as form field replacement with validation API compatibility
|
|
25
|
+
- Manages file state and coordinates with Rails Active Storage
|
|
26
|
+
- Uses `FormController` for upload orchestration and progress tracking
|
|
27
|
+
|
|
28
|
+
- **`<attachment-file>`** - Individual file representation component
|
|
29
|
+
- Handles direct upload via `DirectUploadController`
|
|
30
|
+
- Manages file preview, validation, and removal
|
|
31
|
+
- Supports both existing files (via signed IDs) and new uploads
|
|
32
|
+
|
|
33
|
+
- **`<file-drop>`** - Drag and drop interface component (provided by `@botandrose/file-drop` package)
|
|
34
|
+
- **`<attachment-preview>`** - File preview display component
|
|
35
|
+
- **`<progress-bar>`** - Upload progress indicator (provided by `@botandrose/progress-bar` package)
|
|
36
|
+
|
|
37
|
+
### Key Integration Points
|
|
38
|
+
|
|
39
|
+
- **Rails Active Storage**: Uses `@rails/activestorage` for direct uploads
|
|
40
|
+
- **Form Integration**: Components integrate with standard HTML forms via `FormController`
|
|
41
|
+
- **Validation**: Implements HTML5 form validation APIs (`checkValidity`, `setCustomValidity`, etc.)
|
|
42
|
+
|
|
43
|
+
### Component Communication
|
|
44
|
+
|
|
45
|
+
- Uses Stencil's event system for component communication
|
|
46
|
+
- Key events: `direct-upload:*`, `attachment-file:remove`, `change`
|
|
47
|
+
- `FormController` coordinates upload queue and progress display
|
|
48
|
+
- Components maintain form field compatibility for seamless integration
|
|
49
|
+
|
|
50
|
+
### File Upload Flow
|
|
51
|
+
|
|
52
|
+
1. User selects/drops files into `<input-attachment>`
|
|
53
|
+
2. Files become `<attachment-file>` components with validation
|
|
54
|
+
3. On form submit, `FormController` manages upload queue
|
|
55
|
+
4. `DirectUploadController` handles Rails Active Storage uploads
|
|
56
|
+
5. Progress tracked via `<progress-bar>` components
|
|
57
|
+
6. Completed uploads provide signed IDs for form submission
|
|
58
|
+
|
|
59
|
+
## Testing Configuration
|
|
60
|
+
|
|
61
|
+
- Uses Jest with ts-jest for TypeScript support
|
|
62
|
+
- Puppeteer for e2e testing (headless Chrome)
|
|
63
|
+
- ESM modules enabled via `NODE_OPTIONS=--experimental-vm-modules`
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# input-attachment
|
|
2
|
+
|
|
3
|
+
A web components library providing advanced file upload functionality for Rails Active Storage with drag-and-drop support and real-time progress tracking.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 📁 **Multiple File Support** - Upload single or multiple files
|
|
8
|
+
- 🎨 **Drag & Drop** - Intuitive drag-and-drop file selection
|
|
9
|
+
- 📸 **File Previews** - Automatic preview generation for images and videos
|
|
10
|
+
- 📊 **Progress Tracking** - Real-time upload progress with visual feedback
|
|
11
|
+
- ✅ **Validation** - Built-in file type and size validation
|
|
12
|
+
- 🔐 **Rails Active Storage Integration** - Seamless direct uploads to AWS S3 or other storage backends
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @botandrose/input-attachment
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
or with Bun:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bun add @botandrose/input-attachment
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Basic Example
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<form>
|
|
32
|
+
<input-attachment
|
|
33
|
+
name="files"
|
|
34
|
+
directupload="/rails/active_storage/direct_uploads"
|
|
35
|
+
multiple
|
|
36
|
+
></input-attachment>
|
|
37
|
+
|
|
38
|
+
<button type="submit">Upload</button>
|
|
39
|
+
</form>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### With Validation
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<input-attachment
|
|
46
|
+
name="photos"
|
|
47
|
+
directupload="/rails/active_storage/direct_uploads"
|
|
48
|
+
accepts="image"
|
|
49
|
+
max="5242880"
|
|
50
|
+
required
|
|
51
|
+
></input-attachment>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API Reference
|
|
55
|
+
|
|
56
|
+
### `<input-attachment>`
|
|
57
|
+
|
|
58
|
+
The main component providing file upload functionality.
|
|
59
|
+
|
|
60
|
+
#### Props
|
|
61
|
+
|
|
62
|
+
| Prop | Type | Default | Description |
|
|
63
|
+
| ------ | ------ | --------- | ------------- |
|
|
64
|
+
| `name` | string | - | Form field name for form submission |
|
|
65
|
+
| `directupload` | string | - | Rails Active Storage direct upload endpoint URL |
|
|
66
|
+
| `multiple` | boolean | false | Allow multiple file selection |
|
|
67
|
+
| `required` | boolean | false | Require at least one file |
|
|
68
|
+
| `accepts` | string | - | Comma-separated file types (e.g., "image", "video", "pdf") |
|
|
69
|
+
| `max` | number | - | Maximum file size in bytes |
|
|
70
|
+
| `preview` | boolean | true | Show file previews |
|
|
71
|
+
| `disabled` | boolean | false | Disable file selection (used during form submission) |
|
|
72
|
+
|
|
73
|
+
#### Methods
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// Get/set array of file objects
|
|
77
|
+
get files(): AttachmentFile[]
|
|
78
|
+
set files(val: AttachmentFile[])
|
|
79
|
+
|
|
80
|
+
// Get/set array of signed IDs from Active Storage
|
|
81
|
+
get value(): string[]
|
|
82
|
+
set value(val: string[])
|
|
83
|
+
|
|
84
|
+
// Clear all files
|
|
85
|
+
reset(): void
|
|
86
|
+
|
|
87
|
+
// Validate the component
|
|
88
|
+
checkValidity(): boolean
|
|
89
|
+
setCustomValidity(msg: string): void
|
|
90
|
+
reportValidity(): boolean
|
|
91
|
+
|
|
92
|
+
// Get validation error message
|
|
93
|
+
get validationMessage(): string
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### Events
|
|
97
|
+
|
|
98
|
+
| Event | Detail | Description |
|
|
99
|
+
| ------- | -------- | ------------- |
|
|
100
|
+
| `change` | - | Fired when file list changes (bubbles) |
|
|
101
|
+
| `direct-upload:initialize` | `{ id, file, controller }` | Upload queue initialized |
|
|
102
|
+
| `direct-upload:start` | `{ id }` | Upload started |
|
|
103
|
+
| `direct-upload:progress` | `{ id, progress }` | Upload progress (0-100) |
|
|
104
|
+
| `direct-upload:error` | `{ id, error }` | Upload failed |
|
|
105
|
+
| `direct-upload:end` | `{ id }` | Upload completed |
|
|
106
|
+
|
|
107
|
+
### `<attachment-file>`
|
|
108
|
+
|
|
109
|
+
Individual file representation within the upload component.
|
|
110
|
+
|
|
111
|
+
#### Props
|
|
112
|
+
|
|
113
|
+
| Prop | Type | Default | Description |
|
|
114
|
+
| ------ | ------ | --------- | ------------- |
|
|
115
|
+
| `name` | string | - | Form field name |
|
|
116
|
+
| `value` | string | "" | Signed ID from Rails Active Storage |
|
|
117
|
+
| `filename` | string | - | Display filename |
|
|
118
|
+
| `src` | string | - | Preview image/video URL |
|
|
119
|
+
| `filetype` | string | - | File category (image/video/pdf/unknown) |
|
|
120
|
+
| `size` | number | - | File size in bytes |
|
|
121
|
+
| `state` | string | "complete" | Upload state (pending/complete/error) |
|
|
122
|
+
| `percent` | number | 100 | Upload progress percentage |
|
|
123
|
+
| `preview` | boolean | true | Show preview |
|
|
124
|
+
| `accepts` | string | - | Allowed file types |
|
|
125
|
+
| `max` | number | - | Maximum file size |
|
|
126
|
+
|
|
127
|
+
#### Methods
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// Set file to upload
|
|
131
|
+
set file(file: File)
|
|
132
|
+
|
|
133
|
+
// Load existing file from Active Storage
|
|
134
|
+
set signedId(val: string)
|
|
135
|
+
|
|
136
|
+
// Validate the file
|
|
137
|
+
checkValidity(): boolean
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Form Submission Flow
|
|
141
|
+
|
|
142
|
+
1. User selects/drops files into `<input-attachment>`
|
|
143
|
+
2. Files become `<attachment-file>` components with validation
|
|
144
|
+
3. On form submit, `FormController` intercepts and manages upload queue
|
|
145
|
+
4. Each file uploads to Rails Active Storage via `DirectUploadController`
|
|
146
|
+
5. Signed IDs are collected via `ElementInternals.setFormValue()`
|
|
147
|
+
6. After all uploads complete, form is actually submitted
|
|
148
|
+
7. Server receives signed IDs in form data
|
|
149
|
+
|
|
150
|
+
## Architecture
|
|
151
|
+
|
|
152
|
+
### Components
|
|
153
|
+
|
|
154
|
+
- **`<input-attachment>`** - Main form field replacement with drag-and-drop
|
|
155
|
+
- **`<attachment-file>`** - Individual file representation
|
|
156
|
+
- **`<attachment-preview>`** - File preview display
|
|
157
|
+
- **`<file-drop>`** - Drag and drop interface (from `@botandrose/file-drop`)
|
|
158
|
+
- **`<progress-bar>`** - Upload progress indicator (from `@botandrose/progress-bar`)
|
|
159
|
+
|
|
160
|
+
## Styling
|
|
161
|
+
|
|
162
|
+
The component uses Shadow DOM with customizable CSS custom properties:
|
|
163
|
+
|
|
164
|
+
```css
|
|
165
|
+
input-attachment {
|
|
166
|
+
--input-attachment-text-color: #000;
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Style the file-drop area using the `::part()` pseudo-element:
|
|
171
|
+
|
|
172
|
+
```css
|
|
173
|
+
input-attachment::part(title) {
|
|
174
|
+
font-size: 16px;
|
|
175
|
+
color: #333;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Rails Integration
|
|
180
|
+
|
|
181
|
+
### Setup Active Storage Direct Uploads
|
|
182
|
+
|
|
183
|
+
In your Rails app, ensure Active Storage is configured:
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
# config/storage.yml
|
|
187
|
+
amazon:
|
|
188
|
+
service: S3
|
|
189
|
+
access_key_id: ...
|
|
190
|
+
secret_access_key: ...
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The `directupload` prop should point to:
|
|
194
|
+
```
|
|
195
|
+
/rails/active_storage/direct_uploads
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Accessing Uploaded Files in Rails
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
class Post < ApplicationRecord
|
|
202
|
+
has_many_attached :attachments
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# In controller
|
|
206
|
+
@post = Post.create(attachments: attachment_signed_ids)
|
|
207
|
+
|
|
208
|
+
# The signed_ids are automatically resolved to blobs
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Validation
|
|
212
|
+
|
|
213
|
+
### File Type Validation
|
|
214
|
+
|
|
215
|
+
```html
|
|
216
|
+
<input-attachment
|
|
217
|
+
accepts="image,video"
|
|
218
|
+
></input-attachment>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Supported types: `image`, `video`, `pdf`, or specific MIME types
|
|
222
|
+
|
|
223
|
+
### File Size Validation
|
|
224
|
+
|
|
225
|
+
```html
|
|
226
|
+
<!-- Max 5MB -->
|
|
227
|
+
<input-attachment
|
|
228
|
+
max="5242880"
|
|
229
|
+
></input-attachment>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Custom Validation
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
const attachment = document.querySelector('input-attachment');
|
|
236
|
+
|
|
237
|
+
// Check validity
|
|
238
|
+
if (!attachment.checkValidity()) {
|
|
239
|
+
console.log(attachment.validationMessage);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Set custom error
|
|
243
|
+
attachment.setCustomValidity('Custom error message');
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Development
|
|
247
|
+
|
|
248
|
+
### Prerequisites
|
|
249
|
+
|
|
250
|
+
- Node.js 18+
|
|
251
|
+
- Bun
|
|
252
|
+
|
|
253
|
+
### Commands
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
# Install dependencies
|
|
257
|
+
bun install
|
|
258
|
+
|
|
259
|
+
# Start development server
|
|
260
|
+
bun start
|
|
261
|
+
|
|
262
|
+
# Build for production
|
|
263
|
+
bun run build
|
|
264
|
+
|
|
265
|
+
# Run tests
|
|
266
|
+
bun run test
|
|
267
|
+
|
|
268
|
+
# Watch tests
|
|
269
|
+
bun run test.watch
|
|
270
|
+
|
|
271
|
+
# Run e2e tests only
|
|
272
|
+
bun run test:e2e
|
|
273
|
+
|
|
274
|
+
# Generate new component
|
|
275
|
+
bun run generate
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Browser Support
|
|
279
|
+
|
|
280
|
+
- Chrome/Edge 88+
|
|
281
|
+
- Firefox 85+
|
|
282
|
+
- Safari 15.1+
|
|
283
|
+
|
|
284
|
+
ElementInternals requires modern browsers with form-associated custom elements support.
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bun run start
|
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Jest setup file for DOM polyfills
|
|
2
|
+
|
|
3
|
+
// Polyfill replaceChildren for test environment
|
|
4
|
+
Element.prototype.replaceChildren = Element.prototype.replaceChildren || function(...nodes) {
|
|
5
|
+
while (this.lastChild) {
|
|
6
|
+
this.removeChild(this.lastChild);
|
|
7
|
+
}
|
|
8
|
+
this.append(...nodes);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// Mock HTML input validation methods
|
|
12
|
+
if (typeof HTMLInputElement !== 'undefined') {
|
|
13
|
+
Object.defineProperty(HTMLInputElement.prototype, 'setCustomValidity', {
|
|
14
|
+
value: function() {},
|
|
15
|
+
writable: true
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
Object.defineProperty(HTMLInputElement.prototype, 'reportValidity', {
|
|
19
|
+
value: function() { return true; },
|
|
20
|
+
writable: true
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Progress-bar and file-drop components are mocked via moduleNameMapper in stencil.config.ts
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@botandrose/input-attachment",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Next-gen file field",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "dist/components/index.js",
|
|
7
|
+
"types": "./dist/components/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"prepublishOnly": "bun run build",
|
|
13
|
+
"build": "bunx stencil build --docs && bun run bundle",
|
|
14
|
+
"bundle": "bunx esbuild dist/components/index.js --bundle --format=esm --outfile=dist/input-attachment.esm.js --footer:js='defineCustomElements();'",
|
|
15
|
+
"start": "bunx stencil build --docs --watch --serve",
|
|
16
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules bunx stencil test --spec --e2e --max-workers=1",
|
|
17
|
+
"test:spec": "NODE_OPTIONS=--experimental-vm-modules bunx stencil test --spec --max-workers=1",
|
|
18
|
+
"test:e2e": "NODE_OPTIONS=--experimental-vm-modules bunx stencil test --e2e --max-workers=1",
|
|
19
|
+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules bunx stencil test --spec --watchAll --max-workers=1",
|
|
20
|
+
"generate": "bunx stencil generate"
|
|
21
|
+
},
|
|
22
|
+
"jest": {
|
|
23
|
+
"testEnvironment": "jsdom",
|
|
24
|
+
"extensionsToTreatAsEsm": [".ts", ".tsx"],
|
|
25
|
+
"globals": {
|
|
26
|
+
"ts-jest": {
|
|
27
|
+
"useESM": true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@botandrose/file-drop": "^0.1.0",
|
|
33
|
+
"@botandrose/progress-bar": "^0.1.1",
|
|
34
|
+
"@rails/activestorage": "^8.1.0",
|
|
35
|
+
"@stencil/core": "^4.38.2",
|
|
36
|
+
"rails-request-json": "^0.2.0",
|
|
37
|
+
"ts-jest": "^29.4.5"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/jest": "^29.5.12",
|
|
41
|
+
"@types/node": "^24.9.1",
|
|
42
|
+
"jest": "^29.7.0",
|
|
43
|
+
"jest-cli": "^29.7.0",
|
|
44
|
+
"puppeteer": "^24.26.1"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@rails/activestorage": ">=7.0.0"
|
|
48
|
+
},
|
|
49
|
+
"exports": {
|
|
50
|
+
".": {
|
|
51
|
+
"types": "./dist/types/components.d.ts",
|
|
52
|
+
"import": "./dist/components/index.js"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"license": "MIT"
|
|
56
|
+
}
|