primer_view_components 0.0.123 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/app/assets/javascripts/primer_view_components.js +1 -1
  4. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  5. data/app/assets/styles/primer_view_components.css +2 -2
  6. data/app/assets/styles/primer_view_components.css.map +1 -1
  7. data/app/components/primer/alpha/action_list/item.rb +4 -0
  8. data/app/components/primer/alpha/action_list.css.json +41 -0
  9. data/app/components/primer/alpha/auto_complete.css.json +11 -0
  10. data/app/components/primer/alpha/banner.css.json +14 -0
  11. data/app/components/primer/alpha/button_marketing.css.json +10 -0
  12. data/app/components/primer/alpha/dialog.css.json +63 -0
  13. data/app/components/primer/alpha/dropdown.css.json +21 -0
  14. data/app/components/primer/alpha/layout.css.json +27 -0
  15. data/app/components/primer/alpha/menu.css.json +11 -0
  16. data/app/components/primer/alpha/nav_list/item.rb +3 -1
  17. data/app/components/primer/alpha/nav_list/section.rb +4 -4
  18. data/app/components/primer/alpha/nav_list.js +3 -0
  19. data/app/components/primer/alpha/nav_list.rb +2 -2
  20. data/app/components/primer/alpha/nav_list.ts +4 -0
  21. data/app/components/primer/alpha/overlay/body.rb +26 -0
  22. data/app/components/primer/alpha/overlay/footer.rb +41 -0
  23. data/app/components/primer/alpha/overlay/header.html.erb +15 -0
  24. data/app/components/primer/alpha/overlay/header.rb +47 -0
  25. data/app/components/primer/alpha/overlay.css +1 -0
  26. data/app/components/primer/alpha/overlay.css.json +11 -0
  27. data/app/components/primer/alpha/overlay.css.map +1 -0
  28. data/app/components/primer/alpha/overlay.html.erb +11 -0
  29. data/app/components/primer/alpha/overlay.pcss +14 -0
  30. data/app/components/primer/alpha/overlay.rb +194 -0
  31. data/app/components/primer/alpha/segmented_control.css.json +15 -0
  32. data/app/components/primer/alpha/tab_nav.css.json +10 -0
  33. data/app/components/primer/alpha/text_field.css.json +38 -0
  34. data/app/components/primer/alpha/toggle_switch.css.json +16 -0
  35. data/app/components/primer/alpha/underline_nav.css.json +13 -0
  36. data/app/components/primer/anchored_position.d.ts +27 -0
  37. data/app/components/primer/anchored_position.js +149 -0
  38. data/app/components/primer/anchored_position.ts +167 -0
  39. data/app/components/primer/beta/avatar.css.json +14 -0
  40. data/app/components/primer/beta/avatar_stack.css.json +9 -0
  41. data/app/components/primer/beta/blankslate.css.json +12 -0
  42. data/app/components/primer/beta/border_box.css.json +32 -0
  43. data/app/components/primer/beta/breadcrumbs.css.json +4 -0
  44. data/app/components/primer/beta/button.css.json +22 -0
  45. data/app/components/primer/beta/counter.css.json +6 -0
  46. data/app/components/primer/beta/flash.css.json +15 -0
  47. data/app/components/primer/beta/flash.html.erb +1 -2
  48. data/app/components/primer/beta/label.css.json +20 -0
  49. data/app/components/primer/beta/link.css.json +8 -0
  50. data/app/components/primer/beta/popover.css.json +18 -0
  51. data/app/components/primer/beta/progress_bar.css.json +6 -0
  52. data/app/components/primer/beta/state.css.json +10 -0
  53. data/app/components/primer/beta/subhead.css.json +8 -0
  54. data/app/components/primer/beta/timeline_item.css.json +9 -0
  55. data/app/components/primer/beta/truncate.css.json +6 -0
  56. data/app/components/primer/primer.d.ts +2 -0
  57. data/app/components/primer/primer.js +2 -0
  58. data/app/components/primer/primer.pcss +3 -0
  59. data/app/components/primer/primer.ts +2 -0
  60. data/app/components/primer/truncate.css.json +7 -0
  61. data/app/lib/primer/css/layout.css.json +263 -0
  62. data/app/lib/primer/css/utilities.css.json +1636 -0
  63. data/lib/primer/view_components/linters/base_linter.rb +1 -1
  64. data/lib/primer/view_components/linters/disallow_component_css_counter.rb +30 -0
  65. data/lib/primer/view_components/version.rb +2 -2
  66. data/lib/primer/yard/component_manifest.rb +1 -0
  67. data/previews/primer/alpha/overlay_preview/middle_of_page.html.erb +17 -0
  68. data/previews/primer/alpha/overlay_preview.rb +107 -0
  69. data/static/arguments.json +104 -0
  70. data/static/audited_at.json +4 -0
  71. data/static/classes.json +311 -0
  72. data/static/constants.json +102 -0
  73. data/static/previews.json +21 -0
  74. data/static/statuses.json +4 -0
  75. metadata +19 -2
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Alpha
5
+ # Overlay components codify design patterns related to floating surfaces such
6
+ # as dialogs and menus. They are private components intended to be used by
7
+ # specialized components, and mostly contain presentational logic and
8
+ # behavior.
9
+ #
10
+ # @accessibility
11
+ # - **Overlay Accessible Name**: An overlay should have an accessible name,
12
+ # so screen readers are aware of the purpose of the Overlay when it opens.
13
+ # Give an accessible name setting `:title`. The accessible name will be
14
+ # used as the main heading inside the Overlay.
15
+ # - **Overlay unique id**: A Overlay should be unique. Give a unique id
16
+ # setting `:id`. If no `:id` is given, a default randomize hex id is
17
+ # generated.
18
+ #
19
+ # The combination of both `:title` and `:id` establishes an
20
+ # `aria-labelledby` relationship between the title and the unique id
21
+ # of the Overlay.
22
+ class Overlay < Primer::Component
23
+ DEFAULT_PADDING = :normal
24
+ PADDING_MAPPINGS = {
25
+ DEFAULT_PADDING => nil,
26
+ :condensed => "Overlay-body--paddingCondensed",
27
+ :none => "Overlay-body--paddingNone"
28
+ }.freeze
29
+
30
+ PADDING_OPTIONS = PADDING_MAPPINGS.keys
31
+
32
+ DEFAULT_SIZE = :auto
33
+ SIZE_MAPPINGS = {
34
+ DEFAULT_SIZE => "Overlay--size-auto",
35
+ :small => "Overlay--size-small",
36
+ :medium => "Overlay--size-medium",
37
+ :medium_portrait => "Overlay--size-medium-portrait",
38
+ :large => "Overlay--size-large",
39
+ :xlarge => "Overlay--size-xlarge"
40
+ }.freeze
41
+ SIZE_OPTIONS = SIZE_MAPPINGS.keys
42
+
43
+ DEFAULT_ANCHOR_ALIGN = :start
44
+ ANCHOR_ALIGN_OPTIONS = [DEFAULT_ANCHOR_ALIGN, :center, :end].freeze
45
+
46
+ DEFAULT_ANCHOR_OFFSET = :normal
47
+ ANCHOR_OFFSET_OPTIONS = [DEFAULT_ANCHOR_OFFSET, :spacious].freeze
48
+
49
+ DEFAULT_ANCHOR_SIDE = :outside_bottom
50
+ ANCHOR_SIDE_MAPPINGS = {
51
+ :inside_top => "inside-top",
52
+ :inside_bottom => "inside-bottom",
53
+ :inside_left => "inside-left",
54
+ :inside_right => "inside-right",
55
+ :inside_center => "inside-center",
56
+ :outside_top => "outside-top",
57
+ DEFAULT_ANCHOR_SIDE => "outside-bottom",
58
+ :outside_left => "outside-left",
59
+ :outside_right => "outside-right"
60
+ }.freeze
61
+ ANCHOR_SIDE_OPTIONS = ANCHOR_SIDE_MAPPINGS.keys
62
+
63
+ DEFAULT_POPOVER = :auto
64
+ POPOVER_OPTIONS = [DEFAULT_POPOVER, :manual].freeze
65
+
66
+ ROLE_OPTIONS = [:dialog, :menu].freeze
67
+
68
+ # Optional button to open the Overlay.
69
+ #
70
+ # @param system_arguments [Hash] The same arguments as <%= link_to_component(Primer::ButtonComponent) %>.
71
+ renders_one :show_button, lambda { |**system_arguments|
72
+ system_arguments[:classes] = class_names(
73
+ system_arguments[:classes]
74
+ )
75
+ system_arguments[:id] = "overlay-show-#{@system_arguments[:id]}"
76
+ system_arguments["popovertoggletarget"] = @system_arguments[:id]
77
+ system_arguments[:data] = (system_arguments[:data] || {}).merge({ "show-dialog-id": @system_arguments[:id] })
78
+ Primer::Beta::Button.new(**system_arguments)
79
+ }
80
+
81
+ # Header content.
82
+ #
83
+ # @param divider [Boolean] Show a divider between the header and body.
84
+ # @param visually_hide_title [Boolean] Visually hide the `title` while maintaining a label for assistive technologies.
85
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
86
+ renders_one :header, lambda { |divider: false, size: :medium, visually_hide_title: @visually_hide_title, **system_arguments|
87
+ Primer::Alpha::Overlay::Header.new(
88
+ id: @id,
89
+ title: @title,
90
+ subtitle: @subtitle,
91
+ size: size,
92
+ divider: divider,
93
+ visually_hide_title: visually_hide_title,
94
+ **system_arguments
95
+ )
96
+ }
97
+
98
+ # Required body content.
99
+ #
100
+ # @param padding [Symbol] The padding. <%= one_of(Primer::Alpha::Overlay::PADDING_OPTIONS) %>
101
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
102
+ renders_one :body, lambda { |padding: @padding, **system_arguments|
103
+ Primer::Alpha::Overlay::Body.new(
104
+ padding: padding,
105
+ **system_arguments
106
+ )
107
+ }
108
+
109
+ # Footer content.
110
+ #
111
+ # @param show_divider [Boolean] Show a divider between the footer and body.
112
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
113
+ renders_one :footer, "Footer"
114
+
115
+ # @example Overlay with Cancel and Submit buttons
116
+ # @description
117
+ # An ID is provided which enables wiring of the open and close buttons to the Overlay.
118
+ # @code
119
+ # <%= render(Primer::Alpha::Overlay.new(
120
+ # title: "Overlay Example",
121
+ # id: "my-Overlay",
122
+ # role: :dialog,
123
+ # )) do |d| %>
124
+ # <% d.with_show_button { "Show Overlay" } %>
125
+ # <% d.with_body do %>
126
+ # <p>Some content</p>
127
+ # <% end %>
128
+ # <% end %>
129
+ # @param id [String] The id of the Overlay.
130
+ # @param title [String] Describes the content of the Overlay.
131
+ # @param subtitle [String] Provides dditional context for the Overlay, also setting the `aria-describedby` attribute.
132
+ # @param popover [Symbol] The popover behaviour. <%= one_of(Primer::Alpha::Overlay::POPOVER_OPTIONS) %>
133
+ # @param popover [Symbol] The popover behaviour. <%= one_of(Primer::Alpha::Overlay::POPOVER_OPTIONS) %>
134
+ # @param size [Symbol] The size of the Overlay. <%= one_of(Primer::Alpha::Overlay::SIZE_OPTIONS) %>
135
+ # @param padding [Symbol] The padding given to the Overlay body. <%= one_of(Primer::Alpha::Overlay::PADDING_OPTIONS) %>
136
+ # @param anchor [String] An ID of the element to anchor onto. Defaults to the `show_button`.
137
+ # @param anchor_align [Symbol] The anchor alignment of the Overlay. <%= one_of(Primer::Alpha::Overlay::ANCHOR_ALIGN_OPTIONS) %>
138
+ # @param anchor_side [Symbol] The side to anchor the Overlay to. <%= one_of(Primer::Alpha::Overlay::ANCHOR_SIDE_OPTIONS) %>
139
+ # @param anchor_offset [Symbol] The anchor offset to give the Overlay. <%= one_of(Primer::Alpha::Overlay::ANCHOR_OFFSET_OPTIONS) %>
140
+ # @param allow_out_of_bounds [Boolean] Allow the Overlay to overflow its container.
141
+ # @param visually_hide_title [Boolean] If true will hide the heading title, while still making it available to Screen Readers.
142
+ # @param role [String] The ARIA role. <%= one_of(Primer::Alpha::Overlay::ROLE_OPTIONS) %>
143
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
144
+ def initialize(
145
+ title:,
146
+ role:,
147
+ subtitle: nil,
148
+ popover: DEFAULT_POPOVER,
149
+ size: DEFAULT_SIZE,
150
+ padding: DEFAULT_PADDING,
151
+ anchor: nil,
152
+ anchor_align: DEFAULT_ANCHOR_ALIGN,
153
+ anchor_offset: DEFAULT_ANCHOR_OFFSET,
154
+ anchor_side: DEFAULT_ANCHOR_SIDE,
155
+ allow_out_of_bounds: false,
156
+ visually_hide_title: false,
157
+ id: self.class.generate_id,
158
+ **system_arguments
159
+ )
160
+ @system_arguments = deny_tag_argument(**system_arguments)
161
+
162
+ @system_arguments[:role] = fetch_or_fallback(ROLE_OPTIONS, role)
163
+
164
+ @system_arguments[:id] = id.to_s
165
+ @system_arguments[:classes] = class_names(
166
+ "Overlay",
167
+ SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)],
168
+ "Overlay--motion-scaleFade",
169
+ system_arguments[:classes]
170
+ )
171
+ @system_arguments[:tag] = "anchored-position"
172
+ @system_arguments[:anchor] = anchor || "overlay-show-#{@system_arguments[:id]}"
173
+ @system_arguments[:align] = fetch_or_fallback(ANCHOR_ALIGN_OPTIONS, anchor_align, DEFAULT_ANCHOR_ALIGN)
174
+ @system_arguments[:side] = ANCHOR_SIDE_MAPPINGS[fetch_or_fallback(ANCHOR_SIDE_OPTIONS, anchor_side, DEFAULT_ANCHOR_SIDE)]
175
+ @system_arguments["anchor-offset"] = fetch_or_fallback(ANCHOR_OFFSET_OPTIONS, anchor_offset, DEFAULT_ANCHOR_OFFSET)
176
+ @system_arguments["allow-out-of-bounds"] = true if allow_out_of_bounds
177
+ @id = id.to_s
178
+ @title = title
179
+ @subtitle = subtitle
180
+ @visually_hide_title = visually_hide_title
181
+ @padding = padding
182
+
183
+ @system_arguments[:popover] = popover
184
+ @system_arguments[:aria] ||= {}
185
+ @system_arguments[:aria][:describedby] ||= "#{@id}-description"
186
+ end
187
+
188
+ def before_render
189
+ with_header unless header?
190
+ with_body unless body?
191
+ end
192
+ end
193
+ end
194
+ end
@@ -27,5 +27,20 @@
27
27
  ".SegmentedControl--fullWidth .SegmentedControl-item",
