openproject-primer_view_components 0.80.2 → 0.81.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 +36 -22
- data/README.md +20 -1
- 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/item.rb +2 -1
- data/app/components/primer/alpha/auto_complete/auto_complete.html.erb +8 -6
- data/app/components/primer/alpha/select_panel.rb +1 -1
- data/app/components/primer/alpha/select_panel_element.js +1 -1
- data/app/components/primer/alpha/select_panel_element.ts +1 -1
- data/app/components/primer/alpha/stack.rb +1 -0
- data/app/components/primer/alpha/tab_nav.css +1 -1
- data/app/components/primer/alpha/tab_nav.css.json +1 -0
- data/app/components/primer/alpha/tab_nav.css.map +1 -1
- data/app/components/primer/alpha/tab_nav.pcss +7 -1
- data/app/components/primer/alpha/text_area.rb +1 -0
- 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/beta/auto_complete/item.html.erb +5 -4
- data/app/components/primer/beta/avatar.rb +4 -0
- data/app/components/primer/beta/avatar_stack.rb +6 -0
- data/app/components/primer/beta/blankslate.css +1 -1
- data/app/components/primer/beta/blankslate.css.map +1 -1
- data/app/components/primer/beta/blankslate.pcss +2 -0
- 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/controllers/primer/view_components/toggle_switch_controller.rb +2 -2
- data/app/forms/check_box_with_nested_form.rb +9 -5
- 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/check_box.rb +28 -0
- data/app/lib/primer/forms/dsl/input.rb +23 -0
- data/app/lib/primer/forms/dsl/multi_input.rb +3 -1
- data/app/lib/primer/forms/dsl/text_area_input.rb +12 -1
- data/app/lib/primer/forms/dsl/text_field_input.rb +11 -1
- data/app/lib/primer/forms/form_control.html.erb +2 -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.html.erb +1 -1
- data/app/lib/primer/forms/text_field.rb +8 -0
- data/lib/primer/accessibility.rb +9 -3
- data/lib/primer/view_components/engine.rb +1 -4
- data/lib/primer/view_components/version.rb +2 -2
- data/previews/primer/alpha/action_menu_preview/submitting_forms.html.erb +1 -1
- data/previews/primer/alpha/form_control_preview/playground.html.erb +4 -2
- data/previews/primer/alpha/octicon_symbols_preview/playground.html.erb +1 -1
- data/previews/primer/alpha/overlay_preview.rb +0 -25
- data/previews/primer/alpha/text_area_preview.rb +29 -8
- data/previews/primer/alpha/text_field_preview.rb +34 -4
- data/previews/primer/alpha/toggle_switch_preview.rb +14 -14
- data/previews/primer/alpha/tree_view_preview/loading_failure.html.erb +53 -1
- data/previews/primer/alpha/tree_view_preview/stress_test.html.erb +43 -0
- data/previews/primer/beta/button_preview/all_schemes.html.erb +1 -1
- data/previews/primer/beta/button_preview/invisible_all_visuals.html.erb +1 -1
- data/previews/primer/beta/button_preview/summary_as_button.html.erb +10 -1
- 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 +13 -1
- data/static/form_previews.json +10 -0
- data/static/info_arch.json +106 -15
- data/static/previews.json +93 -14
- metadata +15 -2
|
@@ -302,7 +302,8 @@ module Primer
|
|
|
302
302
|
if @list.allows_selection?
|
|
303
303
|
@content_arguments[:aria] = merge_aria(
|
|
304
304
|
@content_arguments,
|
|
305
|
-
|
|
305
|
+
# Always use aria-selected for listbox (role="option" requires aria-selected per WAI-ARIA 1.2)
|
|
306
|
+
{ aria: @list.acts_as_listbox? || @list.aria_selection_variant == :selected ? { selected: active? } : { checked: active? } }
|
|
306
307
|
)
|
|
307
308
|
end
|
|
308
309
|
|
|
@@ -7,16 +7,18 @@
|
|
|
7
7
|
<% end %>
|
|
8
8
|
</label>
|
|
9
9
|
<span class="autocomplete-body">
|
|
10
|
+
<% clear_button = @is_clearable ? capture do %>
|
|
11
|
+
<button id="<%= @input_id %>-clear" class="btn-octicon" aria-label="Clear"><%= primer_octicon "x" %></button>
|
|
12
|
+
<% end : nil %>
|
|
10
13
|
<% if @with_icon %>
|
|
11
14
|
<div class="form-control autocomplete-embedded-icon-wrap">
|
|
12
15
|
<%= render Primer::Beta::Octicon.new(:search) %>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<% if @is_clearable %>
|
|
16
|
-
<button id="<%= @input_id %>-clear" class="btn-octicon" aria-label="Clear"><%= primer_octicon "x" %></button>
|
|
17
|
-
<% end %>
|
|
18
|
-
<% if @with_icon %>
|
|
16
|
+
<%= input %>
|
|
17
|
+
<%= clear_button %>
|
|
19
18
|
</div>
|
|
19
|
+
<% else %>
|
|
20
|
+
<%= input %>
|
|
21
|
+
<%= clear_button %>
|
|
20
22
|
<% end %>
|
|
21
23
|
<%= results %>
|
|
22
24
|
</span>
|
|
@@ -256,7 +256,7 @@ module Primer
|
|
|
256
256
|
super(
|
|
257
257
|
p: 2,
|
|
258
258
|
role: "listbox",
|
|
259
|
-
aria_selection_variant:
|
|
259
|
+
aria_selection_variant: :selected,
|
|
260
260
|
select_variant: select_variant == :multiple ? :multiple_checkbox : :single,
|
|
261
261
|
**system_arguments
|
|
262
262
|
)
|
|
@@ -84,7 +84,7 @@ let SelectPanelElement = class SelectPanelElement extends HTMLElement {
|
|
|
84
84
|
return this.getAttribute('data-select-variant');
|
|
85
85
|
}
|
|
86
86
|
get ariaSelectionType() {
|
|
87
|
-
return
|
|
87
|
+
return 'aria-selected';
|
|
88
88
|
}
|
|
89
89
|
set selectVariant(variant) {
|
|
90
90
|
if (variant) {
|
|
@@ -97,7 +97,7 @@ export class SelectPanelElement extends HTMLElement {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
get ariaSelectionType(): string {
|
|
100
|
-
return
|
|
100
|
+
return 'aria-selected'
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
set selectVariant(variant: SelectVariant) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.tabnav{border-bottom:var(--borderWidth-thin) solid var(--borderColor-default);margin-bottom:var(--stack-gap-normal);margin-top:0}.tabnav-tabs{display:flex;margin-bottom:calc(var(--borderWidth-thin)*-1);overflow:hidden}.tabnav-tab{background-color:initial;border:var(--borderWidth-thin) solid #0000;border-bottom:0;color:var(--fgColor-muted);display:inline-block;
|
|
1
|
+
.tabnav{border-bottom:var(--borderWidth-thin) solid var(--borderColor-default);margin-bottom:var(--stack-gap-normal);margin-top:0}.tabnav-tabs{display:flex;margin-bottom:calc(var(--borderWidth-thin)*-1);overflow-x:auto;overflow-y:hidden}.tabnav-tab,.tabnav-tabs>li{flex-shrink:0}.tabnav-tab{background-color:initial;border:var(--borderWidth-thin) solid #0000;border-bottom:0;color:var(--fgColor-muted);display:inline-block;font-size:var(--text-body-size-medium);line-height:23px;padding:var(--base-size-8) var(--control-medium-paddingInline-spacious);-webkit-text-decoration:none;text-decoration:none;transition:color .2s cubic-bezier(.3,0,.5,1)}.tabnav-tab.selected,.tabnav-tab[aria-current]:not([aria-current=false]),.tabnav-tab[aria-selected=true]{background-color:var(--bgColor-default);border-color:var(--borderColor-default);border-radius:var(--borderRadius-medium) var(--borderRadius-medium) 0 0;color:var(--fgColor-default)}:is(.tabnav-tab.selected,.tabnav-tab[aria-selected=true],.tabnav-tab[aria-current]:not([aria-current=false])) .octicon{color:inherit}.tabnav-tab:hover{color:var(--fgColor-default);-webkit-text-decoration:none;text-decoration:none;transition-duration:.1s}.tabnav-tab:focus,.tabnav-tab:focus-visible{border-radius:var(--borderRadius-medium) var(--borderRadius-medium) 0 0!important;outline-offset:-6px}.tabnav-tab .octicon,.tabnav-tab:active{color:var(--fgColor-muted)}.tabnav-tab .octicon{margin-right:var(--control-small-gap)}.tabnav-tab .Counter{color:inherit;margin-left:var(--control-small-gap)}.tabnav-extra{color:var(--fgColor-muted);display:inline-block;font-size:var(--text-body-size-small);margin-left:10px;padding-top:10px}.tabnav-extra>.octicon{margin-right:var(--base-size-2)}a.tabnav-extra:hover{color:var(--fgColor-accent);-webkit-text-decoration:none;text-decoration:none}.tabnav-btn{margin-left:var(--controlStack-medium-gap-condensed)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["tab_nav.pcss"],"names":[],"mappings":"AAGA,QAIE,sEAAuE,CADvE,qCAAsC,CAFtC,YAIF,CAEA,aACE,YAAa,CAEb,8CAAiD,CACjD,
|
|
1
|
+
{"version":3,"sources":["tab_nav.pcss"],"names":[],"mappings":"AAGA,QAIE,sEAAuE,CADvE,qCAAsC,CAFtC,YAIF,CAEA,aACE,YAAa,CAEb,8CAAiD,CACjD,eAAgB,CAChB,iBAMF,CAEA,4BAJI,aA2DJ,CAvDA,YAUE,wBAA6B,CAE7B,0CAAgB,CAAhB,eAAgB,CAJhB,0BAA2B,CAP3B,oBAAqB,CAIrB,sCAAuC,CAEvC,gBAAiB,CAHjB,uEAAwE,CAKxE,4BAAqB,CAArB,oBAAqB,CAIrB,4CA0CF,CAxCE,yGAIE,uCAAwC,CACxC,uCAAwC,CACxC,uEAAwE,CAHxE,4BAQF,CAHE,uHACE,aACF,CAGF,kBACE,4BAA6B,CAC7B,4BAAqB,CAArB,oBAAqB,CACrB,uBACF,CAEA,4CAEE,iFAAmF,CACnF,mBACF,CAMA,wCAHE,0BAOF,CAJA,qBAEE,qCAEF,CAEA,qBAGE,aAAc,CADd,oCAEF,CAQF,cAOE,0BAA2B,CAN3B,oBAAqB,CAKrB,qCAAsC,CADtC,gBAAiB,CAFjB,gBASF,CAHE,uBACE,+BACF,CAKF,qBACE,2BAA4B,CAC5B,4BAAqB,CAArB,oBACF,CAOA,YAEE,oDACF","file":"tab_nav.css","sourcesContent":["/* tabnav */\n\n/* Outer wrapper */\n.tabnav {\n margin-top: 0;\n /* stylelint-disable-next-line primer/spacing */\n margin-bottom: var(--stack-gap-normal);\n border-bottom: var(--borderWidth-thin) solid var(--borderColor-default);\n}\n\n.tabnav-tabs {\n display: flex;\n /* stylelint-disable-next-line primer/spacing */\n margin-bottom: calc(var(--borderWidth-thin) * -1);\n overflow-x: auto;\n overflow-y: hidden;\n\n /* stylelint-disable-next-line selector-max-type */\n & > li {\n flex-shrink: 0;\n }\n}\n\n.tabnav-tab {\n display: inline-block;\n flex-shrink: 0;\n /* stylelint-disable-next-line primer/spacing */\n padding: var(--base-size-8) var(--control-medium-paddingInline-spacious);\n font-size: var(--text-body-size-medium);\n /* stylelint-disable-next-line primer/typography */\n line-height: 23px;\n color: var(--fgColor-muted);\n text-decoration: none;\n background-color: transparent;\n border: var(--borderWidth-thin) solid transparent;\n border-bottom: 0;\n transition: color 0.2s cubic-bezier(0.3, 0, 0.5, 1);\n\n &.selected,\n &[aria-selected='true'],\n &[aria-current]:not([aria-current='false']) {\n color: var(--fgColor-default);\n background-color: var(--bgColor-default); /* cover bottom border */\n border-color: var(--borderColor-default);\n border-radius: var(--borderRadius-medium) var(--borderRadius-medium) 0 0;\n\n & .octicon {\n color: inherit;\n }\n }\n\n &:hover {\n color: var(--fgColor-default);\n text-decoration: none;\n transition-duration: 0.1s;\n }\n\n &:focus,\n &:focus-visible {\n border-radius: var(--borderRadius-medium) var(--borderRadius-medium) 0 0 !important;\n outline-offset: -6px;\n }\n\n &:active {\n color: var(--fgColor-muted);\n }\n\n & .octicon {\n /* stylelint-disable-next-line primer/spacing */\n margin-right: var(--control-small-gap);\n color: var(--fgColor-muted);\n }\n\n & .Counter {\n /* stylelint-disable-next-line primer/spacing */\n margin-left: var(--control-small-gap);\n color: inherit;\n }\n}\n\n/* Tabnav extras\n**\n** Tabnav extras are non-tab elements that sit in the tabnav. Usually they're\n** inline text or links. */\n\n.tabnav-extra {\n display: inline-block;\n /* stylelint-disable-next-line primer/spacing */\n padding-top: 10px;\n /* stylelint-disable-next-line primer/spacing */\n margin-left: 10px;\n font-size: var(--text-body-size-small);\n color: var(--fgColor-muted);\n\n & > .octicon {\n margin-right: var(--base-size-2);\n }\n}\n\n/* When tabnav-extra are anchors */\n/* stylelint-disable-next-line selector-no-qualifying-type, selector-max-type */\na.tabnav-extra:hover {\n color: var(--fgColor-accent);\n text-decoration: none;\n}\n\n/* Tabnav buttons\n**\n** For when there are multiple buttons, space them out appropriately. Requires\n** the buttons to be floated or inline-block. */\n\n.tabnav-btn {\n /* stylelint-disable-next-line primer/spacing */\n margin-left: var(--controlStack-medium-gap-condensed);\n}\n"]}
|
|
@@ -12,7 +12,13 @@
|
|
|
12
12
|
display: flex;
|
|
13
13
|
/* stylelint-disable-next-line primer/spacing */
|
|
14
14
|
margin-bottom: calc(var(--borderWidth-thin) * -1);
|
|
15
|
-
overflow:
|
|
15
|
+
overflow-x: auto;
|
|
16
|
+
overflow-y: hidden;
|
|
17
|
+
|
|
18
|
+
/* stylelint-disable-next-line selector-max-type */
|
|
19
|
+
& > li {
|
|
20
|
+
flex-shrink: 0;
|
|
21
|
+
}
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
.tabnav-tab {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
aria: { hidden: true },
|
|
22
22
|
focusable: false
|
|
23
23
|
)) do %>
|
|
24
|
-
<path fill-rule="evenodd" d="M8 2a.75.75 0 0 1 .75.75v11.5a.75.75 0 0 1-1.5 0V2.75A.75.75 0 0 1 8 2Z"
|
|
24
|
+
<path fill-rule="evenodd" d="M8 2a.75.75 0 0 1 .75.75v11.5a.75.75 0 0 1-1.5 0V2.75A.75.75 0 0 1 8 2Z"></path>
|
|
25
25
|
<% end %>
|
|
26
26
|
<% end %>
|
|
27
27
|
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
aria: { hidden: true },
|
|
37
37
|
focusable: false
|
|
38
38
|
)) do %>
|
|
39
|
-
<path fill-rule="evenodd" d="M8 12.5a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9ZM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12Z"
|
|
39
|
+
<path fill-rule="evenodd" d="M8 12.5a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9ZM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12Z"></path>
|
|
40
40
|
<% end %>
|
|
41
41
|
<% end %>
|
|
42
42
|
<% end %>
|
|
@@ -9,7 +9,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
9
9
|
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");
|
|
10
10
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
11
11
|
};
|
|
12
|
-
var _ToolTipElement_instances, _ToolTipElement_abortController, _ToolTipElement_align, _ToolTipElement_side, _ToolTipElement_allowUpdatePosition, _ToolTipElement_showReason, _ToolTipElement_update, _ToolTipElement_updateControl, _ToolTipElement_updateControlReference, _ToolTipElement_updateDirection, _ToolTipElement_updatePosition;
|
|
12
|
+
var _ToolTipElement_instances, _ToolTipElement_abortController, _ToolTipElement_align, _ToolTipElement_side, _ToolTipElement_allowUpdatePosition, _ToolTipElement_showReason, _ToolTipElement_isControlsPopoverOpen, _ToolTipElement_update, _ToolTipElement_updateControl, _ToolTipElement_updateControlReference, _ToolTipElement_updateDirection, _ToolTipElement_updatePosition;
|
|
13
13
|
import '@oddbird/popover-polyfill';
|
|
14
14
|
import { getAnchoredPosition } from '@primer/behaviors';
|
|
15
15
|
const isPopoverOpen = (() => {
|
|
@@ -78,6 +78,7 @@ class ToolTipElement extends HTMLElement {
|
|
|
78
78
|
_ToolTipElement_side.set(this, 'outside-bottom');
|
|
79
79
|
_ToolTipElement_allowUpdatePosition.set(this, false);
|
|
80
80
|
_ToolTipElement_showReason.set(this, 'mouse');
|
|
81
|
+
_ToolTipElement_isControlsPopoverOpen.set(this, false);
|
|
81
82
|
}
|
|
82
83
|
styles() {
|
|
83
84
|
return `
|
|
@@ -244,18 +245,24 @@ class ToolTipElement extends HTMLElement {
|
|
|
244
245
|
if (!this.control)
|
|
245
246
|
return;
|
|
246
247
|
const showing = isPopoverOpen(this);
|
|
248
|
+
// Track when the control's popover (e.g an ActionMenu) is opened/closed
|
|
249
|
+
if (event.type === 'beforetoggle' && event.currentTarget !== this) {
|
|
250
|
+
__classPrivateFieldSet(this, _ToolTipElement_isControlsPopoverOpen, event.newState === 'open', "f");
|
|
251
|
+
}
|
|
247
252
|
// Ensures that tooltip stays open when hovering between tooltip and element
|
|
248
253
|
// WCAG Success Criterion 1.4.13 Hoverable
|
|
249
|
-
const shouldShow = event.type === 'mouseenter' ||
|
|
254
|
+
const shouldShow = (event.type === 'mouseenter' ||
|
|
250
255
|
// Only show tooltip on focus if running in headless browser (for tests) or if focus ring
|
|
251
256
|
// is visible (i.e. if user is using keyboard navigation)
|
|
252
|
-
(event.type === 'focus' && (navigator.webdriver || this.control.matches(':focus-visible')))
|
|
257
|
+
(event.type === 'focus' && (navigator.webdriver || this.control.matches(':focus-visible')))) &&
|
|
258
|
+
// Don't show tooltip if the control's popover is open (e.g. an ActionMenu)
|
|
259
|
+
!__classPrivateFieldGet(this, _ToolTipElement_isControlsPopoverOpen, "f");
|
|
253
260
|
const isMouseLeaveFromButton = event.type === 'mouseleave' &&
|
|
254
261
|
event.relatedTarget !== this.control &&
|
|
255
262
|
event.relatedTarget !== this;
|
|
256
263
|
const isEscapeKeydown = event.type === 'keydown' && event.key === 'Escape';
|
|
257
264
|
const isMouseDownOnButton = event.type === 'mousedown' && event.currentTarget === this.control;
|
|
258
|
-
const isOpeningOtherPopover = event.type === 'beforetoggle' && event.currentTarget !== this;
|
|
265
|
+
const isOpeningOtherPopover = event.type === 'beforetoggle' && event.currentTarget !== this && event.newState === 'open';
|
|
259
266
|
const shouldHide = isMouseLeaveFromButton || isEscapeKeydown || isMouseDownOnButton || isOpeningOtherPopover;
|
|
260
267
|
if (showing && isEscapeKeydown) {
|
|
261
268
|
/* eslint-disable-next-line no-restricted-syntax */
|
|
@@ -288,7 +295,7 @@ class ToolTipElement extends HTMLElement {
|
|
|
288
295
|
}
|
|
289
296
|
}
|
|
290
297
|
}
|
|
291
|
-
_ToolTipElement_abortController = new WeakMap(), _ToolTipElement_align = new WeakMap(), _ToolTipElement_side = new WeakMap(), _ToolTipElement_allowUpdatePosition = new WeakMap(), _ToolTipElement_showReason = new WeakMap(), _ToolTipElement_instances = new WeakSet(), _ToolTipElement_update = function _ToolTipElement_update(isOpen) {
|
|
298
|
+
_ToolTipElement_abortController = new WeakMap(), _ToolTipElement_align = new WeakMap(), _ToolTipElement_side = new WeakMap(), _ToolTipElement_allowUpdatePosition = new WeakMap(), _ToolTipElement_showReason = new WeakMap(), _ToolTipElement_isControlsPopoverOpen = new WeakMap(), _ToolTipElement_instances = new WeakSet(), _ToolTipElement_update = function _ToolTipElement_update(isOpen) {
|
|
292
299
|
if (isOpen) {
|
|
293
300
|
openTooltips.add(this);
|
|
294
301
|
this.classList.remove(TOOLTIP_SR_ONLY_CLASS);
|
|
@@ -174,6 +174,7 @@ class ToolTipElement extends HTMLElement {
|
|
|
174
174
|
#side: AnchorSide = 'outside-bottom'
|
|
175
175
|
#allowUpdatePosition = false
|
|
176
176
|
#showReason: 'focus' | 'mouse' = 'mouse'
|
|
177
|
+
#isControlsPopoverOpen = false
|
|
177
178
|
get showReason() {
|
|
178
179
|
return this.#showReason
|
|
179
180
|
}
|
|
@@ -247,20 +248,28 @@ class ToolTipElement extends HTMLElement {
|
|
|
247
248
|
if (!this.control) return
|
|
248
249
|
const showing = isPopoverOpen(this)
|
|
249
250
|
|
|
251
|
+
// Track when the control's popover (e.g an ActionMenu) is opened/closed
|
|
252
|
+
if (event.type === 'beforetoggle' && event.currentTarget !== this) {
|
|
253
|
+
this.#isControlsPopoverOpen = (event as ToggleEvent).newState === 'open'
|
|
254
|
+
}
|
|
255
|
+
|
|
250
256
|
// Ensures that tooltip stays open when hovering between tooltip and element
|
|
251
257
|
// WCAG Success Criterion 1.4.13 Hoverable
|
|
252
258
|
const shouldShow =
|
|
253
|
-
event.type === 'mouseenter' ||
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
259
|
+
(event.type === 'mouseenter' ||
|
|
260
|
+
// Only show tooltip on focus if running in headless browser (for tests) or if focus ring
|
|
261
|
+
// is visible (i.e. if user is using keyboard navigation)
|
|
262
|
+
(event.type === 'focus' && (navigator.webdriver || this.control.matches(':focus-visible')))) &&
|
|
263
|
+
// Don't show tooltip if the control's popover is open (e.g. an ActionMenu)
|
|
264
|
+
!this.#isControlsPopoverOpen
|
|
257
265
|
const isMouseLeaveFromButton =
|
|
258
266
|
event.type === 'mouseleave' &&
|
|
259
267
|
(event as MouseEvent).relatedTarget !== this.control &&
|
|
260
268
|
(event as MouseEvent).relatedTarget !== this
|
|
261
269
|
const isEscapeKeydown = event.type === 'keydown' && (event as KeyboardEvent).key === 'Escape'
|
|
262
270
|
const isMouseDownOnButton = event.type === 'mousedown' && event.currentTarget === this.control
|
|
263
|
-
const isOpeningOtherPopover =
|
|
271
|
+
const isOpeningOtherPopover =
|
|
272
|
+
event.type === 'beforetoggle' && event.currentTarget !== this && (event as ToggleEvent).newState === 'open'
|
|
264
273
|
const shouldHide = isMouseLeaveFromButton || isEscapeKeydown || isMouseDownOnButton || isOpeningOtherPopover
|
|
265
274
|
|
|
266
275
|
if (showing && isEscapeKeydown) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<%= render Primer::BaseComponent.new(**@system_arguments) do %>
|
|
2
2
|
<span class="ActionListContent">
|
|
3
3
|
<% if leading_visual %>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
<span class="ActionListItem-visual ActionListItem-visual--leading">
|
|
5
|
+
<%= leading_visual %>
|
|
6
|
+
</span>
|
|
7
7
|
<% end %>
|
|
8
8
|
<%= render(Primer::ConditionalWrapper.new(condition: description?, tag: :span, **@description_wrapper_arguments)) do %>
|
|
9
9
|
<span class="ActionListItem-label"><%= content %></span>
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
</span>
|
|
14
14
|
<% end %>
|
|
15
15
|
<% end %>
|
|
16
|
-
|
|
16
|
+
<% if trailing_visual %>
|
|
17
17
|
<span class="ActionListItem-visual ActionListItem-visual--trailing">
|
|
18
18
|
<%= trailing_visual %>
|
|
19
19
|
</span>
|
|
20
20
|
<% end %>
|
|
21
|
+
</span>
|
|
21
22
|
<% end %>
|
|
@@ -79,6 +79,12 @@ module Primer
|
|
|
79
79
|
"AvatarStack--two" => avatars.size == 2,
|
|
80
80
|
"AvatarStack--three-plus" => avatars.size > 2
|
|
81
81
|
)
|
|
82
|
+
|
|
83
|
+
# Make non-linked stacks keyboard-focusable so :focus-within triggers expansion (avoid extra focus stop when avatars are links).
|
|
84
|
+
unless @tooltipped || @disable_expand
|
|
85
|
+
has_linked_avatars = avatars.any?(&:link?)
|
|
86
|
+
@body_arguments[:tabindex] ||= 0 unless has_linked_avatars
|
|
87
|
+
end
|
|
82
88
|
end
|
|
83
89
|
|
|
84
90
|
def render?
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.blankslate-container{container-type:inline-size;width:100%}.blankslate{--blankslate-outer-padding-block:var(--base-size-32);--blankslate-outer-padding-inline:var(--base-size-32);padding:var(--blankslate-outer-padding-block) var(--blankslate-outer-padding-inline);position:relative;text-align:center}.blankslate p{color:var(--fgColor-muted);font-size:var(--text-body-size-large)}.blankslate code{background:var(--bgColor-default);border:var(--borderWidth-thin) solid var(--borderColor-muted);border-radius:var(--borderRadius-medium);font-size:var(--text-body-size-medium);padding:var(--base-size-2) var(--base-size-4) var(--base-size-4)}.blankslate img{height:56px;width:56px}.blankslate-icon{color:var(--fgColor-muted);margin-bottom:var(--stack-gap-condensed);margin-left:var(--control-small-gap);margin-right:var(--control-small-gap)}.blankslate-image{margin-bottom:var(--stack-gap-normal)}.blankslate-heading{font-size:var(--text-title-size-medium);font-weight:var(--text-title-weight-medium);margin-bottom:var(--base-size-4)}.blankslate-action{margin-top:var(--stack-gap-normal)}.blankslate-action:first-of-type{margin-top:var(--stack-gap-spacious)}.blankslate-action:last-of-type{margin-bottom:var(--stack-gap-condensed)}.blankslate-capped{border-radius:0 0 var(--borderRadius-medium) var(--borderRadius-medium)}.blankslate-spacious{--blankslate-outer-padding-block:var(--base-size-80);--blankslate-outer-padding-inline:var(--base-size-40)}.blankslate-narrow{margin:0 auto;max-width:485px}.blankslate-large img{height:80px;width:80px}.blankslate-large h3{font-size:24px;margin:var(--stack-gap-normal) 0}.blankslate-large p{font-size:var(--text-body-size-large)}.blankslate-clean-background{border:0}@container (max-width: 34rem){.blankslate{--blankslate-outer-padding-block:var(--base-size-20);--blankslate-outer-padding-inline:var(--base-size-20)}.blankslate-spacious{--blankslate-outer-padding-block:var(--base-size-44);--blankslate-outer-padding-inline:var(--base-size-28)}.blankslate-icon{margin-bottom:var(--stack-gap-condensed)}.blankslate-heading{font-size:var(--text-title-size-small)}.blankslate p{font-size:var(--text-body-size-medium)}.blankslate-action{margin-top:var(--stack-gap-condensed)}.blankslate-action:first-of-type{margin-top:var(--stack-gap-normal)}.blankslate-action:last-of-type{margin-bottom:calc(var(--stack-gap-condensed)/2)}}
|
|
1
|
+
.blankslate-container{container-type:inline-size;width:100%}.blankslate{--blankslate-outer-padding-block:var(--base-size-32);--blankslate-outer-padding-inline:var(--base-size-32);padding:var(--blankslate-outer-padding-block) var(--blankslate-outer-padding-inline);position:relative;text-align:center}.blankslate p{color:var(--fgColor-muted);font-size:var(--text-body-size-large);margin:auto;max-width:56rem}.blankslate code{background:var(--bgColor-default);border:var(--borderWidth-thin) solid var(--borderColor-muted);border-radius:var(--borderRadius-medium);font-size:var(--text-body-size-medium);padding:var(--base-size-2) var(--base-size-4) var(--base-size-4)}.blankslate img{height:56px;width:56px}.blankslate-icon{color:var(--fgColor-muted);margin-bottom:var(--stack-gap-condensed);margin-left:var(--control-small-gap);margin-right:var(--control-small-gap)}.blankslate-image{margin-bottom:var(--stack-gap-normal)}.blankslate-heading{font-size:var(--text-title-size-medium);font-weight:var(--text-title-weight-medium);margin-bottom:var(--base-size-4)}.blankslate-action{margin-top:var(--stack-gap-normal)}.blankslate-action:first-of-type{margin-top:var(--stack-gap-spacious)}.blankslate-action:last-of-type{margin-bottom:var(--stack-gap-condensed)}.blankslate-capped{border-radius:0 0 var(--borderRadius-medium) var(--borderRadius-medium)}.blankslate-spacious{--blankslate-outer-padding-block:var(--base-size-80);--blankslate-outer-padding-inline:var(--base-size-40)}.blankslate-narrow{margin:0 auto;max-width:485px}.blankslate-large img{height:80px;width:80px}.blankslate-large h3{font-size:24px;margin:var(--stack-gap-normal) 0}.blankslate-large p{font-size:var(--text-body-size-large)}.blankslate-clean-background{border:0}@container (max-width: 34rem){.blankslate{--blankslate-outer-padding-block:var(--base-size-20);--blankslate-outer-padding-inline:var(--base-size-20)}.blankslate-spacious{--blankslate-outer-padding-block:var(--base-size-44);--blankslate-outer-padding-inline:var(--base-size-28)}.blankslate-icon{margin-bottom:var(--stack-gap-condensed)}.blankslate-heading{font-size:var(--text-title-size-small)}.blankslate p{font-size:var(--text-body-size-medium)}.blankslate-action{margin-top:var(--stack-gap-condensed)}.blankslate-action:first-of-type{margin-top:var(--stack-gap-normal)}.blankslate-action:last-of-type{margin-bottom:calc(var(--stack-gap-condensed)/2)}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["blankslate.pcss"],"names":[],"mappings":"AAEA,sBACE,0BAA2B,CAC3B,UACF,CAEA,YACE,oDAAqD,CACrD,qDAAsD,CAItD,oFAAqF,CAFrF,iBAAkB,CAGlB,
|
|
1
|
+
{"version":3,"sources":["blankslate.pcss"],"names":[],"mappings":"AAEA,sBACE,0BAA2B,CAC3B,UACF,CAEA,YACE,oDAAqD,CACrD,qDAAsD,CAItD,oFAAqF,CAFrF,iBAAkB,CAGlB,iBAwBF,CArBE,cAEE,0BAA2B,CAD3B,qCAAsC,CAGtC,WAAY,CADZ,eAEF,CAGA,iBAGE,iCAAkC,CAClC,6DAA8D,CAC9D,wCAAyC,CAHzC,sCAAuC,CADvC,gEAKF,CAGA,gBAEE,WAAY,CADZ,UAEF,CAGF,iBAOE,0BAA2B,CAH3B,wCAAyC,CAEzC,oCAAqC,CAJrC,qCAMF,CAEA,kBAEE,qCACF,CAEA,oBAEE,uCAAwC,CACxC,2CAA4C,CAF5C,gCAGF,CAEA,mBAEE,kCAWF,CATE,iCAEE,oCACF,CAEA,gCAEE,wCACF,CAGF,mBACE,uEACF,CAEA,qBACE,oDAAqD,CACrD,qDACF,CAEA,mBAEE,aAAc,CADd,eAEF,CAME,sBAEE,WAAY,CADZ,UAEF,CAGA,qBAME,cAAe,CAJf,gCAKF,CAGA,oBACE,qCACF,CAKF,6BACE,QACF,CAIA,8BACE,YACE,oDAAqD,CACrD,qDACF,CAEA,qBACE,oDAAqD,CACrD,qDACF,CAEA,iBAEE,wCACF,CAEA,oBACE,sCACF,CAGA,cACE,sCACF,CAEA,mBAEE,qCAWF,CATE,iCAEE,kCACF,CAEA,gCAEE,gDACF,CAEJ","file":"blankslate.css","sourcesContent":["/* blankslate */\n\n.blankslate-container {\n container-type: inline-size;\n width: 100%;\n}\n\n.blankslate {\n --blankslate-outer-padding-block: var(--base-size-32);\n --blankslate-outer-padding-inline: var(--base-size-32);\n\n position: relative;\n /* stylelint-disable-next-line primer/spacing */\n padding: var(--blankslate-outer-padding-block) var(--blankslate-outer-padding-inline);\n text-align: center;\n\n /* stylelint-disable-next-line selector-max-type */\n & p {\n font-size: var(--text-body-size-large);\n color: var(--fgColor-muted);\n max-width: 56rem;\n margin: auto;\n }\n\n /* stylelint-disable-next-line selector-max-type */\n & code {\n padding: var(--base-size-2) var(--base-size-4) var(--base-size-4);\n font-size: var(--text-body-size-medium);\n background: var(--bgColor-default);\n border: var(--borderWidth-thin) solid var(--borderColor-muted);\n border-radius: var(--borderRadius-medium);\n }\n\n /* stylelint-disable-next-line selector-max-type */\n & img {\n width: 56px;\n height: 56px;\n }\n}\n\n.blankslate-icon {\n /* stylelint-disable-next-line primer/spacing */\n margin-right: var(--control-small-gap);\n /* stylelint-disable-next-line primer/spacing */\n margin-bottom: var(--stack-gap-condensed);\n /* stylelint-disable-next-line primer/spacing */\n margin-left: var(--control-small-gap);\n color: var(--fgColor-muted);\n}\n\n.blankslate-image {\n /* stylelint-disable-next-line primer/spacing */\n margin-bottom: var(--stack-gap-normal);\n}\n\n.blankslate-heading {\n margin-bottom: var(--base-size-4);\n font-size: var(--text-title-size-medium);\n font-weight: var(--text-title-weight-medium);\n}\n\n.blankslate-action {\n /* stylelint-disable-next-line primer/spacing */\n margin-top: var(--stack-gap-normal);\n\n &:first-of-type {\n /* stylelint-disable-next-line primer/spacing */\n margin-top: var(--stack-gap-spacious);\n }\n\n &:last-of-type {\n /* stylelint-disable-next-line primer/spacing */\n margin-bottom: var(--stack-gap-condensed);\n }\n}\n\n.blankslate-capped {\n border-radius: 0 0 var(--borderRadius-medium) var(--borderRadius-medium);\n}\n\n.blankslate-spacious {\n --blankslate-outer-padding-block: var(--base-size-80);\n --blankslate-outer-padding-inline: var(--base-size-40);\n}\n\n.blankslate-narrow {\n max-width: 485px;\n margin: 0 auto;\n}\n\n/* was .large-format\n** QUESTION: should we deprecate this? */\n.blankslate-large {\n /* stylelint-disable-next-line selector-max-type */\n & img {\n width: 80px;\n height: 80px;\n }\n\n /* stylelint-disable-next-line selector-max-type */\n & h3 {\n /* stylelint-disable-next-line primer/spacing */\n margin: var(--stack-gap-normal) 0;\n\n /* font-size: $h3-size; // This doesn't actually make the text larger. Should this be $h2-size? */\n /* stylelint-disable-next-line primer/typography */\n font-size: 24px;\n }\n\n /* stylelint-disable-next-line selector-max-type */\n & p {\n font-size: var(--text-body-size-large);\n }\n}\n\n/* was .clean-background\n** TO DO: deprecate this and use utility instead */\n.blankslate-clean-background {\n border: 0;\n}\n\n/* At the time these styles were written,\n `34rem` was our \"small\" breakpoint width */\n@container (max-width: 34rem) {\n .blankslate {\n --blankslate-outer-padding-block: var(--base-size-20);\n --blankslate-outer-padding-inline: var(--base-size-20);\n }\n\n .blankslate-spacious {\n --blankslate-outer-padding-block: var(--base-size-44);\n --blankslate-outer-padding-inline: var(--base-size-28);\n }\n\n .blankslate-icon {\n /* stylelint-disable-next-line primer/spacing */\n margin-bottom: var(--stack-gap-condensed);\n }\n\n .blankslate-heading {\n font-size: var(--text-title-size-small);\n }\n\n /* stylelint-disable-next-line selector-max-type */\n .blankslate p {\n font-size: var(--text-body-size-medium);\n }\n\n .blankslate-action {\n /* stylelint-disable-next-line primer/spacing */\n margin-top: var(--stack-gap-condensed);\n\n &:first-of-type {\n /* stylelint-disable-next-line primer/spacing */\n margin-top: var(--stack-gap-normal);\n }\n\n &:last-of-type {\n /* stylelint-disable-next-line primer/spacing */\n margin-bottom: calc(var(--stack-gap-condensed) / 2);\n }\n }\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<%= render(Primer::BaseComponent.new(tag: :span, hidden: @hidden, data: { target: @target }, **@wrapper_arguments)) do %>
|
|
2
2
|
<%= render Primer::BaseComponent.new(**@system_arguments) do %>
|
|
3
|
-
<circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none"
|
|
4
|
-
<path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"
|
|
3
|
+
<circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none"></circle>
|
|
4
|
+
<path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"></path>
|
|
5
5
|
<% end %>
|
|
6
6
|
<% if no_aria_label? %>
|
|
7
7
|
<span class="sr-only"><%= @sr_text %></span>
|
|
@@ -22,6 +22,7 @@ import './beta/relative_time';
|
|
|
22
22
|
import './alpha/tab_container';
|
|
23
23
|
import '../../lib/primer/forms/primer_multi_input';
|
|
24
24
|
import '../../lib/primer/forms/primer_text_field';
|
|
25
|
+
import '../../lib/primer/forms/primer_text_area';
|
|
25
26
|
import '../../lib/primer/forms/toggle_switch_input';
|
|
26
27
|
import './alpha/action_menu/action_menu_element';
|
|
27
28
|
import './alpha/select_panel_element';
|
|
@@ -22,6 +22,7 @@ import './beta/relative_time';
|
|
|
22
22
|
import './alpha/tab_container';
|
|
23
23
|
import '../../lib/primer/forms/primer_multi_input';
|
|
24
24
|
import '../../lib/primer/forms/primer_text_field';
|
|
25
|
+
import '../../lib/primer/forms/primer_text_area';
|
|
25
26
|
import '../../lib/primer/forms/toggle_switch_input';
|
|
26
27
|
import './alpha/action_menu/action_menu_element';
|
|
27
28
|
import './alpha/select_panel_element';
|
|
@@ -22,6 +22,7 @@ import './beta/relative_time'
|
|
|
22
22
|
import './alpha/tab_container'
|
|
23
23
|
import '../../lib/primer/forms/primer_multi_input'
|
|
24
24
|
import '../../lib/primer/forms/primer_text_field'
|
|
25
|
+
import '../../lib/primer/forms/primer_text_area'
|
|
25
26
|
import '../../lib/primer/forms/toggle_switch_input'
|
|
26
27
|
import './alpha/action_menu/action_menu_element'
|
|
27
28
|
import './alpha/select_panel_element'
|
|
@@ -9,7 +9,7 @@ module Primer
|
|
|
9
9
|
attr_accessor :last_request
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
rescue_from ActionController::
|
|
12
|
+
rescue_from ActionController::InvalidCrossOriginRequest, with: :handle_invalid_cross_origin_request
|
|
13
13
|
|
|
14
14
|
before_action :reject_non_ajax_request
|
|
15
15
|
|
|
@@ -29,7 +29,7 @@ module Primer
|
|
|
29
29
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
|
-
def
|
|
32
|
+
def handle_invalid_cross_origin_request
|
|
33
33
|
render status: :unauthorized, plain: "Bad CSRF token."
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -8,27 +8,31 @@ class CheckBoxWithNestedForm < ApplicationForm
|
|
|
8
8
|
custom_cities_form.text_field(
|
|
9
9
|
name: :custom_cities,
|
|
10
10
|
label: "Custom cities",
|
|
11
|
-
|
|
11
|
+
caption: "A space-separated list of cities"
|
|
12
12
|
)
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
form do |check_form|
|
|
17
|
-
check_form.check_box_group(
|
|
17
|
+
check_form.check_box_group(
|
|
18
|
+
name: :city_categories,
|
|
19
|
+
label: "City categories",
|
|
20
|
+
caption: "Select all that apply."
|
|
21
|
+
) do |check_group|
|
|
18
22
|
check_group.check_box(
|
|
19
23
|
value: "capital",
|
|
20
24
|
label: "Capital",
|
|
21
|
-
|
|
25
|
+
caption: "The capital city"
|
|
22
26
|
)
|
|
23
27
|
check_group.check_box(
|
|
24
28
|
value: "populous",
|
|
25
29
|
label: "Most-populous",
|
|
26
|
-
|
|
30
|
+
caption: "The five most-populous cities"
|
|
27
31
|
)
|
|
28
32
|
check_group.check_box(
|
|
29
33
|
value: "custom",
|
|
30
34
|
label: "Custom",
|
|
31
|
-
|
|
35
|
+
caption: "A custom list of cities"
|
|
32
36
|
) do |custom_check_box|
|
|
33
37
|
custom_check_box.nested_form do |builder|
|
|
34
38
|
CustomCitiesForm.new(builder)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :nodoc:
|
|
4
|
+
class TextFieldWithCharacterLimitForm < ApplicationForm
|
|
5
|
+
form do |my_form|
|
|
6
|
+
my_form.text_field(
|
|
7
|
+
name: :username,
|
|
8
|
+
label: "Username",
|
|
9
|
+
caption: "Choose a unique username",
|
|
10
|
+
character_limit: 20
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
<% if @input.
|
|
2
|
-
<span class="
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<span class="FormControl-caption"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
<% if @input.character_limit? %>
|
|
2
|
+
<span class="sr-only" data-target="<%= @input.character_limit_target_prefix %>.characterLimitSrElement" aria-live="polite" role="status"></span>
|
|
3
|
+
<span class="sr-only" id="<%= @input.character_limit_id %>">You can enter up to <%= @input.character_limit %> <%= @input.character_limit == 1 ? 'character' : 'characters' %></span>
|
|
4
|
+
<span class="FormControl-caption" data-target="<%= @input.character_limit_target_prefix %>.characterLimitElement" data-max-length="<%= @input.character_limit %>" aria-hidden="true">
|
|
5
|
+
<span class="FormControl-caption-icon" hidden><%= render(Primer::Beta::Octicon.new(icon: :"alert-fill", size: :xsmall, aria: { hidden: true })) %></span>
|
|
6
|
+
<span class="FormControl-caption-text"><%= @input.character_limit %> <%= @input.character_limit == 1 ? 'character' : 'characters' %> remaining</span>
|
|
7
|
+
</span>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% if @input.caption? || caption_template? %>
|
|
10
|
+
<span class="FormControl-caption" id="<%= @input.caption_id %>">
|
|
11
|
+
<% if @input.caption? && !@input.caption.blank? %>
|
|
12
|
+
<%= @input.caption %>
|
|
13
|
+
<% elsif caption_template? %>
|
|
14
|
+
<%= render_caption_template %>
|
|
15
|
+
<% end %>
|
|
16
|
+
</span>
|
|
10
17
|
<% end %>
|
|
@@ -0,0 +1,41 @@
|
|
|
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 declare class CharacterCounter {
|
|
6
|
+
private inputElement;
|
|
7
|
+
private characterLimitElement;
|
|
8
|
+
private characterLimitSrElement;
|
|
9
|
+
private SCREEN_READER_DELAY;
|
|
10
|
+
private announceTimeout;
|
|
11
|
+
private isInitialLoad;
|
|
12
|
+
constructor(inputElement: HTMLInputElement | HTMLTextAreaElement, characterLimitElement: HTMLElement, characterLimitSrElement: HTMLElement);
|
|
13
|
+
/**
|
|
14
|
+
* Initialize character counting by setting up event listener and initial count
|
|
15
|
+
*/
|
|
16
|
+
initialize(signal?: AbortSignal): void;
|
|
17
|
+
/**
|
|
18
|
+
* Clean up any pending timeouts
|
|
19
|
+
*/
|
|
20
|
+
cleanup(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Pluralizes a word based on the count
|
|
23
|
+
*/
|
|
24
|
+
private pluralize;
|
|
25
|
+
/**
|
|
26
|
+
* Update the character count display and validation state
|
|
27
|
+
*/
|
|
28
|
+
private updateCharacterCount;
|
|
29
|
+
/**
|
|
30
|
+
* Announce character count to screen readers with debouncing
|
|
31
|
+
*/
|
|
32
|
+
private announceToScreenReader;
|
|
33
|
+
/**
|
|
34
|
+
* Set error when character limit is exceeded
|
|
35
|
+
*/
|
|
36
|
+
private setError;
|
|
37
|
+
/**
|
|
38
|
+
* Clear error when back under character limit
|
|
39
|
+
*/
|
|
40
|
+
private clearError;
|
|
41
|
+
}
|