ariadne_view_components 0.0.47-x86_64-darwin → 0.0.49-x86_64-darwin

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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -25
  3. data/app/assets/javascripts/ariadne_view_components.js +2 -2
  4. data/app/assets/javascripts/ariadne_view_components.js.map +1 -1
  5. data/app/assets/javascripts/components/ariadne/accumulator_controller/accumulator_controller.d.ts +22 -0
  6. data/app/assets/javascripts/components/ariadne/dropdown/menu_component.d.ts +1 -0
  7. data/app/assets/javascripts/components/ariadne/events_controller/events_controller.d.ts +4 -0
  8. data/app/assets/javascripts/components/ariadne/options_controller/options_controller.d.ts +40 -0
  9. data/app/assets/javascripts/components/ariadne/outlet_manager_controller/outlet_manager_controller.d.ts +42 -0
  10. data/app/assets/javascripts/components/ariadne/string_match_controller/string_match_controller.d.ts +27 -0
  11. data/app/assets/javascripts/components/ariadne/synced_boolean_attributes_controller/synced_boolean_attributes_controller.d.ts +44 -0
  12. data/app/assets/javascripts/components/ariadne/toggleable_controller/toggleable_controller.d.ts +34 -0
  13. data/app/assets/stylesheets/ariadne_view_components.css +3 -3
  14. data/app/components/ariadne/accumulator_controller/accumulator_controller.d.ts +22 -0
  15. data/app/components/ariadne/accumulator_controller/accumulator_controller.js +39 -0
  16. data/app/components/ariadne/accumulator_controller/accumulator_controller.ts +48 -0
  17. data/app/components/ariadne/action_card_component.html.erb +11 -0
  18. data/app/components/ariadne/action_card_component.rb +45 -0
  19. data/app/components/ariadne/ariadne.js +10 -0
  20. data/app/components/ariadne/ariadne.ts +10 -0
  21. data/app/components/ariadne/bottom_tab_component.html.erb +4 -0
  22. data/app/components/ariadne/bottom_tab_component.rb +44 -0
  23. data/app/components/ariadne/bottom_tab_nav_component.html.erb +5 -0
  24. data/app/components/ariadne/bottom_tab_nav_component.rb +33 -0
  25. data/app/components/ariadne/breadcrumbs_component.html.erb +13 -0
  26. data/app/components/ariadne/breadcrumbs_component.rb +31 -0
  27. data/app/components/ariadne/checkbox_component.html.erb +5 -0
  28. data/app/components/ariadne/checkbox_component.rb +43 -0
  29. data/app/components/ariadne/close_button_component.html.erb +4 -0
  30. data/app/components/ariadne/close_button_component.rb +33 -0
  31. data/app/components/ariadne/combobox_component.html.erb +14 -0
  32. data/app/components/ariadne/combobox_component.rb +76 -0
  33. data/app/components/ariadne/dropdown/menu_component.d.ts +1 -0
  34. data/app/components/ariadne/dropdown/menu_component.js +1 -0
  35. data/app/components/ariadne/events_controller/events_controller.d.ts +4 -0
  36. data/app/components/ariadne/events_controller/events_controller.js +6 -0
  37. data/app/components/ariadne/events_controller/events_controller.ts +7 -0
  38. data/app/components/ariadne/layout_component.html.erb +21 -0
  39. data/app/components/ariadne/layout_component.rb +69 -0
  40. data/app/components/ariadne/modal_component.html.erb +11 -0
  41. data/app/components/ariadne/modal_component.rb +88 -0
  42. data/app/components/ariadne/options_controller/options_controller.d.ts +40 -0
  43. data/app/components/ariadne/options_controller/options_controller.js +98 -0
  44. data/app/components/ariadne/options_controller/options_controller.ts +132 -0
  45. data/app/components/ariadne/outlet_manager_controller/outlet_manager_controller.d.ts +42 -0
  46. data/app/components/ariadne/outlet_manager_controller/outlet_manager_controller.js +237 -0
  47. data/app/components/ariadne/outlet_manager_controller/outlet_manager_controller.ts +278 -0
  48. data/app/components/ariadne/popover_component.html.erb +10 -0
  49. data/app/components/ariadne/popover_component.rb +81 -0
  50. data/app/components/ariadne/progress_bar_component.html.erb +5 -0
  51. data/app/components/ariadne/progress_bar_component.rb +63 -0
  52. data/app/components/ariadne/relative_time_component.html.erb +3 -0
  53. data/app/components/ariadne/relative_time_component.rb +61 -0
  54. data/app/components/ariadne/show_more_button_component.html.erb +11 -0
  55. data/app/components/ariadne/show_more_button_component.rb +47 -0
  56. data/app/components/ariadne/spinner_component.html.erb +16 -0
  57. data/app/components/ariadne/spinner_component.rb +45 -0
  58. data/app/components/ariadne/string_match_controller/string_match_controller.d.ts +27 -0
  59. data/app/components/ariadne/string_match_controller/string_match_controller.js +51 -0
  60. data/app/components/ariadne/string_match_controller/string_match_controller.ts +64 -0
  61. data/app/components/ariadne/subheader_component.html.erb +11 -0
  62. data/app/components/ariadne/subheader_component.rb +65 -0
  63. data/app/components/ariadne/synced_boolean_attributes_controller/synced_boolean_attributes_controller.d.ts +44 -0
  64. data/app/components/ariadne/synced_boolean_attributes_controller/synced_boolean_attributes_controller.js +153 -0
  65. data/app/components/ariadne/synced_boolean_attributes_controller/synced_boolean_attributes_controller.ts +192 -0
  66. data/app/components/ariadne/toggle_component/toggle_component.html.erb +15 -0
  67. data/app/components/ariadne/toggle_component.rb +95 -0
  68. data/app/components/ariadne/toggleable_controller/toggleable_controller.d.ts +34 -0
  69. data/app/components/ariadne/toggleable_controller/toggleable_controller.js +54 -0
  70. data/app/components/ariadne/toggleable_controller/toggleable_controller.ts +77 -0
  71. data/lib/ariadne/view_components/version.rb +1 -1
  72. data/static/arguments.yml +50 -0
  73. data/static/audited_at.json +17 -0
  74. data/static/classes.yml +209 -173
  75. data/static/constants.json +356 -0
  76. data/static/statuses.json +17 -0
  77. data/tailwind.config.js +7 -7
  78. metadata +75 -12
  79. /data/app/assets/javascripts/{ariadne-form.d.ts → components/ariadne/ariadne-form.d.ts} +0 -0
  80. /data/app/assets/javascripts/{ariadne.d.ts → components/ariadne/ariadne.d.ts} +0 -0
  81. /data/app/assets/javascripts/{clipboard_copy_component → components/ariadne/clipboard_copy_component}/clipboard-copy-component.d.ts +0 -0
  82. /data/app/assets/javascripts/{rich_text_area_component → components/ariadne/rich_text_area_component}/rich-text-area-component.d.ts +0 -0
  83. /data/app/assets/javascripts/{slideover_component → components/ariadne/slideover_component}/slideover-component.d.ts +0 -0
  84. /data/app/assets/javascripts/{tab_container_component → components/ariadne/tab_container_component}/tab-container-component.d.ts +0 -0
  85. /data/app/assets/javascripts/{tab_nav_component → components/ariadne/tab_nav_component}/tab-nav-component.d.ts +0 -0
  86. /data/app/assets/javascripts/{time_ago_component → components/ariadne/time_ago_component}/time-ago-component.d.ts +0 -0
  87. /data/app/assets/javascripts/{tooltip_component → components/ariadne/tooltip_component}/tooltip-component.d.ts +0 -0
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Converts time into a display-friendly format in a relative time (3d) or absolute date after a default of 30 days
5
+ class RelativeTimeComponent < Ariadne::Component
6
+ DEFAULT_TAG = :span
7
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
8
+ DEFAULT_DATE_FORMAT = "%b %-d, %Y"
9
+ DEFAULT_DATE_FORMAT_SWITCH = 30 * 24 * 60 * 60
10
+
11
+ DEFAULT_CLASSES = ""
12
+
13
+ # @example Default
14
+ #
15
+ # <%= render(Ariadne::RelativeTimeComponent.new) { "Example" } %>
16
+ #
17
+ # @param tag [Symbol, String] The rendered tag name.
18
+ # @param classes [String] <%= link_to_classes_docs %>
19
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
20
+ def initialize(
21
+ tag: DEFAULT_TAG,
22
+ classes: "",
23
+ time: nil,
24
+ format: DEFAULT_DATE_FORMAT,
25
+ date_format_switch: DEFAULT_DATE_FORMAT_SWITCH,
26
+ force_relative: false,
27
+ attributes: {}
28
+ )
29
+ raise ArgumentError, "A 'time' argument is required to use the Relative Time component." if time.blank?
30
+
31
+ @now = 1.second.ago
32
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
33
+ @classes = merge_class_names(DEFAULT_CLASSES, classes)
34
+ @time = time
35
+ @date_format_switch = date_format_switch
36
+ @format = format
37
+ @force_relative = force_relative
38
+ @attributes = attributes
39
+
40
+ @formatted_date = formatted
41
+ end
42
+
43
+ private def formatted_time
44
+ @time.localtime.strftime(@format)
45
+ end
46
+
47
+ private def time_difference
48
+ (@now - @time).to_i.abs
49
+ end
50
+
51
+ private def formatted
52
+ diff = time_difference
53
+
54
+ if @force_relative || diff < @date_format_switch
55
+ time_ago_in_words(@time)
56
+ else
57
+ @time.localtime.strftime(@format)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,11 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |component| %>
2
+ <%= render Ariadne::ButtonComponent.new(classes: @button_classes, attributes: @button_attributes) do |button| %>
3
+ <%= @button_text %>
4
+ <% end %>
5
+ <div
6
+ class="ariadne-max-h-full aria-hidden:ariadne-max-h-0 ariadne-overflow-hidden ariadne-my-2 ariadne-border-solid ariadne-border-2 aria-selected:ariadne-hidden aria-selected:ariadne-border-green-300 aria-selected:ariadne-border-solid aria-selected:ariadne-border-2 ariadne-border-transparent ariadne-min-w-[21rem] ariadne-whitespace-nowrap ariadne-text-ellipsis" data-controller="toggleable" aria-hidden="true" data-toggleable-anti-attrs-value='["aria-hidden"]' data-toggleable-outlet="<%= @outlet %>">
7
+ <% hiddens.each do |hidden| %>
8
+ <%= hidden %>
9
+ <% end %>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Add a general description of component here
5
+ # Add additional usage considerations or best practices that may aid the user to use the component correctly.
6
+ # @accessibility Add any accessibility considerations
7
+ class ShowMoreButtonComponent < Ariadne::Component
8
+ DEFAULT_TAG = :div
9
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
10
+
11
+ DEFAULT_CLASSES = { wrapper: "ariadne-group", button: "ariadne-px-2 ariadne-py-0" }
12
+
13
+ DEFAULT_ATTRIBUTES = {
14
+ wrapper: {
15
+ "data-controller": "toggleable",
16
+ },
17
+ button: {
18
+ "data-action": "click->toggleable#toggle",
19
+ },
20
+ }
21
+
22
+ renders_many :hiddens
23
+
24
+ # @example Default
25
+ #
26
+ # <%= render(Ariadne::ShowMoreButtonComponent.new) { "Example" } %>
27
+ #
28
+ # @param tag [Symbol, String] The rendered tag name.
29
+ # @param classes [String] <%= link_to_classes_docs %>
30
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
31
+ def initialize(tag: DEFAULT_TAG, classes: "", button_text: "...", outlet: nil, attributes: {})
32
+ raise ArgumentError, "An 'aria-label' attribute is required to use the Show More Button. Include as much detail as you can about the content to be shown to assist screen readers." if attributes.blank? || attributes["aria-label"].blank?
33
+ raise ArgumentError, "An 'outlet' attribute is required to use the Show More Button. This will ensure controllers can correctly identify states to update." if outlet.blank?
34
+
35
+ @outlet = outlet
36
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
37
+ @classes = merge_class_names(DEFAULT_CLASSES[:wrapper], classes)
38
+ @attributes = DEFAULT_ATTRIBUTES[:wrapper]
39
+ .merge({ "data-toggleable-toggleable-outlet": "[data-toggleable-outlet=#{@outlet}]" })
40
+ .merge(attributes)
41
+
42
+ @button_classes = merge_class_names(DEFAULT_CLASSES[:button], classes)
43
+ @button_attributes = DEFAULT_ATTRIBUTES[:button].merge(attributes)
44
+ @button_text = button_text
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,16 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: :svg, classes: @classes, attributes: @attributes) do |svg| %>
2
+ <circle
3
+ cx="8"
4
+ cy="8"
5
+ r="7"
6
+ stroke="currentColor"
7
+ stroke-opacity="0.25"
8
+ stroke-width="2"
9
+ vector-effect="non-scaling-stroke" />
10
+ <path
11
+ d="M15 8a7.002 7.002 0 00-7-7"
12
+ stroke="currentColor"
13
+ stroke-width="2"
14
+ stroke-linecap="round"
15
+ vector-effect="non-scaling-stroke" />
16
+ <% end %>
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Renders a spinning wheel to indicate a loading state
5
+ # Add color using a tailwind text color class
6
+ class SpinnerComponent < Ariadne::Component
7
+ DEFAULT_SIZE = :md
8
+ DEFAULT_CLASSES = "ariadne-animate-spin ariadne-fill-none"
9
+ DEFAULT_ATTRIBUTES = {
10
+ fill: "none",
11
+ viewBox: "0 0 16 16",
12
+ role: "progressbar",
13
+ "aria-valuetext": "Loading...",
14
+ "aria-busy": true,
15
+ "aria-valuemin": "0",
16
+ "aria-valuemax": "100",
17
+ }.freeze
18
+
19
+ SIZE_CLASS_MAPPINGS = {
20
+ xs: "ariadne-h-4 ariadne-w-4",
21
+ sm: "ariadne-h-6 ariadne-w-6",
22
+ md: "ariadne-h-8 ariadne-w-8",
23
+ lg: "ariadne-h-10 ariadne-w-10",
24
+ xl: "ariadne-h-12 ariadne-w-12",
25
+ }.freeze
26
+
27
+ VALID_SIZES = SIZE_CLASS_MAPPINGS.keys.freeze
28
+
29
+ # @example Default
30
+ #
31
+ # <%= render(Ariadne::SpinnerComponent.new) { } %>
32
+ #
33
+ # @param classes [String] <%= link_to_classes_docs %>
34
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
35
+ def initialize(classes: "", attributes: {}, size: DEFAULT_SIZE)
36
+ @size = fetch_or_raise(VALID_SIZES, size)
37
+ @attributes = DEFAULT_ATTRIBUTES.merge(attributes)
38
+ @classes = merge_class_names(
39
+ DEFAULT_CLASSES,
40
+ SIZE_CLASS_MAPPINGS[size],
41
+ classes,
42
+ )
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ import { TOutletChangeData } from '../outlet_manager_controller/outlet_manager_controller';
2
+ import SyncedBooleanAttributesController from '../synced_boolean_attributes_controller/synced_boolean_attributes_controller';
3
+ export interface StringMatchOutlet extends SyncedBooleanAttributesController<string> {
4
+ change: (event: Event, updateTo?: TOutletChangeData<string>) => void;
5
+ }
6
+ export default class StringMatchController extends SyncedBooleanAttributesController<string> implements StringMatchOutlet {
7
+ #private;
8
+ static outlets: string[];
9
+ static targets: string[];
10
+ static values: {
11
+ keyword: StringConstructor;
12
+ syncedAttrs: ArrayConstructor;
13
+ antiAttrs: ArrayConstructor;
14
+ protectAttrs: BooleanConstructor;
15
+ outletEvents: ArrayConstructor;
16
+ };
17
+ readonly matchTargets: Array<HTMLElement>;
18
+ readonly hasMatchTarget: boolean;
19
+ readonly emptyTarget: Element;
20
+ readonly hasEmptyTarget: boolean;
21
+ keywordValue: string;
22
+ change(event: Event, updateTo?: TOutletChangeData<string>): void;
23
+ getElementsToSync(): Element[] | null | undefined;
24
+ getValueForElement(element: Element): boolean;
25
+ getState(): string;
26
+ outletUpdate: (event: Event, updateTo?: TOutletChangeData<string>) => void;
27
+ }
@@ -0,0 +1,51 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ 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");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var _StringMatchController_instances, _StringMatchController_compareKeywordToTargets;
7
+ import SyncedBooleanAttributesController from '../synced_boolean_attributes_controller/synced_boolean_attributes_controller';
8
+ class StringMatchController extends SyncedBooleanAttributesController {
9
+ constructor() {
10
+ super(...arguments);
11
+ _StringMatchController_instances.add(this);
12
+ this.outletUpdate = this.change;
13
+ }
14
+ change(event, updateTo = {}) {
15
+ var _a;
16
+ const value = (_a = updateTo.data) !== null && _a !== void 0 ? _a : event.currentTarget.value;
17
+ this.keywordValue = value;
18
+ __classPrivateFieldGet(this, _StringMatchController_instances, "m", _StringMatchController_compareKeywordToTargets).call(this);
19
+ this.sendToOutlets(event, Object.assign(Object.assign({}, updateTo), { data: this.keywordValue }));
20
+ }
21
+ getElementsToSync() {
22
+ return this.matchTargets;
23
+ }
24
+ getValueForElement(element) {
25
+ var _a, _b;
26
+ return (_b = (_a = element.innerText) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(this.keywordValue.toLowerCase())) !== null && _b !== void 0 ? _b : false;
27
+ }
28
+ getState() {
29
+ return this.keywordValue;
30
+ }
31
+ }
32
+ _StringMatchController_instances = new WeakSet(), _StringMatchController_compareKeywordToTargets = function _StringMatchController_compareKeywordToTargets() {
33
+ if (this.hasMatchTarget) {
34
+ let foundMatch = false;
35
+ for (let index in this.matchTargets) {
36
+ const target = this.matchTargets[index];
37
+ const value = this.getValueForElement(target);
38
+ this.updateAttributesForElement(target, value);
39
+ if (value) {
40
+ foundMatch = true;
41
+ }
42
+ }
43
+ if (this.hasEmptyTarget) {
44
+ this.emptyTarget.setAttribute('aria-hidden', String(foundMatch));
45
+ }
46
+ }
47
+ };
48
+ StringMatchController.outlets = SyncedBooleanAttributesController.outlets;
49
+ StringMatchController.targets = ['match', 'empty'];
50
+ StringMatchController.values = Object.assign(Object.assign({}, SyncedBooleanAttributesController.values), { keyword: String });
51
+ export default StringMatchController;
@@ -0,0 +1,64 @@
1
+ import {TOutletChangeData} from '../outlet_manager_controller/outlet_manager_controller'
2
+ import SyncedBooleanAttributesController from '../synced_boolean_attributes_controller/synced_boolean_attributes_controller'
3
+
4
+ export interface StringMatchOutlet extends SyncedBooleanAttributesController<string> {
5
+ change: (event: Event, updateTo?: TOutletChangeData<string>) => void
6
+ }
7
+
8
+ export default class StringMatchController
9
+ extends SyncedBooleanAttributesController<string>
10
+ implements StringMatchOutlet
11
+ {
12
+ static outlets = SyncedBooleanAttributesController.outlets
13
+ static targets = ['match', 'empty']
14
+ static values = {
15
+ ...SyncedBooleanAttributesController.values,
16
+ keyword: String,
17
+ }
18
+
19
+ declare readonly matchTargets: Array<HTMLElement>
20
+ declare readonly hasMatchTarget: boolean
21
+
22
+ declare readonly emptyTarget: Element
23
+ declare readonly hasEmptyTarget: boolean
24
+
25
+ declare keywordValue: string
26
+
27
+ change(event: Event, updateTo: TOutletChangeData<string> = {}) {
28
+ const value = updateTo.data ?? (event.currentTarget as HTMLInputElement).value
29
+ this.keywordValue = value
30
+ this.#compareKeywordToTargets()
31
+ this.sendToOutlets(event, {...updateTo, data: this.keywordValue})
32
+ }
33
+ #compareKeywordToTargets() {
34
+ if (this.hasMatchTarget) {
35
+ let foundMatch = false
36
+ for (let index in this.matchTargets) {
37
+ const target = this.matchTargets[index]
38
+ const value = this.getValueForElement(target)
39
+ this.updateAttributesForElement(target, value)
40
+ if (value) {
41
+ foundMatch = true
42
+ }
43
+ }
44
+
45
+ if (this.hasEmptyTarget) {
46
+ this.emptyTarget.setAttribute('aria-hidden', String(foundMatch))
47
+ }
48
+ }
49
+ }
50
+
51
+ getElementsToSync(): Element[] | null | undefined {
52
+ return this.matchTargets
53
+ }
54
+
55
+ getValueForElement(element: Element) {
56
+ return (element as HTMLElement).innerText?.toLowerCase().includes(this.keywordValue.toLowerCase()) ?? false
57
+ }
58
+
59
+ getState() {
60
+ return this.keywordValue
61
+ }
62
+
63
+ outletUpdate = this.change
64
+ }
@@ -0,0 +1,11 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |component| %>
2
+ <div class="ariadne-flex ariadne-justify-between">
3
+ <%= header %>
4
+ <div>
5
+ <% actions.each do |action| %>
6
+ <%= action %>
7
+ <% end %>
8
+ </div>
9
+ </div>
10
+ <%= description %>
11
+ <% end %>
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # A component to render a subheader with optional description below it and optional action
5
+ # components on the right side
6
+ class SubheaderComponent < Ariadne::Component
7
+ DEFAULT_TAGS = {
8
+ wrapper: :div,
9
+ actions: :div,
10
+ header: :h2,
11
+ description: :span,
12
+ }
13
+
14
+ DEFAULT_CLASSES = {
15
+ wrapper: "ariadne-border-b-2 ariadne-border-solid ariadne-border-black ariadne-border-opacity-20",
16
+ header: "",
17
+ actions: "",
18
+ description: "ariadne-text-black/50",
19
+ }
20
+
21
+ DEFAULT_ATTRIBUTES = {
22
+ wrapper: {},
23
+ header: {},
24
+ description: {},
25
+ actions: {},
26
+ }
27
+
28
+ renders_one :header, lambda { |tag: DEFAULT_TAGS[:header], classes: "", attributes: {}|
29
+ Ariadne::HeadingComponent.new(
30
+ tag: check_incoming_tag(DEFAULT_TAGS[:header], tag),
31
+ classes: merge_class_names(DEFAULT_CLASSES[:header], classes),
32
+ attributes: DEFAULT_ATTRIBUTES[:header].merge({ id: @header_id }).merge(attributes),
33
+ )
34
+ }
35
+
36
+ renders_one :description, lambda { |tag: DEFAULT_TAGS[:description], classes: "", attributes: {}|
37
+ Ariadne::BaseComponent.new(
38
+ tag: check_incoming_tag(DEFAULT_TAGS[:description], tag),
39
+ classes: merge_class_names(DEFAULT_CLASSES[:description], classes),
40
+ attributes: DEFAULT_ATTRIBUTES[:description]
41
+ .merge({ "aria-describedby": @header_id })
42
+ .merge(attributes),
43
+ )
44
+ }
45
+
46
+ renders_many :actions
47
+
48
+ # @example Default
49
+ #
50
+ # <%= render(Ariadne::SubheaderComponent.new) { "Example" } %>
51
+ #
52
+ # @param tag [Symbol, String] The rendered tag name.
53
+ # @param classes [String] <%= link_to_classes_docs %>
54
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
55
+ def initialize(tag: DEFAULT_TAGS[:wrapper], classes: "", id: "subheader-#{SecureRandom.uuid}", attributes: {})
56
+ @tag = check_incoming_tag(DEFAULT_TAGS[:wrapper], tag)
57
+ @header_id = id
58
+ @attributes = DEFAULT_ATTRIBUTES[:wrapper].merge(attributes)
59
+ @classes = merge_class_names(
60
+ DEFAULT_CLASSES[:wrapper],
61
+ classes,
62
+ )
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,44 @@
1
+ import OutletManagerController from '../outlet_manager_controller/outlet_manager_controller';
2
+ export type TStimulusDispatchEvent<TDetails> = {
3
+ target?: Element | undefined;
4
+ detail?: TDetails | undefined;
5
+ prefix?: string | undefined;
6
+ bubbles?: boolean | undefined;
7
+ cancelable?: boolean | undefined;
8
+ preventDefault: () => void;
9
+ };
10
+ export type TBooleanValueDetail = {
11
+ value: boolean;
12
+ };
13
+ export interface TSyncAttrDetail extends TBooleanValueDetail {
14
+ attr: string;
15
+ }
16
+ export default class SyncedBooleanAttributesController<T> extends OutletManagerController<T> {
17
+ #private;
18
+ static values: {
19
+ syncedAttrs: ArrayConstructor;
20
+ antiAttrs: ArrayConstructor;
21
+ protectAttrs: BooleanConstructor;
22
+ outletEvents: ArrayConstructor;
23
+ };
24
+ readonly syncedAttrsValue: string[];
25
+ readonly hasSyncedAttrsValue: boolean;
26
+ readonly antiAttrsValue: string[];
27
+ readonly hasAntiAttrsValue: boolean;
28
+ readonly protectAttrsValue: boolean;
29
+ static removeOnFalseAttrs: {
30
+ [k: string]: boolean;
31
+ };
32
+ syncedAttrsLookup: {
33
+ [k: string]: boolean;
34
+ } | null;
35
+ antiAttrsLookup: {
36
+ [k: string]: boolean;
37
+ } | null;
38
+ getValueForElement(element: Element): boolean | null;
39
+ getElementsToSync(): Array<Element> | null | undefined;
40
+ connect(): void;
41
+ updateAttributesForElement(element: Element, value: boolean): void;
42
+ syncElementAttributes(): void;
43
+ validateAttrChange(dispatchEvent: TStimulusDispatchEvent<TSyncAttrDetail>): void;
44
+ }
@@ -0,0 +1,153 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ 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");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var _SyncedBooleanAttributesController_instances, _SyncedBooleanAttributesController_isSyncedAttr, _SyncedBooleanAttributesController_isAntiAttr, _SyncedBooleanAttributesController_isAttr, _SyncedBooleanAttributesController_setAttrs, _SyncedBooleanAttributesController_getLookupForStringArray;
7
+ import OutletManagerController from '../outlet_manager_controller/outlet_manager_controller';
8
+ /*
9
+ This class isn't used directly by itself because it has no functionality. What it does is establishes
10
+ "synced attrs" and "anti-attrs" so that other controllers can extend this one and not worry about
11
+ implementing the logic themselves (and thus have duplicate logic all over our controllers)
12
+
13
+ To implement this, extend your controller with "SyncedBooleanAttributesController" then spread its
14
+ values into your controllers then implement:
15
+
16
+ export default class MyNewController extends SyncedBooleanAttributesController {
17
+ static values = {
18
+ ...SyncedBooleanAttributesController.values,
19
+ myString: String,
20
+ }
21
+ }
22
+
23
+ Also, consider the functions defined here ABOVE the connection() function. Those functions will
24
+ give you control over how your controller can interact with other SyncedBooleanAttributeControllers.
25
+ Not every controller needs them but you should consider them (description can be found in the functions)
26
+
27
+ And don't forget that when you want to change an attr on an element, you should not do it manually.
28
+ Instead run:
29
+ this.updateAttributesForElement(element, value)
30
+ This will let you take advantage of the ecosystem created by this base controller without any additional work
31
+ */
32
+ class SyncedBooleanAttributesController extends OutletManagerController {
33
+ constructor() {
34
+ super(...arguments);
35
+ _SyncedBooleanAttributesController_instances.add(this);
36
+ this.syncedAttrsLookup = null;
37
+ this.antiAttrsLookup = null;
38
+ }
39
+ getValueForElement(element) {
40
+ // This function allows the base controller to access a given element's
41
+ // current status so the attributes can be set or compared. For example,
42
+ // you will want to make sure the attributes are added initially and are
43
+ // in sync with the controller's state. To ensure this, you can use the
44
+ // default connect() function or add "this.syncElementAttributes()" to your
45
+ // custom connect function. It'll look through your targets and get values for
46
+ // each then set the appropriate attrs and anti-attrs
47
+ return null;
48
+ }
49
+ getElementsToSync() {
50
+ // These are the elements your controller wants to keep in sync with the attrs
51
+ // Sometimes these are this.element, sometimes they're specific targets.
52
+ // Return them here so the base controller can automate some behaviors for you
53
+ return [];
54
+ }
55
+ connect() {
56
+ // This function will sync attrs and anti-attrs when the controller connects.
57
+ // The logic is abstracted to a function so you can override this connect
58
+ // function in favor of your own without having to duplicate the sync logic
59
+ this.syncElementAttributes();
60
+ }
61
+ updateAttributesForElement(element, value) {
62
+ // This is how you should update any synced or anti-synced attrs on your elements
63
+ // Do not do it manually unless you are very sure of what you're doing
64
+ if (this.hasSyncedAttrsValue) {
65
+ __classPrivateFieldGet(this, _SyncedBooleanAttributesController_instances, "m", _SyncedBooleanAttributesController_setAttrs).call(this, element, this.syncedAttrsValue, value);
66
+ }
67
+ if (this.hasAntiAttrsValue) {
68
+ __classPrivateFieldGet(this, _SyncedBooleanAttributesController_instances, "m", _SyncedBooleanAttributesController_setAttrs).call(this, element, this.antiAttrsValue, !value);
69
+ }
70
+ }
71
+ syncElementAttributes() {
72
+ var _a;
73
+ this.syncOutlets();
74
+ // Essentially just a "sync attrs and anti-attrs on mount" function
75
+ const elements = this.getElementsToSync();
76
+ if (elements === null || elements === void 0 ? void 0 : elements.length) {
77
+ for (let index in elements) {
78
+ const element = elements[index];
79
+ const value = (_a = this.getValueForElement(element)) !== null && _a !== void 0 ? _a : false;
80
+ this.updateAttributesForElement(element, value);
81
+ }
82
+ }
83
+ }
84
+ validateAttrChange(dispatchEvent) {
85
+ // If you protect your attrs, then this function will deny other controllers you specify from making changes to them.
86
+ // For example, if you want an item to disappear when it's selected, then your Options controller likely has an "aria-hidden"
87
+ // synced attr. If you use another attr to filter the list and then remove that filter, normally that would unhide your selected
88
+ // element. But if you have Options protect its attrs, the filter behavior won't be allowed to change it at any time and thus
89
+ // the element will remain hidden
90
+ const { target, detail } = dispatchEvent;
91
+ if (target && detail) {
92
+ const currentValue = this.getValueForElement(target);
93
+ if (currentValue !== null && this.protectAttrsValue && __classPrivateFieldGet(this, _SyncedBooleanAttributesController_instances, "m", _SyncedBooleanAttributesController_isAttr).call(this, detail.attr)) {
94
+ dispatchEvent.preventDefault();
95
+ }
96
+ }
97
+ }
98
+ }
99
+ _SyncedBooleanAttributesController_instances = new WeakSet(), _SyncedBooleanAttributesController_isSyncedAttr = function _SyncedBooleanAttributesController_isSyncedAttr(attr) {
100
+ var _a;
101
+ // Helper function to determine if the attr is synced
102
+ if (this.syncedAttrsLookup === null) {
103
+ this.syncedAttrsLookup = __classPrivateFieldGet(this, _SyncedBooleanAttributesController_instances, "m", _SyncedBooleanAttributesController_getLookupForStringArray).call(this, this.syncedAttrsValue);
104
+ }
105
+ return (_a = this.syncedAttrsLookup[attr]) !== null && _a !== void 0 ? _a : false;
106
+ }, _SyncedBooleanAttributesController_isAntiAttr = function _SyncedBooleanAttributesController_isAntiAttr(attr) {
107
+ var _a;
108
+ // Helper function to determine if the attr is anti-synced
109
+ if (this.antiAttrsLookup === null) {
110
+ this.antiAttrsLookup = __classPrivateFieldGet(this, _SyncedBooleanAttributesController_instances, "m", _SyncedBooleanAttributesController_getLookupForStringArray).call(this, this.antiAttrsValue);
111
+ }
112
+ return (_a = this.antiAttrsLookup[attr]) !== null && _a !== void 0 ? _a : false;
113
+ }, _SyncedBooleanAttributesController_isAttr = function _SyncedBooleanAttributesController_isAttr(attr) {
114
+ // Helper function to determine if an attr is known to a controller
115
+ return __classPrivateFieldGet(this, _SyncedBooleanAttributesController_instances, "m", _SyncedBooleanAttributesController_isAntiAttr).call(this, attr) || __classPrivateFieldGet(this, _SyncedBooleanAttributesController_instances, "m", _SyncedBooleanAttributesController_isSyncedAttr).call(this, attr);
116
+ }, _SyncedBooleanAttributesController_setAttrs = function _SyncedBooleanAttributesController_setAttrs(element, attrs, value) {
117
+ // Attempts to change the attr for an element. However, it'll dispatch an event
118
+ // first so other controllers get the opportunity to deny it
119
+ const attrState = JSON.stringify(value);
120
+ for (let index in attrs) {
121
+ const attr = attrs[index];
122
+ const dispatchEvent = this.dispatch('attrChange', {
123
+ target: element,
124
+ detail: { attr, value },
125
+ });
126
+ if (!dispatchEvent.defaultPrevented) {
127
+ if (attrState === 'false' && SyncedBooleanAttributesController.removeOnFalseAttrs[attr]) {
128
+ element.removeAttribute(attr);
129
+ }
130
+ else {
131
+ element.setAttribute(attr, attrState);
132
+ }
133
+ }
134
+ }
135
+ }, _SyncedBooleanAttributesController_getLookupForStringArray = function _SyncedBooleanAttributesController_getLookupForStringArray(arr) {
136
+ // Helper function to return an array of strings into an object for easy lookup
137
+ // While the arrays contained here are small, looking up attrs will happen often
138
+ // so I think it's worth the small sacrifice to memory
139
+ if (!(arr === null || arr === void 0 ? void 0 : arr.length)) {
140
+ return {};
141
+ }
142
+ return arr.reduce((acc, cur) => {
143
+ acc[cur] = true;
144
+ return acc;
145
+ }, {});
146
+ };
147
+ SyncedBooleanAttributesController.values = Object.assign(Object.assign({}, OutletManagerController.values), { syncedAttrs: Array, antiAttrs: Array, protectAttrs: Boolean });
148
+ // Some attributes are only false in HTML if they don't exist
149
+ // If included here, the property will be deleted on "false"
150
+ SyncedBooleanAttributesController.removeOnFalseAttrs = {
151
+ checked: true,
152
+ };
153
+ export default SyncedBooleanAttributesController;