bard-tag_field 0.5.1 → 0.5.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63dcbc22c1ab61723177f5ae24d9ce800ce276274491a390b0495c17352c519c
4
- data.tar.gz: 91e3c318345b56a48bcea2b48903ddd16f684c2ae098f8a416ce0fec0d14cc0e
3
+ metadata.gz: 93823b65fed876944585ba4745482acf052992a8bc28077918631c7da583b398
4
+ data.tar.gz: 7e2e0a4fa6bce5db7f8f3c36eb424338dd70ae18c2bb0668bdfbf66f0427258b
5
5
  SHA512:
6
- metadata.gz: f2565d04096d161a991258f0b8a0c796775be897c7a4710c16ba169e5807562a044ede61f2ad5d5a1f975efbb4e887718e16730862b2ab3fddd0952b8ecd6604
7
- data.tar.gz: ae2bc2e85a57311e5a2a472346665459cae033bb8b44900fdfb1df0043bd3e92a349de2162bfe289515905cff74f4a54b44ec85f06cc765728190922c99b6087
6
+ metadata.gz: c5db43cac2c9605e1e6f217a51116286210d2c359c43fcb59f0dee4e3514b515a94f36d51c65c1c3e226c8dba9b76994727b98e9d02aafeabb199dab510b2745
7
+ data.tar.gz: 2de91bdff7ae7750dc57b3c8a87f5847f4edc123ce9ef2c2b0a8ee933eca18a836e7b500066b058792df7af83e807ecdbd0ee6f90428fba69163e06955825f88
data/CLAUDE.md CHANGED
@@ -10,8 +10,8 @@ This is a Rails form helper gem that provides `tag_field` for creating interacti
10
10
  - `lib/bard/tag_field/form_builder.rb` - Rails form builder integration that handles method signature variants (like Rails' `select` helper)
11
11
  - `lib/bard/tag_field/field.rb` - Core field rendering logic, extends `ActionView::Helpers::Tags::TextField`
12
12
  - `lib/bard/tag_field.rb` - Rails Engine that auto-registers the form builder and precompiles JavaScript assets
13
- - `input-tag/` - JavaScript build directory using Rollup to bundle the `@botandrose/input-tag` package with Bun
14
- - `app/assets/javascripts/input-tag.js` - Compiled JavaScript output for Rails asset pipeline
13
+ - `input-tag/` - The @botandrose/input-tag custom element source (standalone package)
14
+ - `app/assets/javascripts/input-tag.js` - Symlink to `input-tag/dist/input-tag.js` for Rails asset pipeline
15
15
 
16
16
  ## Development Commands
17
17
 
@@ -34,7 +34,7 @@ bundle exec appraisal install
34
34
  # Build JavaScript assets (required before running tests or releasing)
35
35
  cd input-tag && bun run build
36
36
 
37
- # Install Bun dependencies
37
+ # Install bun dependencies
38
38
  cd input-tag && bun install
39
39
 
40
40
  # Clean compiled assets
@@ -96,13 +96,14 @@ Test setup includes a mock Rails application (TestApp) initialized in spec/spec_
96
96
 
97
97
  ## JavaScript Build Process
98
98
 
99
- The gem bundles the `@botandrose/input-tag` package using Rollup with Bun:
100
- 1. Source: `input-tag/index.js` imports from `@botandrose/input-tag`
99
+ The `input-tag/` directory contains the standalone @botandrose/input-tag custom element:
100
+ 1. Source: `input-tag/src/input-tag.js`
101
101
  2. Build: `cd input-tag && bun run build` runs Rollup
102
- 3. Output: `app/assets/javascripts/input-tag.js` for Rails asset pipeline
103
- 4. The Engine precompiles this asset (lib/bard/tag_field.rb:11)
102
+ 3. Output: `input-tag/dist/input-tag.js` (bundled with dependencies)
103
+ 4. Symlink: `app/assets/javascripts/input-tag.js` -> `../../../input-tag/dist/input-tag.js`
104
+ 5. The Engine precompiles this asset (lib/bard/tag_field.rb:11)
104
105
 
105
- **Important:** Always rebuild JavaScript assets after updating the `@botandrose/input-tag` dependency.
106
+ **Important:** Run `cd input-tag && bun run build` after making changes to input-tag source.
106
107
 
107
108
  ## Multi-Rails Version Support
108
109
 
data/Rakefile CHANGED
@@ -9,12 +9,15 @@ Cucumber::Rake::Task.new(:cucumber)
9
9
 
10
10
  task default: [:spec, :cucumber]
11
11
 
12
+ task build: :build_js
13
+
12
14
  desc "Build JavaScript assets"
13
15
  task :build_js do
14
16
  sh "cd input-tag && bun run build"
17
+ cp "input-tag/dist/input-tag.js", "app/assets/javascripts/input-tag.js"
15
18
  end
16
19
 
17
- desc "Install Bun dependencies"
20
+ desc "Install bun dependencies"
18
21
  task :install_deps do
19
22
  sh "cd input-tag && bun install"
20
23
  end
@@ -1154,6 +1154,19 @@ class InputTag extends HTMLElement {
1154
1154
  return this._internals.form;
1155
1155
  }
1156
1156
 
1157
+ _setFormValue(values) {
1158
+ this._internals.value = values;
1159
+
1160
+ const formData = new FormData();
1161
+ values.forEach(value => formData.append(this.name, value));
1162
+ // Always append empty string when no values so server knows to clear the field
1163
+ // (like Rails multiple checkboxes which prepend an empty hidden field)
1164
+ if (values.length === 0) {
1165
+ formData.append(this.name, "");
1166
+ }
1167
+ this._internals.setFormValue(formData);
1168
+ }
1169
+
1157
1170
  get name() {
1158
1171
  return this.getAttribute("name");
1159
1172
  }
@@ -1183,16 +1196,7 @@ class InputTag extends HTMLElement {
1183
1196
  }
1184
1197
 
1185
1198
  const oldValues = this._internals.value;
1186
- this._internals.value = values;
1187
-
1188
- const formData = new FormData();
1189
- values.forEach(value => formData.append(this.name, value));
1190
- // For single mode, append empty string when no values (like standard HTML inputs)
1191
- // For multiple mode, leave empty (like standard HTML multiple selects)
1192
- if (values.length === 0 && !this.multiple) {
1193
- formData.append(this.name, "");
1194
- }
1195
- this._internals.setFormValue(formData);
1199
+ this._setFormValue(values);
1196
1200
 
1197
1201
  // Update taggle to match the new values
1198
1202
  if (this._taggle && this.initialized) {
@@ -1595,16 +1599,7 @@ class InputTag extends HTMLElement {
1595
1599
  // Directly update internals without triggering the setter
1596
1600
  const values = this._taggle.getTagValues();
1597
1601
  const oldValues = this._internals.value;
1598
- this._internals.value = values;
1599
-
1600
- const formData = new FormData();
1601
- values.forEach(value => formData.append(this.name, value));
1602
- // For single mode, append empty string when no values (like standard HTML inputs)
1603
- // For multiple mode, leave empty (like standard HTML multiple selects)
1604
- if (values.length === 0 && !this.multiple) {
1605
- formData.append(this.name, "");
1606
- }
1607
- this._internals.setFormValue(formData);
1602
+ this._setFormValue(values);
1608
1603
 
1609
1604
  if(this.initialized && !this.suppressEvents && JSON.stringify(oldValues) !== JSON.stringify(values)) {
1610
1605
  this.dispatchEvent(new CustomEvent("change", {
@@ -1827,16 +1822,7 @@ class InputTag extends HTMLElement {
1827
1822
  // Update the internal value to match taggle state
1828
1823
  const values = this._taggle.getTagValues();
1829
1824
  const oldValues = this._internals.value;
1830
- this._internals.value = values;
1831
-
1832
- const formData = new FormData();
1833
- values.forEach(value => formData.append(this.name, value));
1834
- // For single mode, append empty string when no values (like standard HTML inputs)
1835
- // For multiple mode, leave empty (like standard HTML multiple selects)
1836
- if (values.length === 0 && !this.multiple) {
1837
- formData.append(this.name, "");
1838
- }
1839
- this._internals.setFormValue(formData);
1825
+ this._setFormValue(values);
1840
1826
 
1841
1827
  // Check validity after updating
1842
1828
  this.checkRequired();
data/input-tag/.gitignore CHANGED
@@ -1,2 +1,6 @@
1
- node_modules
2
-
1
+ /node_modules/
2
+ /coverage
3
+ /dist
4
+ /tmp/*
5
+ !tmp/.gitkeep
6
+ .idea
@@ -0,0 +1,87 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ This is `@botandrose/input-tag`, a declarative, framework-agnostic custom element for tag input with autocomplete functionality. The project consists of:
8
+
9
+ - A form-associated custom element (`input-tag`) with shadow DOM encapsulation
10
+ - Integration with the third-party `taggle` library (modified) for tag management
11
+ - Autocomplete functionality via the `autocompleter` library and HTML datalist elements
12
+ - Comprehensive cross-browser testing using Web Test Runner
13
+
14
+ ## Key Commands
15
+
16
+ **Testing:**
17
+ ```bash
18
+ npm test # Run tests once (default browser)
19
+ npm run test:watch # Run tests in watch mode
20
+ npm run test:all # Run tests across all browsers (chromium, firefox, webkit)
21
+ npm run test:chrome # Run tests in Chrome/Chromium only
22
+ npm run test:firefox # Run tests in Firefox only
23
+ npm run test:webkit # Run tests in Safari/WebKit only
24
+ npm run test:coverage # Run tests with coverage report
25
+ npm run test:ci # CI mode with fail-only plugin
26
+ ```
27
+
28
+ **Single test execution:**
29
+ Web Test Runner supports filtering via `--grep` flag:
30
+ ```bash
31
+ npm test -- --grep "should add tags"
32
+ npm run test:watch -- --grep "autocomplete"
33
+ ```
34
+
35
+ ## Architecture
36
+
37
+ **Core Components:**
38
+ - `src/input-tag.js` - Main custom element class with form integration, event handling, and autocomplete setup
39
+ - `src/taggle.js` - Modified version of Taggle library for tag UI management and interactions
40
+ - `TagOption` class - Custom element for individual tag rendering with shadow DOM
41
+
42
+ **Key Architectural Patterns:**
43
+ - Form-associated custom elements using ElementInternals API for native form integration
44
+ - Shadow DOM encapsulation for both the main element and individual tags
45
+ - Event-driven architecture with `change` and `update` events
46
+ - Declarative configuration via HTML attributes (`multiple`, `required`, `list`)
47
+ - Integration with HTML datalist elements for autocomplete suggestions
48
+
49
+ **State Management:**
50
+ - Tags stored internally in the taggle instance
51
+ - Form value synchronized via ElementInternals
52
+ - Autocomplete suggestions managed by external `autocompleter` library
53
+ - Pre-existing `<tag-option>` elements parsed and converted to initial tags
54
+
55
+ ## Testing Structure
56
+
57
+ Tests are categorized by functionality in separate files:
58
+ - `input-tag.test.js` - Smoke tests and basic element creation
59
+ - `basic-functionality.test.js` - Tag CRUD operations, single/multiple modes
60
+ - `form-integration.test.js` - Form association, validation, reset, FormData
61
+ - `events.test.js` - Event firing, bubbling, timing, and cleanup
62
+ - `autocomplete.test.js` - Datalist integration and suggestion behavior
63
+ - `edge-cases.test.js` - Empty inputs, duplicates, special characters, memory leaks
64
+ - `api-methods.test.js` - Public method validation and error handling
65
+ - `dom-mutation.test.js` - Dynamic DOM changes and element lifecycle
66
+ - `value-label-separation.test.js` - Handling value/label pairs in options
67
+ - `nested-datalist.test.js` - Complex datalist scenarios
68
+
69
+ **Test Utilities:**
70
+ - `test/lib/test-utils.js` - Shared helpers like `setupGlobalTestHooks()` and `waitForElement()`
71
+ - `test/lib/fail-only.mjs` - Plugin preventing `it.only` tests in CI
72
+
73
+ ## Important Implementation Details
74
+
75
+ **Form Integration:**
76
+ Uses ElementInternals API for native form association, validation, and reset handling. The element automatically registers with forms and participates in form submission.
77
+
78
+ **Event System:**
79
+ - `change` events fire when tag collection changes (like native form elements)
80
+ - `update` events provide granular detail about individual tag additions/removals
81
+ - Events properly bubble through shadow DOM boundaries
82
+
83
+ **Browser Compatibility:**
84
+ Tested across Chromium, Firefox, and WebKit. Uses Web Test Runner with Playwright for cross-browser testing. Special handling for Android keyboards (comma key behavior).
85
+
86
+ **Memory Management:**
87
+ Tests include memory leak prevention checks, especially for autocomplete functionality and event listeners in shadow DOM.
data/input-tag/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Bot & Rose
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,135 @@
1
+ # @botandrose/input-tag
2
+
3
+ A declarative, framework-agnostic custom element for tag input with optional autocomplete functionality.
4
+
5
+ Built with appreciation on the excellent foundation of [Taggle.js](https://github.com/okcoker/taggle.js) by Sean Coker.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @botandrose/input-tag
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Import the custom element:
16
+
17
+ ```javascript
18
+ import "@botandrose/input-tag"
19
+ ```
20
+
21
+ Then use it in your HTML:
22
+
23
+ ```html
24
+ <input-tag name="tags" multiple>
25
+ <tag-option value="javascript">JavaScript</tag-option>
26
+ <tag-option value="typescript">TypeScript</tag-option>
27
+ </input-tag>
28
+
29
+ <datalist id="suggestions">
30
+ <option value="react">React</option>
31
+ <option value="vue">Vue</option>
32
+ <option value="angular">Angular</option>
33
+ </datalist>
34
+
35
+ <input-tag name="frameworks" list="suggestions" multiple></input-tag>
36
+ ```
37
+
38
+ ## Features
39
+
40
+ - Form-associated custom element with full form integration
41
+ - Autocomplete support via datalist or custom options
42
+ - Multiple/single value modes
43
+ - Real-time validation
44
+ - Accessible keyboard navigation
45
+ - Shadow DOM encapsulation
46
+ - Framework-agnostic
47
+
48
+ ## API
49
+
50
+ ### Attributes
51
+
52
+ - `name`: Form field name
53
+ - `multiple`: Allow multiple tags (default: single tag mode)
54
+ - `required`: Make field required
55
+ - `list`: ID of datalist for autocomplete suggestions
56
+
57
+ ### Properties
58
+
59
+ - `value`: Get/set tag values - returns **array** when `multiple`, **string** when single mode
60
+ - `tags`: Get current tag values as array (read-only)
61
+ - `options`: Get available autocomplete options from datalist
62
+ - `form`: Reference to associated form element
63
+ - `name`: Get the name attribute value
64
+
65
+ ### Events
66
+
67
+ - `change`: Fired when tag values change
68
+ - `update`: Fired when individual tags are added/removed with detail `{tag, isNew}`
69
+
70
+ ### Methods
71
+
72
+ #### Tag Management
73
+ - `add(tag | tags[])`: Add single tag or array of tags
74
+ - `remove(tag)`: Remove specific tag by value
75
+ - `removeAll()`: Clear all tags
76
+ - `has(tag)`: Check if tag exists
77
+ - `addAt(tag, index)`: Add tag at specific position
78
+
79
+ #### Form Integration
80
+ - `reset()`: Clear all tags and input field
81
+ - `checkValidity()`: Check if field passes validation
82
+ - `reportValidity()`: Check validity and show validation UI
83
+
84
+ #### Interaction
85
+ - `focus()`: Focus the input field
86
+ - `disable()`: Disable the input
87
+ - `enable()`: Enable the input
88
+
89
+ ### JavaScript API Example
90
+
91
+ ```javascript
92
+ // Multiple mode
93
+ const multipleTag = document.querySelector('input-tag[multiple]')
94
+
95
+ // Add tags
96
+ multipleTag.add('react')
97
+ multipleTag.add(['vue', 'angular'])
98
+
99
+ // Get current tags
100
+ console.log(multipleTag.value) // ['react', 'vue', 'angular'] - returns array
101
+ console.log(multipleTag.tags) // ['react', 'vue', 'angular'] - also array
102
+
103
+ // Set all tags at once
104
+ multipleTag.value = ['new', 'tags'] // accepts array
105
+
106
+ // Single mode
107
+ const singleTag = document.querySelector('input-tag:not([multiple])')
108
+
109
+ // Set single tag
110
+ singleTag.value = 'selected-tag' // accepts string
111
+
112
+ // Get current tag
113
+ console.log(singleTag.value) // 'selected-tag' - returns string
114
+ console.log(singleTag.tags) // ['selected-tag'] - always array
115
+
116
+ // Backward compatibility: arrays still work in single mode
117
+ singleTag.value = ['another-tag'] // accepts array, uses first item
118
+ console.log(singleTag.value) // 'another-tag' - still returns string
119
+
120
+ // Form validation
121
+ if (!singleTag.checkValidity()) {
122
+ singleTag.reportValidity()
123
+ }
124
+ ```
125
+
126
+ ## Testing
127
+
128
+ ```bash
129
+ npm test
130
+ npm run test:all
131
+ ```
132
+
133
+ ## License
134
+
135
+ MIT
@@ -0,0 +1,99 @@
1
+ # Testing Guide
2
+
3
+ This project uses Web Test Runner for testing across multiple browsers.
4
+
5
+ ## Running Tests
6
+
7
+ ```bash
8
+ # Run tests once
9
+ npm test
10
+
11
+ # Run tests in watch mode
12
+ npm run test:watch
13
+
14
+ # Run tests in specific browsers
15
+ npm run test:chrome
16
+ npm run test:firefox
17
+ npm run test:webkit
18
+
19
+ # Run tests in all browsers
20
+ npm run test:all
21
+
22
+ # Run tests with coverage
23
+ npm run test:coverage
24
+
25
+ # CI mode (fail-only)
26
+ npm run test:ci
27
+ ```
28
+
29
+ ## Test Structure
30
+
31
+ Tests are organized in the `test/` directory with one file per category:
32
+
33
+ - `test/input-tag.test.js` - Smoke tests for basic functionality
34
+ - `test/basic-functionality.test.js` - Tag creation, removal, single/multiple modes
35
+ - `test/form-integration.test.js` - Form association, validation, reset behavior
36
+ - `test/events.test.js` - Change and update event handling, bubbling, timing
37
+ - `test/autocomplete.test.js` - Datalist integration, filtering, selection
38
+ - `test/edge-cases.test.js` - Empty strings, duplicates, special characters, memory leaks
39
+ - `test/api-methods.test.js` - Public methods, validation, property getters
40
+ - `test/lib/test-utils.js` - Shared test utilities and helpers
41
+ - `test/lib/fail-only.mjs` - Plugin to prevent `it.only` in CI
42
+
43
+ ## Test Categories
44
+
45
+ ### Basic Tag Functionality (`basic-functionality.test.js`)
46
+ - Adding and removing tags programmatically
47
+ - Single vs multiple tag mode behavior
48
+ - Tag display and value management
49
+ - Pre-existing tag-option initialization
50
+ - Maximum tag limits and validation
51
+
52
+ ### Form Integration (`form-integration.test.js`)
53
+ - Form association with ElementInternals
54
+ - HTML validation (required attribute, checkValidity)
55
+ - Form reset behavior and event handling
56
+ - FormData integration for form submission
57
+ - Custom validity and validation messages
58
+
59
+ ### Events (`events.test.js`)
60
+ - Change event firing on tag modifications
61
+ - Update events with detailed tag information
62
+ - Event bubbling and composition across shadow DOM
63
+ - Focus and blur event handling
64
+ - Event timing, sequence, and cleanup
65
+
66
+ ### Autocomplete (`autocomplete.test.js`)
67
+ - Datalist integration and option reading
68
+ - Suggestion filtering and selection behavior
69
+ - Multiple datalist scenarios and switching
70
+ - Missing datalist handling
71
+ - Autocomplete UI interaction and positioning
72
+
73
+ ### Edge Cases (`edge-cases.test.js`)
74
+ - Empty string and whitespace-only input handling
75
+ - Duplicate tag prevention (exact, case-insensitive, whitespace)
76
+ - Very long tag names and input handling
77
+ - Special characters, Unicode, and HTML-like content
78
+ - Rapid input changes and memory leak prevention
79
+ - Invalid HTML scenarios and malformed elements
80
+ - Android keyboard support (comma behavior)
81
+
82
+ ### API Methods (`api-methods.test.js`)
83
+ - Focus method and input targeting
84
+ - Reset method and state clearing
85
+ - Validation methods (checkValidity, reportValidity)
86
+ - Value getter and setter with event handling
87
+ - Property getters (form, name, options)
88
+ - Error handling for invalid parameters
89
+
90
+ ## Browser Compatibility
91
+
92
+ Tests run on:
93
+ - Chromium (Chrome/Edge)
94
+ - Firefox
95
+ - WebKit (Safari)
96
+
97
+ ## Coverage
98
+
99
+ Coverage reports are generated in the `coverage/` directory when running `npm run test:coverage`.