primer_view_components 0.47.0 → 0.49.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 +4 -4
- data/CHANGELOG.md +24 -16
- data/app/assets/javascripts/components/primer/primer.d.ts +1 -0
- data/app/assets/javascripts/lib/primer/forms/character_counter.d.ts +41 -0
- data/app/assets/javascripts/lib/primer/forms/primer_text_area.d.ts +13 -0
- data/app/assets/javascripts/lib/primer/forms/primer_text_field.d.ts +2 -0
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/assets/styles/primer_view_components.css +1 -1
- data/app/assets/styles/primer_view_components.css.map +1 -1
- data/app/components/primer/alpha/action_list.css +1 -1
- data/app/components/primer/alpha/action_list.css.map +1 -1
- data/app/components/primer/alpha/auto_complete/auto_complete.html.erb +8 -6
- data/app/components/primer/alpha/text_area.rb +1 -0
- data/app/components/primer/alpha/text_field.css +1 -1
- data/app/components/primer/alpha/text_field.css.map +1 -1
- data/app/components/primer/alpha/text_field.rb +1 -0
- data/app/components/primer/alpha/toggle_switch.html.erb +2 -2
- data/app/components/primer/alpha/tool_tip.js +12 -5
- data/app/components/primer/alpha/tool_tip.ts +14 -5
- data/app/components/primer/alpha/underline_nav.css +1 -1
- data/app/components/primer/alpha/underline_nav.css.map +1 -1
- data/app/components/primer/beta/auto_complete/item.html.erb +5 -4
- data/app/components/primer/beta/avatar.rb +10 -2
- data/app/components/primer/beta/avatar_stack.css +1 -1
- data/app/components/primer/beta/avatar_stack.css.json +5 -3
- data/app/components/primer/beta/avatar_stack.css.map +1 -1
- data/app/components/primer/beta/avatar_stack.pcss +12 -1
- data/app/components/primer/beta/avatar_stack.rb +6 -0
- data/app/components/primer/beta/button.css +1 -1
- data/app/components/primer/beta/button.css.map +1 -1
- data/app/components/primer/beta/spinner.html.erb +2 -2
- data/app/components/primer/primer.d.ts +1 -0
- data/app/components/primer/primer.js +1 -0
- data/app/components/primer/primer.ts +1 -0
- data/app/forms/text_area_with_character_limit_form.rb +13 -0
- data/app/forms/text_field_with_character_limit_form.rb +13 -0
- data/app/lib/primer/forms/caption.html.erb +16 -9
- data/app/lib/primer/forms/character_counter.d.ts +41 -0
- data/app/lib/primer/forms/character_counter.js +114 -0
- data/app/lib/primer/forms/character_counter.ts +129 -0
- data/app/lib/primer/forms/dsl/input.rb +23 -0
- data/app/lib/primer/forms/dsl/text_area_input.rb +12 -1
- data/app/lib/primer/forms/dsl/text_field_input.rb +10 -1
- data/app/lib/primer/forms/primer_text_area.d.ts +13 -0
- data/app/lib/primer/forms/primer_text_area.js +53 -0
- data/app/lib/primer/forms/primer_text_area.ts +37 -0
- data/app/lib/primer/forms/primer_text_field.d.ts +2 -0
- data/app/lib/primer/forms/primer_text_field.js +16 -2
- data/app/lib/primer/forms/primer_text_field.ts +16 -3
- data/app/lib/primer/forms/text_area.html.erb +6 -4
- data/app/lib/primer/forms/text_field.rb +8 -0
- data/lib/primer/view_components/version.rb +1 -1
- data/previews/primer/alpha/action_menu_preview/submitting_forms.html.erb +1 -1
- data/previews/primer/alpha/text_area_preview.rb +23 -2
- data/previews/primer/alpha/text_field_preview.rb +28 -7
- data/previews/primer/alpha/tree_view_preview/loading_failure.html.erb +53 -1
- data/previews/primer/beta/avatar_stack_preview.rb +9 -0
- data/previews/primer/forms_preview/text_area_with_character_limit_form.html.erb +3 -0
- data/previews/primer/forms_preview/text_field_with_character_limit_form.html.erb +3 -0
- data/previews/primer/forms_preview.rb +6 -0
- data/static/arguments.json +18 -0
- data/static/form_previews.json +10 -0
- data/static/info_arch.json +109 -0
- data/static/previews.json +91 -0
- metadata +14 -2
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared character counting functionality for text inputs with character limits.
|
|
3
|
+
* Handles real-time character count updates, validation, and aria-live announcements.
|
|
4
|
+
*/
|
|
5
|
+
export class CharacterCounter {
|
|
6
|
+
private SCREEN_READER_DELAY: number = 500
|
|
7
|
+
private announceTimeout: number | null = null
|
|
8
|
+
private isInitialLoad: boolean = true
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
private inputElement: HTMLInputElement | HTMLTextAreaElement,
|
|
12
|
+
private characterLimitElement: HTMLElement,
|
|
13
|
+
private characterLimitSrElement: HTMLElement,
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Initialize character counting by setting up event listener and initial count
|
|
18
|
+
*/
|
|
19
|
+
initialize(signal?: AbortSignal): void {
|
|
20
|
+
this.inputElement.addEventListener('keyup', () => this.updateCharacterCount(), signal ? {signal} : undefined) // Keyup used over input for better screen reader support
|
|
21
|
+
this.inputElement.addEventListener(
|
|
22
|
+
'paste',
|
|
23
|
+
() => setTimeout(() => this.updateCharacterCount(), 50), // Gives the pasted content time to register
|
|
24
|
+
signal ? {signal} : undefined,
|
|
25
|
+
)
|
|
26
|
+
this.updateCharacterCount()
|
|
27
|
+
this.isInitialLoad = false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Clean up any pending timeouts
|
|
32
|
+
*/
|
|
33
|
+
cleanup(): void {
|
|
34
|
+
if (this.announceTimeout) {
|
|
35
|
+
clearTimeout(this.announceTimeout)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Pluralizes a word based on the count
|
|
41
|
+
*/
|
|
42
|
+
private pluralize(count: number, string: string): string {
|
|
43
|
+
return count === 1 ? string : `${string}s`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Update the character count display and validation state
|
|
48
|
+
*/
|
|
49
|
+
private updateCharacterCount(): void {
|
|
50
|
+
if (!this.characterLimitElement) return
|
|
51
|
+
|
|
52
|
+
const maxLengthAttr = this.characterLimitElement.getAttribute('data-max-length')
|
|
53
|
+
if (!maxLengthAttr) return
|
|
54
|
+
|
|
55
|
+
const maxLength = parseInt(maxLengthAttr, 10)
|
|
56
|
+
const currentLength = this.inputElement.value.length
|
|
57
|
+
const charactersRemaining = maxLength - currentLength
|
|
58
|
+
let message = ''
|
|
59
|
+
|
|
60
|
+
if (charactersRemaining >= 0) {
|
|
61
|
+
const characterText = this.pluralize(charactersRemaining, 'character')
|
|
62
|
+
message = `${charactersRemaining} ${characterText} remaining`
|
|
63
|
+
const textSpan = this.characterLimitElement.querySelector('.FormControl-caption-text')
|
|
64
|
+
if (textSpan) {
|
|
65
|
+
textSpan.textContent = message
|
|
66
|
+
}
|
|
67
|
+
this.clearError()
|
|
68
|
+
} else {
|
|
69
|
+
const charactersOver = -charactersRemaining
|
|
70
|
+
const characterText = this.pluralize(charactersOver, 'character')
|
|
71
|
+
message = `${charactersOver} ${characterText} over`
|
|
72
|
+
const textSpan = this.characterLimitElement.querySelector('.FormControl-caption-text')
|
|
73
|
+
if (textSpan) {
|
|
74
|
+
textSpan.textContent = message
|
|
75
|
+
}
|
|
76
|
+
this.setError()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// We don't want this announced on initial load
|
|
80
|
+
if (!this.isInitialLoad) {
|
|
81
|
+
this.announceToScreenReader(message)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Announce character count to screen readers with debouncing
|
|
87
|
+
*/
|
|
88
|
+
private announceToScreenReader(message: string): void {
|
|
89
|
+
if (this.announceTimeout) {
|
|
90
|
+
clearTimeout(this.announceTimeout)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.announceTimeout = window.setTimeout(() => {
|
|
94
|
+
if (this.characterLimitSrElement) {
|
|
95
|
+
this.characterLimitSrElement.textContent = message
|
|
96
|
+
}
|
|
97
|
+
}, this.SCREEN_READER_DELAY)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Set error when character limit is exceeded
|
|
102
|
+
*/
|
|
103
|
+
private setError(): void {
|
|
104
|
+
this.inputElement.setAttribute('invalid', 'true')
|
|
105
|
+
this.inputElement.setAttribute('aria-invalid', 'true')
|
|
106
|
+
this.characterLimitElement.classList.add('fgColor-danger')
|
|
107
|
+
|
|
108
|
+
// Show danger icon
|
|
109
|
+
const icon = this.characterLimitElement.querySelector('.FormControl-caption-icon')
|
|
110
|
+
if (icon) {
|
|
111
|
+
icon.removeAttribute('hidden')
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Clear error when back under character limit
|
|
117
|
+
*/
|
|
118
|
+
private clearError(): void {
|
|
119
|
+
this.inputElement.removeAttribute('invalid')
|
|
120
|
+
this.inputElement.removeAttribute('aria-invalid')
|
|
121
|
+
this.characterLimitElement.classList.remove('fgColor-danger')
|
|
122
|
+
|
|
123
|
+
// Hide danger icon
|
|
124
|
+
const icon = this.characterLimitElement.querySelector('.FormControl-caption-icon')
|
|
125
|
+
if (icon) {
|
|
126
|
+
icon.setAttribute('hidden', '')
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -33,6 +33,9 @@ module Primer
|
|
|
33
33
|
# @!macro [new] form_full_width_arguments
|
|
34
34
|
# @param full_width [Boolean] When set to `true`, the field will take up all the horizontal space allowed by its container. Defaults to `true`.
|
|
35
35
|
|
|
36
|
+
# @!macro [new] form_input_character_limit_arguments
|
|
37
|
+
# @param character_limit [Number] Optional character limit for the input. If provided, a character counter will be displayed below the input.
|
|
38
|
+
|
|
36
39
|
# @!macro [new] form_system_arguments
|
|
37
40
|
# @param system_arguments [Hash] A hash of attributes passed to the underlying Rails builder methods. These options may mean something special depending on the type of input, otherwise they are emitted as HTML attributes. See the [Rails documentation](https://guides.rubyonrails.org/form_helpers.html) for more information. In addition, the usual Primer utility arguments are accepted in system arguments. For example, passing `mt: 2` will add the `mt-2` class to the input. See the Primer system arguments docs for details.
|
|
38
41
|
|
|
@@ -112,6 +115,7 @@ module Primer
|
|
|
112
115
|
|
|
113
116
|
@ids = {}.tap do |id_map|
|
|
114
117
|
id_map[:validation] = "validation-#{@base_id}" if supports_validation?
|
|
118
|
+
id_map[:character_limit_caption] = "character_limit-#{@base_id}" if character_limit?
|
|
115
119
|
id_map[:caption] = "caption-#{@base_id}" if caption? || caption_template?
|
|
116
120
|
end
|
|
117
121
|
|
|
@@ -196,6 +200,25 @@ module Primer
|
|
|
196
200
|
form.render_caption_template(caption_template_name)
|
|
197
201
|
end
|
|
198
202
|
|
|
203
|
+
def character_limit?
|
|
204
|
+
false
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def character_limit_id
|
|
208
|
+
ids[:character_limit_caption]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def character_limit_target_prefix
|
|
212
|
+
case type
|
|
213
|
+
when :text_field
|
|
214
|
+
"primer-text-field"
|
|
215
|
+
when :text_area
|
|
216
|
+
"primer-text-area"
|
|
217
|
+
else
|
|
218
|
+
""
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
199
222
|
def valid?
|
|
200
223
|
supports_validation? && validation_messages.empty? && !@invalid
|
|
201
224
|
end
|
|
@@ -5,13 +5,20 @@ module Primer
|
|
|
5
5
|
module Dsl
|
|
6
6
|
# :nodoc:
|
|
7
7
|
class TextAreaInput < Input
|
|
8
|
-
attr_reader :name, :label
|
|
8
|
+
attr_reader :name, :label, :character_limit
|
|
9
9
|
|
|
10
10
|
def initialize(name:, label:, **system_arguments)
|
|
11
11
|
@name = name
|
|
12
12
|
@label = label
|
|
13
|
+
@character_limit = system_arguments.delete(:character_limit)
|
|
14
|
+
|
|
15
|
+
if @character_limit.present? && @character_limit.to_i <= 0
|
|
16
|
+
raise ArgumentError, "character_limit must be a positive integer, got #{@character_limit}"
|
|
17
|
+
end
|
|
13
18
|
|
|
14
19
|
super(**system_arguments)
|
|
20
|
+
|
|
21
|
+
add_input_data(:target, "primer-text-area.inputElement")
|
|
15
22
|
end
|
|
16
23
|
|
|
17
24
|
def to_component
|
|
@@ -22,6 +29,10 @@ module Primer
|
|
|
22
29
|
:text_area
|
|
23
30
|
end
|
|
24
31
|
|
|
32
|
+
def character_limit?
|
|
33
|
+
@character_limit.present?
|
|
34
|
+
end
|
|
35
|
+
|
|
25
36
|
# :nocov:
|
|
26
37
|
def focusable?
|
|
27
38
|
true
|
|
@@ -6,7 +6,7 @@ module Primer
|
|
|
6
6
|
attr_reader(
|
|
7
7
|
*%i[
|
|
8
8
|
name label show_clear_button leading_visual leading_spinner trailing_visual clear_button_id
|
|
9
|
-
visually_hide_label inset monospace field_wrap_classes auto_check_src
|
|
9
|
+
visually_hide_label inset monospace field_wrap_classes auto_check_src character_limit
|
|
10
10
|
]
|
|
11
11
|
)
|
|
12
12
|
|
|
@@ -24,6 +24,11 @@ module Primer
|
|
|
24
24
|
@inset = system_arguments.delete(:inset)
|
|
25
25
|
@monospace = system_arguments.delete(:monospace)
|
|
26
26
|
@auto_check_src = system_arguments.delete(:auto_check_src)
|
|
27
|
+
@character_limit = system_arguments.delete(:character_limit)
|
|
28
|
+
|
|
29
|
+
if @character_limit.present? && @character_limit.to_i <= 0
|
|
30
|
+
raise ArgumentError, "character_limit must be a positive integer, got #{@character_limit}"
|
|
31
|
+
end
|
|
27
32
|
|
|
28
33
|
if @leading_visual
|
|
29
34
|
@leading_visual[:classes] = class_names(
|
|
@@ -67,6 +72,10 @@ module Primer
|
|
|
67
72
|
true
|
|
68
73
|
end
|
|
69
74
|
|
|
75
|
+
def character_limit?
|
|
76
|
+
@character_limit.present?
|
|
77
|
+
end
|
|
78
|
+
|
|
70
79
|
def validation_arguments
|
|
71
80
|
if auto_check_src.present?
|
|
72
81
|
super.merge(
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class PrimerTextAreaElement extends HTMLElement {
|
|
2
|
+
#private;
|
|
3
|
+
inputElement: HTMLTextAreaElement;
|
|
4
|
+
characterLimitElement: HTMLElement;
|
|
5
|
+
characterLimitSrElement: HTMLElement;
|
|
6
|
+
connectedCallback(): void;
|
|
7
|
+
disconnectedCallback(): void;
|
|
8
|
+
}
|
|
9
|
+
declare global {
|
|
10
|
+
interface Window {
|
|
11
|
+
PrimerTextAreaElement: typeof PrimerTextAreaElement;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
8
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
11
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
12
|
+
};
|
|
13
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
14
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
15
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
16
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
17
|
+
};
|
|
18
|
+
var _PrimerTextAreaElement_characterCounter;
|
|
19
|
+
import { controller, target } from '@github/catalyst';
|
|
20
|
+
import { CharacterCounter } from './character_counter';
|
|
21
|
+
let PrimerTextAreaElement = class PrimerTextAreaElement extends HTMLElement {
|
|
22
|
+
constructor() {
|
|
23
|
+
super(...arguments);
|
|
24
|
+
_PrimerTextAreaElement_characterCounter.set(this, null);
|
|
25
|
+
}
|
|
26
|
+
connectedCallback() {
|
|
27
|
+
if (this.characterLimitElement) {
|
|
28
|
+
__classPrivateFieldSet(this, _PrimerTextAreaElement_characterCounter, new CharacterCounter(this.inputElement, this.characterLimitElement, this.characterLimitSrElement), "f");
|
|
29
|
+
__classPrivateFieldGet(this, _PrimerTextAreaElement_characterCounter, "f").initialize();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
disconnectedCallback() {
|
|
33
|
+
__classPrivateFieldGet(this, _PrimerTextAreaElement_characterCounter, "f")?.cleanup();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
_PrimerTextAreaElement_characterCounter = new WeakMap();
|
|
37
|
+
__decorate([
|
|
38
|
+
target
|
|
39
|
+
], PrimerTextAreaElement.prototype, "inputElement", void 0);
|
|
40
|
+
__decorate([
|
|
41
|
+
target
|
|
42
|
+
], PrimerTextAreaElement.prototype, "characterLimitElement", void 0);
|
|
43
|
+
__decorate([
|
|
44
|
+
target
|
|
45
|
+
], PrimerTextAreaElement.prototype, "characterLimitSrElement", void 0);
|
|
46
|
+
PrimerTextAreaElement = __decorate([
|
|
47
|
+
controller
|
|
48
|
+
], PrimerTextAreaElement);
|
|
49
|
+
export { PrimerTextAreaElement };
|
|
50
|
+
if (!window.customElements.get('primer-text-area')) {
|
|
51
|
+
Object.assign(window, { PrimerTextAreaElement });
|
|
52
|
+
window.customElements.define('primer-text-area', PrimerTextAreaElement);
|
|
53
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {controller, target} from '@github/catalyst'
|
|
2
|
+
import {CharacterCounter} from './character_counter'
|
|
3
|
+
|
|
4
|
+
@controller
|
|
5
|
+
export class PrimerTextAreaElement extends HTMLElement {
|
|
6
|
+
@target inputElement: HTMLTextAreaElement
|
|
7
|
+
@target characterLimitElement: HTMLElement
|
|
8
|
+
@target characterLimitSrElement: HTMLElement
|
|
9
|
+
|
|
10
|
+
#characterCounter: CharacterCounter | null = null
|
|
11
|
+
|
|
12
|
+
connectedCallback(): void {
|
|
13
|
+
if (this.characterLimitElement) {
|
|
14
|
+
this.#characterCounter = new CharacterCounter(
|
|
15
|
+
this.inputElement,
|
|
16
|
+
this.characterLimitElement,
|
|
17
|
+
this.characterLimitSrElement,
|
|
18
|
+
)
|
|
19
|
+
this.#characterCounter.initialize()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
disconnectedCallback(): void {
|
|
24
|
+
this.#characterCounter?.cleanup()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare global {
|
|
29
|
+
interface Window {
|
|
30
|
+
PrimerTextAreaElement: typeof PrimerTextAreaElement
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!window.customElements.get('primer-text-area')) {
|
|
35
|
+
Object.assign(window, {PrimerTextAreaElement})
|
|
36
|
+
window.customElements.define('primer-text-area', PrimerTextAreaElement)
|
|
37
|
+
}
|
|
@@ -15,6 +15,8 @@ export declare class PrimerTextFieldElement extends HTMLElement {
|
|
|
15
15
|
validationErrorIcon: HTMLElement;
|
|
16
16
|
leadingVisual: HTMLElement;
|
|
17
17
|
leadingSpinner: HTMLElement;
|
|
18
|
+
characterLimitElement: HTMLElement;
|
|
19
|
+
characterLimitSrElement: HTMLElement;
|
|
18
20
|
connectedCallback(): void;
|
|
19
21
|
disconnectedCallback(): void;
|
|
20
22
|
clearContents(): void;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable custom-elements/expose-class-on-global */
|
|
2
1
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
2
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
3
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -16,13 +15,15 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
16
15
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
17
16
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
18
17
|
};
|
|
19
|
-
var _PrimerTextFieldElement_abortController;
|
|
18
|
+
var _PrimerTextFieldElement_abortController, _PrimerTextFieldElement_characterCounter;
|
|
20
19
|
import '@github/auto-check-element';
|
|
21
20
|
import { controller, target } from '@github/catalyst';
|
|
21
|
+
import { CharacterCounter } from './character_counter';
|
|
22
22
|
let PrimerTextFieldElement = class PrimerTextFieldElement extends HTMLElement {
|
|
23
23
|
constructor() {
|
|
24
24
|
super(...arguments);
|
|
25
25
|
_PrimerTextFieldElement_abortController.set(this, void 0);
|
|
26
|
+
_PrimerTextFieldElement_characterCounter.set(this, null);
|
|
26
27
|
}
|
|
27
28
|
connectedCallback() {
|
|
28
29
|
__classPrivateFieldGet(this, _PrimerTextFieldElement_abortController, "f")?.abort();
|
|
@@ -40,9 +41,15 @@ let PrimerTextFieldElement = class PrimerTextFieldElement extends HTMLElement {
|
|
|
40
41
|
const errorMessage = await event.detail.response.text();
|
|
41
42
|
this.setError(errorMessage);
|
|
42
43
|
}, { signal });
|
|
44
|
+
// Set up character limit tracking if present
|
|
45
|
+
if (this.characterLimitElement) {
|
|
46
|
+
__classPrivateFieldSet(this, _PrimerTextFieldElement_characterCounter, new CharacterCounter(this.inputElement, this.characterLimitElement, this.characterLimitSrElement), "f");
|
|
47
|
+
__classPrivateFieldGet(this, _PrimerTextFieldElement_characterCounter, "f").initialize(signal);
|
|
48
|
+
}
|
|
43
49
|
}
|
|
44
50
|
disconnectedCallback() {
|
|
45
51
|
__classPrivateFieldGet(this, _PrimerTextFieldElement_abortController, "f")?.abort();
|
|
52
|
+
__classPrivateFieldGet(this, _PrimerTextFieldElement_characterCounter, "f")?.cleanup();
|
|
46
53
|
}
|
|
47
54
|
clearContents() {
|
|
48
55
|
this.inputElement.value = '';
|
|
@@ -92,6 +99,7 @@ let PrimerTextFieldElement = class PrimerTextFieldElement extends HTMLElement {
|
|
|
92
99
|
}
|
|
93
100
|
};
|
|
94
101
|
_PrimerTextFieldElement_abortController = new WeakMap();
|
|
102
|
+
_PrimerTextFieldElement_characterCounter = new WeakMap();
|
|
95
103
|
__decorate([
|
|
96
104
|
target
|
|
97
105
|
], PrimerTextFieldElement.prototype, "inputElement", void 0);
|
|
@@ -113,6 +121,12 @@ __decorate([
|
|
|
113
121
|
__decorate([
|
|
114
122
|
target
|
|
115
123
|
], PrimerTextFieldElement.prototype, "leadingSpinner", void 0);
|
|
124
|
+
__decorate([
|
|
125
|
+
target
|
|
126
|
+
], PrimerTextFieldElement.prototype, "characterLimitElement", void 0);
|
|
127
|
+
__decorate([
|
|
128
|
+
target
|
|
129
|
+
], PrimerTextFieldElement.prototype, "characterLimitSrElement", void 0);
|
|
116
130
|
PrimerTextFieldElement = __decorate([
|
|
117
131
|
controller
|
|
118
132
|
], PrimerTextFieldElement);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
/* eslint-disable custom-elements/expose-class-on-global */
|
|
2
|
-
|
|
3
1
|
import '@github/auto-check-element'
|
|
4
2
|
import type {AutoCheckErrorEvent, AutoCheckSuccessEvent} from '@github/auto-check-element'
|
|
5
3
|
import {controller, target} from '@github/catalyst'
|
|
4
|
+
import {CharacterCounter} from './character_counter'
|
|
6
5
|
|
|
7
6
|
declare global {
|
|
8
7
|
interface HTMLElementEventMap {
|
|
@@ -20,8 +19,11 @@ export class PrimerTextFieldElement extends HTMLElement {
|
|
|
20
19
|
@target validationErrorIcon: HTMLElement
|
|
21
20
|
@target leadingVisual: HTMLElement
|
|
22
21
|
@target leadingSpinner: HTMLElement
|
|
22
|
+
@target characterLimitElement: HTMLElement
|
|
23
|
+
@target characterLimitSrElement: HTMLElement
|
|
23
24
|
|
|
24
25
|
#abortController: AbortController | null
|
|
26
|
+
#characterCounter: CharacterCounter | null = null
|
|
25
27
|
|
|
26
28
|
connectedCallback(): void {
|
|
27
29
|
this.#abortController?.abort()
|
|
@@ -48,16 +50,27 @@ export class PrimerTextFieldElement extends HTMLElement {
|
|
|
48
50
|
},
|
|
49
51
|
{signal},
|
|
50
52
|
)
|
|
53
|
+
|
|
54
|
+
// Set up character limit tracking if present
|
|
55
|
+
if (this.characterLimitElement) {
|
|
56
|
+
this.#characterCounter = new CharacterCounter(
|
|
57
|
+
this.inputElement,
|
|
58
|
+
this.characterLimitElement,
|
|
59
|
+
this.characterLimitSrElement,
|
|
60
|
+
)
|
|
61
|
+
this.#characterCounter.initialize(signal)
|
|
62
|
+
}
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
disconnectedCallback() {
|
|
54
66
|
this.#abortController?.abort()
|
|
67
|
+
this.#characterCounter?.cleanup()
|
|
55
68
|
}
|
|
56
69
|
|
|
57
70
|
clearContents() {
|
|
58
71
|
this.inputElement.value = ''
|
|
59
72
|
this.inputElement.focus()
|
|
60
|
-
this.inputElement.dispatchEvent(new Event('input', {
|
|
73
|
+
this.inputElement.dispatchEvent(new Event('input', {bubbles: true, cancelable: false}))
|
|
61
74
|
}
|
|
62
75
|
|
|
63
76
|
clearError(): void {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
<%=
|
|
3
|
-
<%=
|
|
1
|
+
<primer-text-area>
|
|
2
|
+
<%= render(FormControl.new(input: @input)) do %>
|
|
3
|
+
<%= content_tag(:div, **@field_wrap_arguments) do %>
|
|
4
|
+
<%= builder.text_area(@input.name, **@input.input_arguments) %>
|
|
5
|
+
<% end %>
|
|
4
6
|
<% end %>
|
|
5
|
-
|
|
7
|
+
</primer-text-area>
|
|
@@ -77,6 +77,14 @@ module Primer
|
|
|
77
77
|
Primer::Beta::Truncate.new(**truncate_arguments).with_content(text)
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
|
+
|
|
81
|
+
def character_limit_validation_arguments
|
|
82
|
+
{
|
|
83
|
+
class: "FormControl-inlineValidation",
|
|
84
|
+
id: @input.character_limit_validation_id,
|
|
85
|
+
hidden: true
|
|
86
|
+
}
|
|
87
|
+
end
|
|
80
88
|
end
|
|
81
89
|
end
|
|
82
90
|
end
|
|
@@ -16,6 +16,7 @@ module Primer
|
|
|
16
16
|
# @param disabled toggle
|
|
17
17
|
# @param invalid toggle
|
|
18
18
|
# @param validation_message text
|
|
19
|
+
# @param character_limit number
|
|
19
20
|
def playground(
|
|
20
21
|
name: "my-text-area",
|
|
21
22
|
id: "my-text-area",
|
|
@@ -26,7 +27,8 @@ module Primer
|
|
|
26
27
|
full_width: true,
|
|
27
28
|
disabled: false,
|
|
28
29
|
invalid: false,
|
|
29
|
-
validation_message: nil
|
|
30
|
+
validation_message: nil,
|
|
31
|
+
character_limit: nil
|
|
30
32
|
)
|
|
31
33
|
system_arguments = {
|
|
32
34
|
name: name,
|
|
@@ -38,7 +40,8 @@ module Primer
|
|
|
38
40
|
full_width: full_width,
|
|
39
41
|
disabled: disabled,
|
|
40
42
|
invalid: invalid,
|
|
41
|
-
validation_message: validation_message
|
|
43
|
+
validation_message: validation_message,
|
|
44
|
+
character_limit: character_limit
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
render(Primer::Alpha::TextArea.new(**system_arguments))
|
|
@@ -93,6 +96,24 @@ module Primer
|
|
|
93
96
|
def with_validation_message
|
|
94
97
|
render(Primer::Alpha::TextArea.new(validation_message: "An error occurred!", name: "my-text-area", label: "Tell me about yourself"))
|
|
95
98
|
end
|
|
99
|
+
|
|
100
|
+
# @label With character limit
|
|
101
|
+
# @snapshot interactive
|
|
102
|
+
def with_character_limit
|
|
103
|
+
render(Primer::Alpha::TextArea.new(character_limit: 10, name: "my-text-area", label: "Tell me about yourself"))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @label With character limit, over limit
|
|
107
|
+
# @snapshot interactive
|
|
108
|
+
def with_character_limit_over_limit
|
|
109
|
+
render(Primer::Alpha::TextArea.new(character_limit: 10, name: "my-text-area", label: "Tell me about yourself", value: "This text is definitely over the limit."))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @label With character limit and caption
|
|
113
|
+
# @snapshot
|
|
114
|
+
def with_character_limit_and_caption
|
|
115
|
+
render(Primer::Alpha::TextArea.new(character_limit: 100, caption: "With a caption.", name: "my-text-area", label: "Tell me about yourself"))
|
|
116
|
+
end
|
|
96
117
|
#
|
|
97
118
|
# @!endgroup
|
|
98
119
|
end
|
|
@@ -24,6 +24,7 @@ module Primer
|
|
|
24
24
|
# @param monospace toggle
|
|
25
25
|
# @param leading_visual_icon octicon
|
|
26
26
|
# @param leading_spinner toggle
|
|
27
|
+
# @param character_limit number
|
|
27
28
|
def playground(
|
|
28
29
|
name: "my-text-field",
|
|
29
30
|
id: "my-text-field",
|
|
@@ -42,7 +43,8 @@ module Primer
|
|
|
42
43
|
inset: false,
|
|
43
44
|
monospace: false,
|
|
44
45
|
leading_visual_icon: nil,
|
|
45
|
-
leading_spinner: false
|
|
46
|
+
leading_spinner: false,
|
|
47
|
+
character_limit: nil
|
|
46
48
|
)
|
|
47
49
|
system_arguments = {
|
|
48
50
|
name: name,
|
|
@@ -61,7 +63,8 @@ module Primer
|
|
|
61
63
|
placeholder: placeholder,
|
|
62
64
|
inset: inset,
|
|
63
65
|
monospace: monospace,
|
|
64
|
-
leading_spinner: leading_spinner
|
|
66
|
+
leading_spinner: leading_spinner,
|
|
67
|
+
character_limit: character_limit
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
if leading_visual_icon
|
|
@@ -197,7 +200,7 @@ module Primer
|
|
|
197
200
|
end
|
|
198
201
|
|
|
199
202
|
# @label With trailing label
|
|
200
|
-
# @snapshot
|
|
203
|
+
# @snapshot
|
|
201
204
|
def with_trailing_label
|
|
202
205
|
render(Primer::Alpha::TextField.new(trailing_visual: { label: { text: "Hello" } }, name: "my-text-field-15", label: "My text field"))
|
|
203
206
|
end
|
|
@@ -213,6 +216,24 @@ module Primer
|
|
|
213
216
|
def with_validation_message
|
|
214
217
|
render(Primer::Alpha::TextField.new(validation_message: "An error occurred!", name: "my-text-field-17", label: "My text field"))
|
|
215
218
|
end
|
|
219
|
+
|
|
220
|
+
# @label With character limit
|
|
221
|
+
# @snapshot interactive
|
|
222
|
+
def with_character_limit
|
|
223
|
+
render(Primer::Alpha::TextField.new(character_limit: 10, name: "my-text-field-18", label: "Username"))
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# @label With character limit, over limit
|
|
227
|
+
# @snapshot interactive
|
|
228
|
+
def with_character_limit_over_limit
|
|
229
|
+
render(Primer::Alpha::TextField.new(character_limit: 10, name: "my-text-field-19", label: "Tell me about yourself", value: "This text is definitely over the limit."))
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# @label With character limit and caption
|
|
233
|
+
# @snapshot
|
|
234
|
+
def with_character_limit_and_caption
|
|
235
|
+
render(Primer::Alpha::TextField.new(character_limit: 20, caption: "Choose a unique username.", name: "my-text-field-20", label: "Username"))
|
|
236
|
+
end
|
|
216
237
|
#
|
|
217
238
|
# @!endgroup
|
|
218
239
|
|
|
@@ -220,24 +241,24 @@ module Primer
|
|
|
220
241
|
#
|
|
221
242
|
# @label Auto check request ok
|
|
222
243
|
def with_auto_check_ok
|
|
223
|
-
render(Primer::Alpha::TextField.new(auto_check_src: UrlHelpers.primer_view_components.example_check_ok_path, name: "my-text-field-
|
|
244
|
+
render(Primer::Alpha::TextField.new(auto_check_src: UrlHelpers.primer_view_components.example_check_ok_path, name: "my-text-field-21", label: "My text field"))
|
|
224
245
|
end
|
|
225
246
|
|
|
226
247
|
# @label Auto check request accepted
|
|
227
248
|
def with_auto_check_accepted
|
|
228
|
-
render(Primer::Alpha::TextField.new(auto_check_src: UrlHelpers.primer_view_components.example_check_accepted_path, name: "my-text-field-
|
|
249
|
+
render(Primer::Alpha::TextField.new(auto_check_src: UrlHelpers.primer_view_components.example_check_accepted_path, name: "my-text-field-22", label: "My text field"))
|
|
229
250
|
end
|
|
230
251
|
|
|
231
252
|
# @label Auto check request error
|
|
232
253
|
def with_auto_check_error
|
|
233
|
-
render(Primer::Alpha::TextField.new(auto_check_src: UrlHelpers.primer_view_components.example_check_error_path, name: "my-text-field-
|
|
254
|
+
render(Primer::Alpha::TextField.new(auto_check_src: UrlHelpers.primer_view_components.example_check_error_path, name: "my-text-field-23", label: "My text field"))
|
|
234
255
|
end
|
|
235
256
|
#
|
|
236
257
|
# @!endgroup
|
|
237
258
|
|
|
238
259
|
# @label With data target attribute
|
|
239
260
|
def with_data_target
|
|
240
|
-
render(Primer::Alpha::TextField.new(name: "my-text-field", label: "My text field", data: { target: "custom-component.inputElement" }))
|
|
261
|
+
render(Primer::Alpha::TextField.new(name: "my-text-field-24", label: "My text field", data: { target: "custom-component.inputElement" }))
|
|
241
262
|
end
|
|
242
263
|
#
|
|
243
264
|
# @!endgroup
|