28
28
  ".SegmentedControl--fullWidth .Button--iconOnly",
29
29
  ".SegmentedControl--fullWidth .Button-withTooltip"
30
+ ],
31
+ "classes": [
32
+ "SegmentedControl",
33
+ "SegmentedControl-item",
34
+ "SegmentedControl-item--selected",
35
+ "Button",
36
+ "Button-label",
37
+ "Button--small",
38
+ "Button--iconOnly",
39
+ "Button--medium",
40
+ "Button--large",
41
+ "Button--invisible",
42
+ "Button--invisible-noVisuals",
43
+ "SegmentedControl--fullWidth",
44
+ "Button-withTooltip"
30
45
  ]
31
46
  }
@@ -20,5 +20,15 @@
20
20
  ".tabnav-extra>.octicon",
21
21
  "a.tabnav-extra:hover",
22
22
  ".tabnav-btn"
23
+ ],
24
+ "classes": [
25
+ "tabnav",
26
+ "tabnav-tabs",
27
+ "tabnav-tab",
28
+ "selected",
29
+ "octicon",
30
+ "Counter",
31
+ "tabnav-extra",
32
+ "tabnav-btn"
23
33
  ]
24
34
  }
@@ -130,5 +130,43 @@
130
130
  "input.FormControl-radio[type=radio]:checked",
