chrome-devtools-frontend 1.0.945329 → 1.0.945579

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 (45) hide show
  1. package/config/gni/devtools_grd_files.gni +1 -0
  2. package/config/gni/devtools_image_files.gni +1 -0
  3. package/front_end/Images/src/circled_exclamation_icon.svg +3 -0
  4. package/front_end/core/sdk/CSSMetadata.ts +0 -1
  5. package/front_end/generated/protocol.d.ts +0 -4
  6. package/front_end/models/emulation/EmulatedDevices.ts +2 -4
  7. package/front_end/panels/application/BackForwardCacheView.ts +8 -1
  8. package/front_end/panels/elements/StyleEditorWidget.ts +13 -2
  9. package/front_end/panels/elements/StylePropertyTreeElement.ts +8 -12
  10. package/front_end/panels/elements/StylesSidebarPane.ts +35 -9
  11. package/front_end/ui/components/adorners/Adorner.ts +14 -14
  12. package/front_end/ui/components/buttons/Button.ts +116 -42
  13. package/front_end/ui/components/data_grid/DataGrid.ts +122 -122
  14. package/front_end/ui/components/data_grid/DataGridController.ts +42 -42
  15. package/front_end/ui/components/diff_view/DiffView.ts +4 -4
  16. package/front_end/ui/components/docs/button/basic.html +3 -0
  17. package/front_end/ui/components/docs/button/basic.ts +16 -0
  18. package/front_end/ui/components/expandable_list/ExpandableList.ts +11 -11
  19. package/front_end/ui/components/icon_button/Icon.ts +24 -21
  20. package/front_end/ui/components/icon_button/IconButton.ts +31 -31
  21. package/front_end/ui/components/issue_counter/IssueCounter.ts +52 -52
  22. package/front_end/ui/components/issue_counter/IssueLinkIcon.ts +42 -42
  23. package/front_end/ui/components/linear_memory_inspector/LinearMemoryInspector.ts +67 -67
  24. package/front_end/ui/components/linear_memory_inspector/LinearMemoryInspectorController.ts +22 -22
  25. package/front_end/ui/components/linear_memory_inspector/LinearMemoryInspectorPane.ts +36 -36
  26. package/front_end/ui/components/linear_memory_inspector/LinearMemoryNavigator.ts +19 -19
  27. package/front_end/ui/components/linear_memory_inspector/LinearMemoryValueInterpreter.ts +25 -25
  28. package/front_end/ui/components/linear_memory_inspector/LinearMemoryViewer.ts +52 -52
  29. package/front_end/ui/components/linear_memory_inspector/ValueInterpreterDisplay.ts +21 -21
  30. package/front_end/ui/components/linear_memory_inspector/ValueInterpreterSettings.ts +6 -6
  31. package/front_end/ui/components/markdown_view/MarkdownImage.ts +14 -14
  32. package/front_end/ui/components/markdown_view/MarkdownLink.ts +8 -8
  33. package/front_end/ui/components/markdown_view/MarkdownView.ts +6 -6
  34. package/front_end/ui/components/render_coordinator/RenderCoordinator.ts +33 -33
  35. package/front_end/ui/components/report_view/ReportView.ts +18 -18
  36. package/front_end/ui/components/request_link_icon/RequestLinkIcon.ts +53 -53
  37. package/front_end/ui/components/settings/SettingCheckbox.ts +15 -15
  38. package/front_end/ui/components/survey_link/SurveyLink.ts +28 -28
  39. package/front_end/ui/components/text_editor/TextEditor.ts +51 -51
  40. package/front_end/ui/components/text_editor/javascript.ts +6 -6
  41. package/front_end/ui/components/text_prompt/TextPrompt.ts +19 -19
  42. package/front_end/ui/components/tree_outline/TreeOutline.ts +56 -56
  43. package/front_end/ui/legacy/Infobar.ts +9 -0
  44. package/front_end/ui/legacy/tabbedPane.css +1 -1
  45. package/package.json +1 -1