131
131
  "input.FormControl-radio[type=radio]:checked:disabled",
132
132
  "input.FormControl-radio[type=radio]:focus-visible"
133
+ ],
134
+ "classes": [
135
+ "FormControl",
136
+ "FormControl--fullWidth",
137
+ "FormControl-label",
138
+ "FormControl-caption",
139
+ "FormControl-inlineValidation",
140
+ "FormControl-spacingWrapper",
141
+ "FormControl-horizontalGroup",
142
+ "FormControl-input",
143
+ "FormControl-select",
144
+ "FormControl-textarea",
145
+ "FormControl-small",
146
+ "FormControl-medium",
147
+ "FormControl-large",
148
+ "FormControl-inset",
149
+ "FormControl-monospace",
150
+ "FormControl-error",
151
+ "FormControl-success",
152
+ "FormControl-warning",
153
+ "FormControl-toggleSwitchInput",
154
+ "FormControl-input-wrap",
155
+ "FormControl-input-leadingVisualWrap",
156
+ "FormControl-input-leadingVisual",
157
+ "FormControl-input-trailingAction",
158
+ "FormControl-input-trailingAction--divider",
159
+ "FormControl-input-wrap--leadingVisual",
160
+ "FormControl-input-wrap--trailingAction",
161
+ "FormControl-input-wrap-trailingAction--divider",
162
+ "FormControl-select-wrap",
163
+ "FormControl-checkbox-wrap",
164
+ "FormControl-radio-wrap",
165
+ "FormControl-checkbox-labelWrap",
166
+ "FormControl-radio-labelWrap",
167
+ "FormControl-check-group-wrap",
168
+ "FormControl-radio-group-wrap",
169
+ "FormControl-checkbox",
170
+ "FormControl-radio"
133
171
  ]
134
172
  }
@@ -36,5 +36,21 @@
36
36
  ".ToggleSwitch-statusOff",
37
37
  ".ToggleSwitch--statusAtEnd",
38
38
  ".ToggleSwitch--statusAtEnd .ToggleSwitch-status"
39
+ ],
40
+ "classes": [
41
+ "ToggleSwitch",
42
+ "ToggleSwitch--checked",
43
+ "ToggleSwitch-statusOn",
44
+ "ToggleSwitch-statusOff",
45
+ "ToggleSwitch-track",
46
+ "ToggleSwitch-knob",
47
+ "ToggleSwitch-lineIcon",
48
+ "ToggleSwitch-circleIcon",
49
+ "ToggleSwitch-icons",
50
+ "ToggleSwitch-status",
51
+ "ToggleSwitch-statusIcon",
52
+ "ToggleSwitch--small",
53
+ "ToggleSwitch--disabled",
54
+ "ToggleSwitch--statusAtEnd"
39
55
  ]
40
56
  }
@@ -24,5 +24,18 @@
24
24
  ".UnderlineNav--full .UnderlineNav-body",
25
25
  ".UnderlineNav-octicon",
26
26
  ".UnderlineNav-container"
27
+ ],
28
+ "classes": [
29
+ "UnderlineNav",
30
+ "Counter",
31
+ "Counter--primary",
32
+ "UnderlineNav-body",
33
+ "UnderlineNav-item",
34
+ "selected",
35
+ "UnderlineNav--right",
36
+ "UnderlineNav-actions",
37
+ "UnderlineNav--full",
38
+ "UnderlineNav-octicon",
39
+ "UnderlineNav-container"
27
40
  ]