@@ -45,6 +45,7 @@ grd_files_release_sources = [
45
45
  "front_end/Images/chromeRight.avif",
46
46
  "front_end/Images/chromeSelect.svg",
47
47
  "front_end/Images/chromeSelectDark.svg",
48
+ "front_end/Images/circled_exclamation_icon.svg",
48
49
  "front_end/Images/close-icon.svg",
49
50
  "front_end/Images/copy_icon.svg",
50
51
  "front_end/Images/cssoverview_icons_2x.avif",
@@ -56,6 +56,7 @@ devtools_svg_sources = [
56
56
  "chevrons.svg",
57
57
  "chromeSelect.svg",
58
58
  "chromeSelectDark.svg",
59
+ "circled_exclamation_icon.svg",
59
60
  "close-icon.svg",
60
61
  "copy_icon.svg",
61
62
  "dropdown_7x6_icon.svg",
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 4.233 4.233" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M7.25 10.25h1.5v1.5h-1.5zm0-6h1.5v4.5h-1.5ZM7.992.5C3.853.5.5 3.86.5 8c0 4.14 3.353 7.5 7.492 7.5 4.148 0 7.508-3.36 7.508-7.5 0-4.14-3.36-7.5-7.508-7.5ZM8 14c-3.315 0-6-2.685-6-6s2.685-6 6-6 6 2.685 6 6-2.685 6-6 6z" transform="scale(.26458)" style="fill:#000"/>
3
+ </svg>
@@ -567,7 +567,6 @@ const extraPropertyValues = {
567
567
  'ui-monospace',
568
568
  'ui-rounded',
569
569
  '-webkit-body',
570
- '-webkit-pictograph',
571
570
  ],
572
571
  },
573
572
  'zoom': {values: ['normal']},
@@ -10442,10 +10442,6 @@ declare namespace Protocol {
10442
10442
  * The fantasy font-family.
10443
10443
  */
10444
10444
  fantasy?: string;
10445
- /**
10446
- * The pictograph font-family.
10447
- */
10448
- pictograph?: string;
10449
10445
  }
10450
10446
 
10451
10447
  /**
@@ -502,14 +502,12 @@ export class EmulatedDevicesList extends Common.ObjectWrapper.ObjectWrapper<Even
502
502
  constructor() {
503
503
  super();
504
504
 
505
- this.#standardSetting = Common.Settings.Settings.instance().createSetting(
506
- 'standardEmulatedDeviceList', [], Common.Settings.SettingStorageType.Synced);
505
+ this.#standardSetting = Common.Settings.Settings.instance().createSetting('standardEmulatedDeviceList', []);
507
506
  this.#standardInternal = new Set();
508
507
  this.listFromJSONV1(this.#standardSetting.get(), this.#standardInternal);
509
508
  this.updateStandardDevices();
510
509
 
511
- this.#customSetting = Common.Settings.Settings.instance().createSetting(
512
- 'customEmulatedDeviceList', [], Common.Settings.SettingStorageType.Synced);
510
+ this.#customSetting = Common.Settings.Settings.instance().createSetting('customEmulatedDeviceList', []);
513
511
  this.#customInternal = new Set();
514
512
  if (!this.listFromJSONV1(this.#customSetting.get(), this.#customInternal)) {
515
513
  this.saveCustomDevices();
@@ -262,7 +262,14 @@ export class BackForwardCacheView extends UI.ThrottledWidget.ThrottledWidget {
262
262
 
263
263
  private renderReason(explanation: Protocol.Page.BackForwardCacheNotRestoredExplanation): LitHtml.TemplateResult {
264
264
  return LitHtml.html`
265
- <li>${explanation.reason} : ${
265
+ <li>
266
+ <${IconButton.Icon.Icon.litTagName} class="inline-icon" .data=${{
267
+ iconName: 'circled_exclamation_icon',
268
+ color: 'orange',
269
+ width: '16px',
270
+ height: '16px',
271
+ } as IconButton.Icon.IconData}></${IconButton.Icon.Icon.litTagName}>
272
+ ${explanation.reason} : ${
266
273
  (explanation.reason in NotRestoredReasonDescription) ?
267
274
  LitHtml.html`${NotRestoredReasonDescription[explanation.reason].name()}` :
268
275
  LitHtml.nothing} </li>
@@ -30,6 +30,8 @@ export class StyleEditorWidget extends UI.Widget.VBox {
30
30
  private section?: StylePropertiesSection;
31
31
  private editorContainer: HTMLElement;
32
32
 
33
+ #triggerKey: string|undefined;
34
+
33
35
  constructor() {
34
36
  super(true);
35
37
  this.contentElement.tabIndex = 0;
@@ -71,6 +73,14 @@ export class StyleEditorWidget extends UI.Widget.VBox {
71
73
  this.editor?.addEventListener('propertydeselected', this.onPropertyDeselected);
72
74
  }
73
75
 
76
+ setTriggerKey(value: string): void {
77
+ this.#triggerKey = value;
78
+ }
79
+
80
+ getTriggerKey(): string|undefined {
81
+ return this.#triggerKey;
82
+ }
83
+
74
84
  unbindContext(): void {
75
85
  this.pane = undefined;
76
86
  this.section = undefined;
@@ -105,8 +115,8 @@ export class StyleEditorWidget extends UI.Widget.VBox {
105
115
  }
106
116
 
107
117
  static createTriggerButton(
108
- pane: StylesSidebarPane, section: StylePropertiesSection, editorClass: {new(): Editor},
109
- buttonTitle: string): HTMLElement {
118
+ pane: StylesSidebarPane, section: StylePropertiesSection, editorClass: {new(): Editor}, buttonTitle: string,
119
+ triggerKey: string): HTMLElement {
110
120
  const triggerButton = createButton(buttonTitle);
111
121
 
112
122
  triggerButton.onclick = async(event): Promise<void> => {
@@ -115,6 +125,7 @@ export class StyleEditorWidget extends UI.Widget.VBox {
115
125
  const widget = StyleEditorWidget.instance();
116
126
  widget.setEditor(editorClass);
117
127
  widget.bindContext(pane, section);
128
+ widget.setTriggerKey(triggerKey);
118
129
  await widget.render();
119
130
  const scrollerElement = triggerButton.enclosingNodeOrSelfWithClass('style-panes-wrapper');
120
131
  const onScroll = (): void => {
@@ -668,21 +668,17 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
668
668
  const section = this.section();
669
669
  if (this.valueElement && section && section.editable && this.property.name === 'display') {
670
670
  const propertyValue = this.property.trimmedValueWithoutImportant();
671
- if (propertyValue === 'flex' || propertyValue === 'inline-flex') {
671
+ const isFlex = propertyValue === 'flex' || propertyValue === 'inline-flex';
672
+ const isGrid = propertyValue === 'grid' || propertyValue === 'inline-grid';
673
+ if (isFlex || isGrid) {
674
+ const key = `${section.getSectionIdx()}_${section.nextEditorTriggerButtonIdx}`;
672
675
  const button = StyleEditorWidget.createTriggerButton(
673
- this.parentPaneInternal, section, FlexboxEditor, i18nString(UIStrings.flexboxEditorButton));
676
+ this.parentPaneInternal, section, isFlex ? FlexboxEditor : GridEditor,
677
+ isFlex ? i18nString(UIStrings.flexboxEditorButton) : i18nString(UIStrings.gridEditorButton), key);
678
+ section.nextEditorTriggerButtonIdx++;
674
679
  this.listItemElement.appendChild(button);
675
680
  const helper = this.parentPaneInternal.swatchPopoverHelper();
676
- if (helper.isShowing(StyleEditorWidget.instance())) {
677
- helper.setAnchorElement(button);
678
- }
679
- }
680
- if (propertyValue === 'grid' || propertyValue === 'inline-grid') {
681
- const button = StyleEditorWidget.createTriggerButton(
682
- this.parentPaneInternal, section, GridEditor, i18nString(UIStrings.gridEditorButton));
683
- this.listItemElement.appendChild(button);
684
- const helper = this.parentPaneInternal.swatchPopoverHelper();
685
- if (helper.isShowing(StyleEditorWidget.instance())) {
681
+ if (helper.isShowing(StyleEditorWidget.instance()) && StyleEditorWidget.instance().getTriggerKey() === key) {
686
682
  helper.setAnchorElement(button);
687
683
  }
688
684
  }
@@ -832,6 +832,7 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
832
832
  this.idleCallbackManager = new IdleCallbackManager();
833
833
 
834
834
  const blocks = [new SectionBlock(null)];
835
+ let sectionIdx = 0;
835
836
  let lastParentNode: SDK.DOMModel.DOMNode|null = null;
836
837
  for (const style of matchedStyles.nodeStyles()) {
837
838
  const parentNode = matchedStyles.isInherited(style) ? matchedStyles.nodeForStyle(style) : null;
@@ -844,7 +845,8 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
844
845
  const lastBlock = blocks[blocks.length - 1];
845
846
  if (lastBlock) {
846
847
  this.idleCallbackManager.schedule(() => {
847
- const section = new StylePropertiesSection(this, matchedStyles, style);
848
+ const section = new StylePropertiesSection(this, matchedStyles, style, sectionIdx);
849
+ sectionIdx++;
848
850
  lastBlock.sections.push(section);
849
851
  });
850
852
  }
@@ -860,7 +862,8 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
860
862
  const block = SectionBlock.createPseudoTypeBlock(pseudoType);
861
863
  for (const style of matchedStyles.pseudoStyles(pseudoType)) {
862
864
  this.idleCallbackManager.schedule(() => {
863
- const section = new StylePropertiesSection(this, matchedStyles, style);
865
+ const section = new StylePropertiesSection(this, matchedStyles, style, sectionIdx);
866
+ sectionIdx++;
864
867
  block.sections.push(section);
865
868
  });
866
869
  }
@@ -871,7 +874,8 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
871
874
  const block = SectionBlock.createKeyframesBlock(keyframesRule.name().text);
872
875
  for (const keyframe of keyframesRule.keyframes()) {
873
876
  this.idleCallbackManager.schedule(() => {
874
- block.sections.push(new KeyframePropertiesSection(this, matchedStyles, keyframe.style));
877
+ block.sections.push(new KeyframePropertiesSection(this, matchedStyles, keyframe.style, sectionIdx));
878
+ sectionIdx++;
875
879
  });
876
880
  }
877
881
  blocks.push(block);
@@ -917,7 +921,7 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
917
921
  const node = this.node();
918
922
  const blankSection = new BlankStylePropertiesSection(
919
923
  this, insertAfterSection.matchedStyles, node ? node.simpleSelector() : '', styleSheetId, ruleLocation,
920
- insertAfterSection.style());
924
+ insertAfterSection.style(), 0);
921
925
 
922
926
  this.sectionsContainer.insertBefore(blankSection.element, insertAfterSection.element.nextSibling);
923
927
 
@@ -929,6 +933,13 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
929
933
  block.sections.splice(index + 1, 0, blankSection);
930
934
  blankSection.startEditingSelector();
931
935
  }
936
+ let sectionIdx = 0;
937
+ for (const block of this.sectionBlocks) {
938
+ for (const section of block.sections) {
939
+ section.setSectionIdx(sectionIdx);
940
+ sectionIdx++;
941
+ }
942
+ }
932
943
  }
933
944
 
934
945
  removeSection(section: StylePropertiesSection): void {
@@ -1208,10 +1219,15 @@ export class StylePropertiesSection {
1208
1219
 
1209
1220
  private queryListElement: HTMLElement;
1210
1221
 
1222
+ // Used to identify buttons that trigger a flexbox or grid editor.
1223
+ nextEditorTriggerButtonIdx = 1;
1224
+ private sectionIdx = 0;
1225
+
1211
1226
  constructor(
1212
1227
  parentPane: StylesSidebarPane, matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles,
1213
- style: SDK.CSSStyleDeclaration.CSSStyleDeclaration) {
1228
+ style: SDK.CSSStyleDeclaration.CSSStyleDeclaration, sectionIdx: number) {
1214
1229
  this.parentPane = parentPane;
1230
+ this.sectionIdx = sectionIdx;
1215
1231
  this.styleInternal = style;
1216
1232
  this.matchedStyles = matchedStyles;
1217
1233
  this.editable = Boolean(style.styleSheetId && style.range);
@@ -1344,6 +1360,15 @@ export class StylePropertiesSection {
1344
1360
  this.onpopulate();
1345
1361
  }
1346
1362
 
1363
+ setSectionIdx(sectionIdx: number): void {
1364
+ this.sectionIdx = sectionIdx;
1365
+ this.onpopulate();
1366
+ }
1367
+
1368
+ getSectionIdx(): number {
1369
+ return this.sectionIdx;
1370
+ }
1371
+
1347
1372
  registerFontProperty(treeElement: StylePropertyTreeElement): void {
1348
1373
  if (this.fontEditorSectionManager) {
1349
1374
  this.fontEditorSectionManager.registerFontProperty(treeElement);
@@ -1935,6 +1960,7 @@ export class StylePropertiesSection {
1935
1960
 
1936
1961
  onpopulate(): void {
1937
1962
  this.parentPane.setActiveProperty(null);
1963
+ this.nextEditorTriggerButtonIdx = 1;
1938
1964
  this.propertiesTreeOutline.removeChildren();
1939
1965
  const style = this.styleInternal;
1940
1966
  let count = 0;
@@ -2457,10 +2483,10 @@ export class BlankStylePropertiesSection extends StylePropertiesSection {
2457
2483
  constructor(
2458
2484
  stylesPane: StylesSidebarPane, matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles, defaultSelectorText: string,
2459
2485
  styleSheetId: Protocol.CSS.StyleSheetId, ruleLocation: TextUtils.TextRange.TextRange,
2460
- insertAfterStyle: SDK.CSSStyleDeclaration.CSSStyleDeclaration) {
2486
+ insertAfterStyle: SDK.CSSStyleDeclaration.CSSStyleDeclaration, sectionIdx: number) {
2461
2487
  const cssModel = (stylesPane.cssModel() as SDK.CSSModel.CSSModel);
2462
2488
  const rule = SDK.CSSRule.CSSStyleRule.createDummyRule(cssModel, defaultSelectorText);
2463
- super(stylesPane, matchedStyles, rule.style);
2489
+ super(stylesPane, matchedStyles, rule.style, sectionIdx);
2464
2490
  this.normal = false;
2465
2491
  this.ruleLocation = ruleLocation;
2466
2492
  this.styleSheetId = styleSheetId;
@@ -2564,8 +2590,8 @@ export class BlankStylePropertiesSection extends StylePropertiesSection {
2564
2590
  export class KeyframePropertiesSection extends StylePropertiesSection {
2565
2591
  constructor(
2566
2592
  stylesPane: StylesSidebarPane, matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles,
2567
- style: SDK.CSSStyleDeclaration.CSSStyleDeclaration) {
2568
- super(stylesPane, matchedStyles, style);
2593
+ style: SDK.CSSStyleDeclaration.CSSStyleDeclaration, sectionIdx: number) {
2594
+ super(stylesPane, matchedStyles, style, sectionIdx);
2569
2595
  this.selectorElement.className = 'keyframe-key';
2570
2596
  }
2571
2597
 
@@ -19,18 +19,18 @@ export class Adorner extends HTMLElement {
19
19
  static readonly litTagName = LitHtml.literal`devtools-adorner`;
20
20
  name = '';
21
21
 
22
- private readonly shadow = this.attachShadow({mode: 'open'});
23
- private isToggle = false;
24
- private ariaLabelDefault?: string;
25
- private ariaLabelActive?: string;
26
- private content?: HTMLElement;
22
+ readonly #shadow = this.attachShadow({mode: 'open'});
23
+ #isToggle = false;
24
+ #ariaLabelDefault?: string;
25
+ #ariaLabelActive?: string;
26
+ #content?: HTMLElement;
27
27
 
28
28
  set data(data: AdornerData) {
29
29
  this.name = data.name;
30
30
  data.content.slot = 'content';
31
- this.content?.remove();
31
+ this.#content?.remove();
32
32
  this.append(data.content);
33
- this.content = data.content;
33
+ this.#content = data.content;
34
34
  this.render();
35
35
  }
36
36
 
@@ -38,7 +38,7 @@ export class Adorner extends HTMLElement {
38
38
  if (!this.getAttribute('aria-label')) {
39
39
  this.setAttribute('aria-label', this.name);
40
40
  }
41
- this.shadow.adoptedStyleSheets = [adornerStyles];
41
+ this.#shadow.adoptedStyleSheets = [adornerStyles];
42
42
  }
43
43
 
44
44
  isActive(): boolean {
@@ -50,12 +50,12 @@ export class Adorner extends HTMLElement {
50
50
  * an active state; pass `false` to force-set an inactive state.
51
51
  */
52
52
  toggle(forceActiveState?: boolean): void {
53
- if (!this.isToggle) {
53
+ if (!this.#isToggle) {
54
54
  return;
55
55
  }
56
56
  const shouldBecomeActive = forceActiveState === undefined ? !this.isActive() : forceActiveState;
57
57
  this.setAttribute('aria-pressed', Boolean(shouldBecomeActive).toString());
58
- this.setAttribute('aria-label', (shouldBecomeActive ? this.ariaLabelActive : this.ariaLabelDefault) || this.name);
58
+ this.setAttribute('aria-label', (shouldBecomeActive ? this.#ariaLabelActive : this.#ariaLabelDefault) || this.name);
59
59
  }
60
60
 
61
61
  show(): void {
@@ -78,9 +78,9 @@ export class Adorner extends HTMLElement {
78
78
  }): void {
79
79
  const {isToggle = false, shouldPropagateOnKeydown = false, ariaLabelDefault, ariaLabelActive} = options;
80
80
 
81
- this.isToggle = isToggle;
82
- this.ariaLabelDefault = ariaLabelDefault;
83
- this.ariaLabelActive = ariaLabelActive;
81
+ this.#isToggle = isToggle;
82
+ this.#ariaLabelDefault = ariaLabelDefault;
83
+ this.#ariaLabelActive = ariaLabelActive;
84
84
  this.setAttribute('aria-label', ariaLabelDefault);
85
85
 
86
86
  if (isToggle) {
@@ -111,7 +111,7 @@ export class Adorner extends HTMLElement {
111
111
  // clang-format off
112
112
  render(html`
113
113
  <slot name="content"></slot>
114
- `, this.shadow, {
114
+ `, this.#shadow, {
115
115
  host: this,
116
116
  });
117
117
  }
@@ -25,12 +25,16 @@ export const enum Size {
25
25
  MEDIUM = 'MEDIUM',
26
26
  }
27
27
 
28
+ type ButtonType = 'button'|'submit'|'reset';
29
+
28
30
  interface ButtonState {
29
31
  iconUrl?: string;
30
32
  variant?: Variant;
31
33
  size?: Size;
32
34
  disabled: boolean;
33
35
  active: boolean;
36
+ type: ButtonType;
37
+ value?: string;
34
38
  }
35
39
 
36
40
  export type ButtonData = {
@@ -39,30 +43,46 @@ export type ButtonData = {
39
43
  size?: Size,
40
44
  disabled?: boolean,
41
45
  active?: boolean,
46
+ type?: ButtonType,
47
+ value?: string,
42
48
  }|{
43
49
  variant: Variant.PRIMARY | Variant.SECONDARY,
44
50
  iconUrl?: string,
45
51
  size?: Size,
46
52
  disabled?: boolean,
47
53
  active?: boolean,
54
+ type?: ButtonType,
55
+ value?: string,
48
56
  };
49
57
 
58
+ interface ButtonElementInternals extends ElementInternals {
59
+ readonly form?: HTMLFormElement;
60
+ readonly validity: ValidityState;
61
+ readonly willValidate: boolean;
62
+ readonly validationMessage: string;
63
+ checkValidity(): void;
64
+ reportValidity(): void;
65
+ }
66
+
50
67
  export class Button extends HTMLElement {
68
+ static formAssociated = true;
51
69
  static readonly litTagName = LitHtml.literal`devtools-button`;
52
- private readonly shadow = this.attachShadow({mode: 'open', delegatesFocus: true});
53
- private readonly boundRender = this.render.bind(this);
54
- private readonly boundOnClick = this.onClick.bind(this);
55
- private readonly props: ButtonState = {
70
+ readonly #shadow = this.attachShadow({mode: 'open', delegatesFocus: true});
71
+ readonly #boundRender = this.render.bind(this);
72
+ readonly #boundOnClick = this.onClick.bind(this);
73
+ readonly #props: ButtonState = {
56
74
  size: Size.MEDIUM,
57
75
  disabled: false,
58
76
  active: false,
77
+ type: 'button',
59
78
  };
60
- private isEmpty = true;
79
+ #isEmpty = true;
80
+ #internals = this.attachInternals() as ButtonElementInternals;
61
81
 
62
82
  constructor() {
63
83
  super();
64
84
  this.setAttribute('role', 'presentation');
65
- this.addEventListener('click', this.boundOnClick, true);
85
+ this.addEventListener('click', this.#boundOnClick, true);
66
86
  }
67
87
 
68
88
  /**
@@ -70,104 +90,158 @@ export class Button extends HTMLElement {
70
90
  * for increased type-safety.
71
91
  */
72
92
  set data(data: ButtonData) {
73
- this.props.variant = data.variant;
74
- this.props.iconUrl = data.iconUrl;
75
- this.props.size = data.size || Size.MEDIUM;
76
- this.props.active = Boolean(data.active);
93
+ this.#props.variant = data.variant;
94
+ this.#props.iconUrl = data.iconUrl;
95
+ this.#props.size = data.size || Size.MEDIUM;
96
+ this.#props.active = Boolean(data.active);
97
+ this.#props.type = data.type || 'button';
77
98
  this.setDisabledProperty(data.disabled || false);
78
- ComponentHelpers.ScheduledRender.scheduleRender(this, this.boundRender);
99
+ ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
79
100
  }
80
101
 
81
102
  set iconUrl(iconUrl: string|undefined) {
82
- this.props.iconUrl = iconUrl;
83
- ComponentHelpers.ScheduledRender.scheduleRender(this, this.boundRender);
103
+ this.#props.iconUrl = iconUrl;
104
+ ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
84
105
  }
85
106
 
86
107
  set variant(variant: Variant) {
87
- this.props.variant = variant;
88
- ComponentHelpers.ScheduledRender.scheduleRender(this, this.boundRender);
108
+ this.#props.variant = variant;
109
+ ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
89
110
  }
90
111
 
91
112
  set size(size: Size) {
92
- this.props.size = size;
93
- ComponentHelpers.ScheduledRender.scheduleRender(this, this.boundRender);
113
+ this.#props.size = size;
114
+ ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
115
+ }
116
+
117
+ set type(type: ButtonType) {
118
+ this.#props.type = type;
119
+ ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
94
120
  }
95
121
 
96
122
  set disabled(disabled: boolean) {
97
123
  this.setDisabledProperty(disabled);
98
- ComponentHelpers.ScheduledRender.scheduleRender(this, this.boundRender);
124
+ ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
99
125
  }
100
126
 
101
127
  set active(active: boolean) {
102
- this.props.active = active;
103
- ComponentHelpers.ScheduledRender.scheduleRender(this, this.boundRender);
128
+ this.#props.active = active;
129
+ ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
104
130
  }
105
131
 
106
132
  private setDisabledProperty(disabled: boolean): void {
107
- this.props.disabled = disabled;
133
+ this.#props.disabled = disabled;
108
134
  this.toggleAttribute('disabled', disabled);
109
135
  }
110
136
 
111
137
  focus(): void {
112
- this.shadow.querySelector('button')?.focus();
138
+ this.#shadow.querySelector('button')?.focus();
113
139
  }
114
140
 
115
141
  connectedCallback(): void {
116
- this.shadow.adoptedStyleSheets = [buttonStyles];
117
- ComponentHelpers.ScheduledRender.scheduleRender(this, this.boundRender);
142
+ this.#shadow.adoptedStyleSheets = [buttonStyles];
143
+ ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
118
144
  }
119
145
 
120
146
  private onClick(event: Event): void {
121
- if (this.props.disabled) {
147
+ if (this.#props.disabled) {
122
148
  event.stopPropagation();
123
149
  event.preventDefault();
150
+ return;
151
+ }
152
+ if (this.form && this.#props.type === 'submit') {
153
+ event.preventDefault();
154
+ this.form.dispatchEvent(new SubmitEvent('submit', {
155
+ submitter: this,
156
+ }));
157
+ }
158
+ if (this.form && this.#props.type === 'reset') {
159
+ event.preventDefault();
160
+ this.form.reset();
124
161
  }
125
162
  }
126
163
 
127
164
  private onSlotChange(event: Event): void {
128
165
  const slot = event.target as HTMLSlotElement | undefined;
129
166
  const nodes = slot?.assignedNodes();
130
- this.isEmpty = !nodes || !Boolean(nodes.length);
131
- ComponentHelpers.ScheduledRender.scheduleRender(this, this.boundRender);
167
+ this.#isEmpty = !nodes || !Boolean(nodes.length);
168
+ ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
132
169
  }
133
170
 
134
171
  private render(): void {
135
- if (!this.props.variant) {
172
+ if (!this.#props.variant) {
136
173
  throw new Error('Button requires a variant to be defined');
137
174
  }
138
- if (this.props.variant === Variant.TOOLBAR) {
139
- if (!this.props.iconUrl) {
175
+ if (this.#props.variant === Variant.TOOLBAR) {
176
+ if (!this.#props.iconUrl) {
140
177
  throw new Error('Toolbar button requires an icon');
141
178
  }
142
- if (!this.isEmpty) {
179
+ if (!this.#isEmpty) {
143
180
  throw new Error('Tooblar button does not accept children');
144
181
  }
145
182
  }
146
183
  const classes = {
147
- primary: this.props.variant === Variant.PRIMARY,
148
- secondary: this.props.variant === Variant.SECONDARY,
149
- toolbar: this.props.variant === Variant.TOOLBAR,
150
- 'text-with-icon': Boolean(this.props.iconUrl) && !this.isEmpty,
151
- 'only-icon': Boolean(this.props.iconUrl) && this.isEmpty,
152
- small: Boolean(this.props.size === Size.SMALL),
153
- active: this.props.active,
184
+ primary: this.#props.variant === Variant.PRIMARY,
185
+ secondary: this.#props.variant === Variant.SECONDARY,
186
+ toolbar: this.#props.variant === Variant.TOOLBAR,
187
+ 'text-with-icon': Boolean(this.#props.iconUrl) && !this.#isEmpty,
188
+ 'only-icon': Boolean(this.#props.iconUrl) && this.#isEmpty,
189
+ small: Boolean(this.#props.size === Size.SMALL),
190
+ active: this.#props.active,
154
191
  };
155
192
  // clang-format off
156
193
  LitHtml.render(
157
194
  LitHtml.html`
158
- <button .disabled=${this.props.disabled} class=${LitHtml.Directives.classMap(classes)}>
159
- ${this.props.iconUrl ? LitHtml.html`<${IconButton.Icon.Icon.litTagName}
195
+ <button .disabled=${this.#props.disabled} class=${LitHtml.Directives.classMap(classes)}>
196
+ ${this.#props.iconUrl ? LitHtml.html`<${IconButton.Icon.Icon.litTagName}
160
197
  .data=${{
161
- iconPath: this.props.iconUrl,
198
+ iconPath: this.#props.iconUrl,
162
199
  color: 'var(--color-background)',
163
200
  } as IconButton.Icon.IconData}
164
201
  >
165
202
  </${IconButton.Icon.Icon.litTagName}>` : ''}
166
203
  <slot @slotchange=${this.onSlotChange}></slot>
167
204
  </button>
168
- `, this.shadow, {host: this});
205
+ `, this.#shadow, {host: this});
169
206
  // clang-format on
170
207
  }
208
+
209
+ // Based on https://web.dev/more-capable-form-controls/ to make custom elements form-friendly.
210
+ // Form controls usually expose a "value" property.
211
+ get value(): string {
212
+ return this.#props.value || '';
213
+ }
214
+ set value(value: string) {
215
+ this.#props.value = value;
216
+ }
217
+
218
+ // The following properties and methods aren't strictly required,
219
+ // but browser-level form controls provide them. Providing them helps
220
+ // ensure consistency with browser-provided controls.
221
+ get form(): HTMLFormElement|undefined {
222
+ return this.#internals.form;
223
+ }
224
+ get name(): string|null {
225
+ return this.getAttribute('name');
226
+ }
227
+ get type(): ButtonType {
228
+ return this.#props.type;
229
+ }
230
+ get validity(): ValidityState {
231
+ return this.#internals.validity;
232
+ }
233
+ get validationMessage(): string {
234
+ return this.#internals.validationMessage;
235
+ }
236
+ get willValidate(): boolean {
237
+ return this.#internals.willValidate;
238
+ }
239
+ checkValidity(): void {
240
+ return this.#internals.checkValidity();
241
+ }
242
+ reportValidity(): void {
243
+ return this.#internals.reportValidity();
244
+ }
171
245
  }
172
246
 
173
247
  ComponentHelpers.CustomElements.defineComponent('devtools-button', Button);