28
41
  }
@@ -0,0 +1,27 @@
1
+ import type { AnchorAlignment, AnchorSide, PositionSettings } from '@primer/behaviors';
2
+ export default class AnchoredPositionElement extends HTMLElement implements PositionSettings {
3
+ #private;
4
+ get align(): AnchorAlignment;
5
+ set align(value: AnchorAlignment);
6
+ get side(): AnchorSide;
7
+ set side(value: AnchorSide);
8
+ get anchorOffset(): number;
9
+ set anchorOffset(value: number | 'normal' | 'spacious');
10
+ get anchor(): string;
11
+ set anchor(value: string);
12
+ get anchorElement(): HTMLElement | null;
13
+ set anchorElement(value: HTMLElement | null);
14
+ get alignmentOffset(): number;
15
+ set alignmentOffset(value: number);
16
+ get allowOutOfBounds(): boolean;
17
+ set allowOutOfBounds(value: boolean);
18
+ connectedCallback(): void;
19
+ static observedAttributes: string[];
20
+ attributeChangedCallback(): void;
21
+ update(): void;
22
+ }
23
+ declare global {
24
+ interface Window {
25
+ AnchoredPositionElement: typeof AnchoredPositionElement;
26
+ }
27
+ }
@@ -0,0 +1,149 @@
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 __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
+ if (kind === "m") throw new TypeError("Private method is not writable");
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
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
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
+ };
12
+ var _AnchoredPositionElement_anchorElement, _AnchoredPositionElement_animationFrame;
13
+ import { getAnchoredPosition } from '@primer/behaviors';
14
+ const updateWhenVisible = (() => {
15
+ const anchors = new Set();
16
+ let intersectionObserver = null;
17
+ let resizeObserver = null;
18
+ function updateVisibleAnchors() {
19
+ for (const anchor of anchors) {
20
+ anchor.update();
21
+ }
22
+ }
23
+ return (el) => {
24
+ // eslint-disable-next-line github/prefer-observers
25
+ window.addEventListener('resize', updateVisibleAnchors);
26
+ intersectionObserver || (intersectionObserver = new IntersectionObserver(entries => {
27
+ for (const entry of entries) {
28
+ const target = entry.target;
29
+ if (entry.isIntersecting) {
30
+ target.update();
31
+ anchors.add(target);
32
+ }
33
+ else {
34
+ anchors.delete(target);
35
+ }
36
+ }
37
+ }));
38
+ resizeObserver || (resizeObserver = new ResizeObserver(() => {
39
+ for (const anchor of anchors) {
40
+ anchor.update();
41
+ }
42
+ }));
43
+ resizeObserver.observe(el.ownerDocument.documentElement);
44
+ intersectionObserver.observe(el);
45
+ };
46
+ })();
47
+ export default class AnchoredPositionElement extends HTMLElement {
48
+ constructor() {
49
+ super(...arguments);
50
+ _AnchoredPositionElement_anchorElement.set(this, null);
51
+ _AnchoredPositionElement_animationFrame.set(this, void 0);
52
+ }
53
+ get align() {
54
+ const value = this.getAttribute('align');
55
+ if (value === 'center' || value === 'end')
56
+ return value;
57
+ return 'start';
58
+ }
59
+ set align(value) {
60
+ this.setAttribute('align', `${value}`);
61
+ }
62
+ get side() {
63
+ const value = this.getAttribute('side');
64
+ if (value === 'inside-top' ||
65
+ value === 'inside-bottom' ||
66
+ value === 'inside-left' ||
67
+ value === 'inside-right' ||
68
+ value === 'inside-center' ||
69
+ value === 'outside-top' ||
70
+ value === 'outside-left' ||
71
+ value === 'outside-right') {
72
+ return value;
73
+ }
74
+ return 'outside-bottom';
75
+ }
76
+ set side(value) {
77
+ this.setAttribute('side', `${value}`);
78
+ }
79
+ get anchorOffset() {
80
+ const alias = this.getAttribute('anchor-offset');
81
+ if (alias === 'spacious' || alias === '8')
82
+ return 8;
83
+ return 4;
84
+ }
85
+ set anchorOffset(value) {
86
+ this.setAttribute('anchor-offset', `${value}`);
87
+ }
88
+ get anchor() {
89
+ return this.getAttribute('anchor') || '';
90
+ }
91
+ set anchor(value) {
92
+ this.setAttribute('anchor', `${value}`);
93
+ }
94
+ get anchorElement() {
95
+ if (__classPrivateFieldGet(this, _AnchoredPositionElement_anchorElement, "f"))
96
+ return __classPrivateFieldGet(this, _AnchoredPositionElement_anchorElement, "f");
97
+ const idRef = this.anchor;
98
+ if (!idRef)
99
+ return null;
100
+ return this.ownerDocument.getElementById(idRef);
101
+ }
102
+ set anchorElement(value) {
103
+ __classPrivateFieldSet(this, _AnchoredPositionElement_anchorElement, value, "f");
104
+ if (!__classPrivateFieldGet(this, _AnchoredPositionElement_anchorElement, "f")) {
105
+ this.removeAttribute('anchor');
106
+ }
107
+ }
108
+ get alignmentOffset() {
109
+ return Number(this.getAttribute('alignment-offset'));
110
+ }
111
+ set alignmentOffset(value) {
112
+ this.setAttribute('alignment-offset', `${value}`);
113
+ }
114
+ get allowOutOfBounds() {
115
+ return this.hasAttribute('allow-out-of-bounds');
116
+ }
117
+ set allowOutOfBounds(value) {
118
+ this.toggleAttribute('allow-out-of-bounds', value);
119
+ }
120
+ connectedCallback() {
121
+ this.update();
122
+ this.addEventListener('beforetoggle', () => this.update());
123
+ updateWhenVisible(this);
124
+ }
125
+ attributeChangedCallback() {
126
+ this.update();
127
+ }
128
+ update() {
129
+ if (!this.isConnected)
130
+ return;
131
+ cancelAnimationFrame(__classPrivateFieldGet(this, _AnchoredPositionElement_animationFrame, "f"));
132
+ __classPrivateFieldSet(this, _AnchoredPositionElement_animationFrame, requestAnimationFrame(() => {
133
+ const anchor = this.anchorElement;
134
+ if (!anchor)
135
+ return;
136
+ const { left, top, anchorSide, anchorAlign } = getAnchoredPosition(this, anchor, this);
137
+ this.style.top = `${top}px`;
138
+ this.style.left = `${left}px`;
139
+ this.classList.remove('Overlay--anchorAlign-start', 'Overlay--anchorAlign-center', 'Overlay--anchorAlign-end', 'Overlay--anchorSide-insideTop', 'Overlay--anchorSide-insideBottom', 'Overlay--anchorSide-insideLeft', 'Overlay--anchorSide-insideRight', 'Overlay--anchorSide-insideCenter', 'Overlay--anchorSide-outsideTop', 'Overlay--anchorSide-outsideLeft', 'Overlay--anchorSide-outsideRight');
140
+ this.classList.add(`Overlay--anchorAlign-${anchorAlign}`, `Overlay--anchorSide-${anchorSide}`);
141
+ }), "f");
142
+ }
143
+ }
144
+ _AnchoredPositionElement_anchorElement = new WeakMap(), _AnchoredPositionElement_animationFrame = new WeakMap();
145
+ AnchoredPositionElement.observedAttributes = ['align', 'side', 'anchor', 'alignment-offset', 'allow-out-of-bounds'];
146
+ if (!customElements.get('anchored-position')) {
147
+ window.AnchoredPositionElement = AnchoredPositionElement;
148
+ customElements.define('anchored-position', AnchoredPositionElement);
149
+ }
@@ -0,0 +1,167 @@
1
+ import type {AnchorAlignment, AnchorSide, PositionSettings} from '@primer/behaviors'
2
+ import {getAnchoredPosition} from '@primer/behaviors'
3
+
4
+ const updateWhenVisible = (() => {
5
+ const anchors = new Set<AnchoredPositionElement>()
6
+ let intersectionObserver: IntersectionObserver | null = null
7
+ let resizeObserver: ResizeObserver | null = null
8
+ function updateVisibleAnchors() {
9
+ for (const anchor of anchors) {
10
+ anchor.update()
11
+ }
12
+ }
13
+ return (el: AnchoredPositionElement) => {
14
+ // eslint-disable-next-line github/prefer-observers
15
+ window.addEventListener('resize', updateVisibleAnchors)
16
+ intersectionObserver ||= new IntersectionObserver(entries => {
17
+ for (const entry of entries) {
18
+ const target = entry.target as AnchoredPositionElement
19
+ if (entry.isIntersecting) {
20
+ target.update()
21
+ anchors.add(target)
22
+ } else {
23
+ anchors.delete(target)
24
+ }
25
+ }
26
+ })
27
+ resizeObserver ||= new ResizeObserver(() => {
28
+ for (const anchor of anchors) {
29
+ anchor.update()
30
+ }
31
+ })
32
+ resizeObserver.observe(el.ownerDocument.documentElement)
33
+ intersectionObserver.observe(el)
34
+ }
35
+ })()
36
+
37
+ export default class AnchoredPositionElement extends HTMLElement implements PositionSettings {
38
+ get align(): AnchorAlignment {
39
+ const value = this.getAttribute('align')
40
+ if (value === 'center' || value === 'end') return value
41
+ return 'start'
42
+ }
43
+
44
+ set align(value: AnchorAlignment) {
45
+ this.setAttribute('align', `${value}`)
46
+ }
47
+
48
+ get side(): AnchorSide {
49
+ const value = this.getAttribute('side')
50
+ if (
51
+ value === 'inside-top' ||
52
+ value === 'inside-bottom' ||
53
+ value === 'inside-left' ||
54
+ value === 'inside-right' ||
55
+ value === 'inside-center' ||
56
+ value === 'outside-top' ||
57
+ value === 'outside-left' ||
58
+ value === 'outside-right'
59
+ ) {
60
+ return value
61
+ }
62
+ return 'outside-bottom'
63
+ }
64
+
65
+ set side(value: AnchorSide) {
66
+ this.setAttribute('side', `${value}`)
67
+ }
68
+
69
+ get anchorOffset(): number {
70
+ const alias = this.getAttribute('anchor-offset')
71
+ if (alias === 'spacious' || alias === '8') return 8
72
+ return 4
73
+ }
74
+
75
+ set anchorOffset(value: number | 'normal' | 'spacious') {
76
+ this.setAttribute('anchor-offset', `${value}`)
77
+ }
78
+
79
+ get anchor() {
80
+ return this.getAttribute('anchor') || ''
81
+ }
82
+
83
+ set anchor(value: string) {
84
+ this.setAttribute('anchor', `${value}`)
85
+ }
86
+
87
+ #anchorElement: HTMLElement | null = null
88
+ get anchorElement(): HTMLElement | null {
89
+ if (this.#anchorElement) return this.#anchorElement
90
+ const idRef = this.anchor
91
+ if (!idRef) return null
92
+ return this.ownerDocument.getElementById(idRef)
93
+ }
94
+
95
+ set anchorElement(value: HTMLElement | null) {
96
+ this.#anchorElement = value
97
+ if (!this.#anchorElement) {
98
+ this.removeAttribute('anchor')
99
+ }
100
+ }
101
+
102
+ get alignmentOffset(): number {
103
+ return Number(this.getAttribute('alignment-offset'))
104
+ }
105
+
106
+ set alignmentOffset(value: number) {
107
+ this.setAttribute('alignment-offset', `${value}`)
108
+ }
109
+
110
+ get allowOutOfBounds() {
111
+ return this.hasAttribute('allow-out-of-bounds')
112
+ }
113
+
114
+ set allowOutOfBounds(value: boolean) {
115
+ this.toggleAttribute('allow-out-of-bounds', value)
116
+ }
117
+
118
+ connectedCallback() {
119
+ this.update()
120
+ this.addEventListener('beforetoggle', () => this.update())
121
+ updateWhenVisible(this)
122
+ }
123
+
124
+ static observedAttributes = ['align', 'side', 'anchor', 'alignment-offset', 'allow-out-of-bounds']
125
+ attributeChangedCallback() {
126
+ this.update()
127
+ }
128
+
129
+ #animationFrame: ReturnType<typeof requestAnimationFrame>
130
+ update() {
131
+ if (!this.isConnected) return
132
+ cancelAnimationFrame(this.#animationFrame)
133
+
134
+ this.#animationFrame = requestAnimationFrame(() => {
135
+ const anchor = this.anchorElement
136
+ if (!anchor) return
137
+ const {left, top, anchorSide, anchorAlign} = getAnchoredPosition(this, anchor, this)
138
+ this.style.top = `${top}px`
139
+ this.style.left = `${left}px`
140
+ this.classList.remove(
141
+ 'Overlay--anchorAlign-start',
142
+ 'Overlay--anchorAlign-center',
143
+ 'Overlay--anchorAlign-end',
144
+ 'Overlay--anchorSide-insideTop',
145
+ 'Overlay--anchorSide-insideBottom',
146
+ 'Overlay--anchorSide-insideLeft',
147
+ 'Overlay--anchorSide-insideRight',
148
+ 'Overlay--anchorSide-insideCenter',
149
+ 'Overlay--anchorSide-outsideTop',
150
+ 'Overlay--anchorSide-outsideLeft',
151
+ 'Overlay--anchorSide-outsideRight'
152
+ )
153
+ this.classList.add(`Overlay--anchorAlign-${anchorAlign}`, `Overlay--anchorSide-${anchorSide}`)
154
+ })
155
+ }
156
+ }
157
+
158
+ if (!customElements.get('anchored-position')) {
159
+ window.AnchoredPositionElement = AnchoredPositionElement
160
+ customElements.define('anchored-position', AnchoredPositionElement)
161
+ }
162
+
163
+ declare global {
164
+ interface Window {
165
+ AnchoredPositionElement: typeof AnchoredPositionElement
166
+ }
167
+ }
@@ -13,5 +13,19 @@
13
13
  ".avatar-6",
14
14
  ".avatar-7",
15
15
  ".avatar-8"
16
+ ],
17
+ "classes": [
18
+ "avatar",
19
+ "avatar-link",
20
+ "avatar-group-item",
21
+ "avatar-1",
22
+ "avatar-2",
23
+ "avatar-small",
24
+ "avatar-3",
25
+ "avatar-4",
26
+ "avatar-5",
27
+ "avatar-6",
28
+ "avatar-7",
29
+ "avatar-8"
16
30
  ]
17
31
  }