@bitforgehq/ui 0.3.3 → 0.5.0-next.3

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 (99) hide show
  1. package/CHANGELOG.md +394 -0
  2. package/README.md +14 -14
  3. package/ai/MANIFEST.index.json +101 -2
  4. package/ai/MANIFEST.json +2630 -746
  5. package/ai/REFERENCE.index.json +196 -0
  6. package/ai/REFERENCE.json +1625 -140
  7. package/ai/UI_GUIDE.md +83 -21
  8. package/ai/components/app-frame.md +1 -1
  9. package/ai/components/badge.md +39 -2
  10. package/ai/components/chat-composer.md +357 -0
  11. package/ai/components/chat-message.md +251 -0
  12. package/ai/components/chat-thread.md +521 -0
  13. package/ai/components/checkbox.md +7 -0
  14. package/ai/components/combobox.md +12 -0
  15. package/ai/components/date-input.md +9 -0
  16. package/ai/components/datetime-input.md +2 -0
  17. package/ai/components/field.md +10 -0
  18. package/ai/components/file-input.md +498 -0
  19. package/ai/components/header.md +1 -1
  20. package/ai/components/image-input.md +368 -0
  21. package/ai/components/image-viewer.md +309 -0
  22. package/ai/components/list-item.md +105 -9
  23. package/ai/components/media-card.md +478 -0
  24. package/ai/components/menu-button.md +2 -0
  25. package/ai/components/menu.md +10 -0
  26. package/ai/components/multi-combobox.md +7 -0
  27. package/ai/components/nav-group.md +10 -3
  28. package/ai/components/nav-item.md +11 -2
  29. package/ai/components/number-input.md +2 -7
  30. package/ai/components/popover.md +13 -0
  31. package/ai/components/radio-group.md +6 -0
  32. package/ai/components/segmented.md +14 -2
  33. package/ai/components/select.md +100 -42
  34. package/ai/components/sidebar-toggle.md +16 -9
  35. package/ai/components/sidebar.md +41 -13
  36. package/ai/components/step.md +112 -0
  37. package/ai/components/steps.md +124 -0
  38. package/ai/components/tab-group.md +18 -2
  39. package/ai/components/table.md +115 -4
  40. package/ai/components/theme-menu-button.md +1 -0
  41. package/ai/components/time-input.md +2 -0
  42. package/ai/components/timeline-event.md +144 -0
  43. package/ai/components/timeline.md +201 -0
  44. package/ai/components/toggle.md +11 -1
  45. package/ai/components/toolbar.md +7 -18
  46. package/ai/reference/bf-action-directive.md +3 -1
  47. package/ai/reference/bf-badge-emphasis.md +27 -0
  48. package/ai/reference/bf-chat-composer-harness.md +64 -0
  49. package/ai/reference/bf-chat-message-role.md +27 -0
  50. package/ai/reference/bf-chat-message-status.md +28 -0
  51. package/ai/reference/bf-fade-in-directive.md +18 -0
  52. package/ai/reference/bf-file-input-harness.md +67 -0
  53. package/ai/reference/bf-file-input-variant.md +27 -0
  54. package/ai/reference/bf-image-input-harness.md +67 -0
  55. package/ai/reference/bf-image-input-shape.md +27 -0
  56. package/ai/reference/bf-image-viewer-actions-context.md +29 -0
  57. package/ai/reference/bf-image-viewer-harness.md +87 -0
  58. package/ai/reference/bf-image-viewer-image.md +25 -0
  59. package/ai/reference/bf-image-viewer-options.md +33 -0
  60. package/ai/reference/bf-image-viewer-origin.md +23 -0
  61. package/ai/reference/bf-image-viewer-ref.md +55 -0
  62. package/ai/reference/bf-image-viewer-service.md +32 -0
  63. package/ai/reference/bf-labels.md +105 -74
  64. package/ai/reference/bf-media-card-aspect.md +28 -0
  65. package/ai/reference/bf-media-card-harness.md +66 -0
  66. package/ai/reference/bf-motion-list-directive.md +22 -0
  67. package/ai/reference/bf-prose-directive.md +16 -0
  68. package/ai/reference/bf-rejected-file-reason.md +28 -0
  69. package/ai/reference/bf-rejected-file.md +28 -0
  70. package/ai/reference/bf-select-harness.md +14 -5
  71. package/ai/reference/bf-select-option.md +2 -0
  72. package/ai/reference/bf-size.md +3 -0
  73. package/ai/reference/bf-step-heading-level.md +28 -0
  74. package/ai/reference/bf-table-align.md +1 -1
  75. package/ai/reference/bf-table-column-align.md +33 -0
  76. package/ai/reference/bf-table-column.md +7 -7
  77. package/ai/reference/bf-table-harness.md +12 -0
  78. package/ai/reference/bf-timeline-tone.md +31 -0
  79. package/ai/reference/bf-toggle-harness.md +55 -0
  80. package/ai/reference/bf_table_sort_select.md +23 -0
  81. package/ai/reference/provide-bf-labels.md +10 -3
  82. package/ai/reference/provide-bf-select.md +20 -0
  83. package/bin/bf-ui.js +1 -0
  84. package/dark-ink.css +124 -0
  85. package/fesm2022/bitforgehq-ui-mobile.mjs +2 -1
  86. package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -1
  87. package/fesm2022/bitforgehq-ui-overlay.mjs +2649 -606
  88. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
  89. package/fesm2022/bitforgehq-ui-testing.mjs +406 -11
  90. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
  91. package/fesm2022/bitforgehq-ui.mjs +11149 -6957
  92. package/fesm2022/bitforgehq-ui.mjs.map +1 -1
  93. package/llms-full.txt +5219 -141
  94. package/llms.txt +40 -1
  95. package/package.json +2 -1
  96. package/theme.css +84 -0
  97. package/types/bitforgehq-ui-overlay.d.ts +684 -7
  98. package/types/bitforgehq-ui-testing.d.ts +510 -7
  99. package/types/bitforgehq-ui.d.ts +1757 -186
@@ -110,29 +110,44 @@ function provideBfTesting(options = {}) {
110
110
  return makeEnvironmentProviders([...providers, ɵprovideBfDocumentReset()]);
111
111
  }
112
112
  var BfSelectHarness = class BfSelectHarness extends ComponentHarness {
113
- static hostSelector = "bf-select";
114
- select = this.locatorFor("select");
113
+ static hostSelector = "bf-select, [data-bf-select]";
114
+ nativeSelect = this.locatorForOptional("select");
115
+ combobox = this.locatorForOptional("[role=\"combobox\"]");
115
116
  label = this.locatorForOptional("label");
116
117
  error = this.locatorForOptional("p[id$=\"-error\"]");
117
118
  help = this.locatorForOptional("p[id$=\"-help\"]");
118
119
  nativeOptions = this.locatorForAll("option");
120
+ styledOptions = this.locatorForAll("[data-bf-select-options] > span");
119
121
  static with(filters = {}) {
120
122
  return new HarnessPredicate(BfSelectHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
121
123
  }
122
124
  async getLabel() {
123
125
  const label = await this.label();
124
126
  if (label) return (await label.text({ exclude: "[aria-hidden=\"true\"]" })).trim();
125
- return await (await this.select()).getAttribute("aria-label") ?? "";
127
+ return await (await this.control()).getAttribute("aria-label") ?? "";
128
+ }
129
+ async isOpen() {
130
+ if (await this.nativeSelect()) return false;
131
+ return await (await this.control()).getAttribute("aria-expanded") === "true";
126
132
  }
127
133
  async getValueLabel() {
134
+ if (!await this.nativeSelect()) {
135
+ const field = await this.control();
136
+ return await field.getAttribute("data-placeholder") !== null ? "" : (await field.text()).trim();
137
+ }
128
138
  for (const option of await this.options()) if (option.choice && await option.element.getProperty("selected")) return option.label;
129
139
  return "";
130
140
  }
131
141
  async getOptionLabels() {
142
+ if (!await this.nativeSelect()) return (await this.styledChoices()).map((option) => option.label);
132
143
  return (await this.options()).filter((option) => option.choice).map((option) => option.label);
133
144
  }
134
145
  async selectOption(label) {
135
146
  if (await this.isDisabled()) throw new Error(`BfSelectHarness: the select is disabled, so "${label}" cannot be chosen`);
147
+ if (!await this.nativeSelect()) {
148
+ await this.selectStyled(label);
149
+ return;
150
+ }
136
151
  const options = await this.options();
137
152
  const option = options.find((o) => o.choice && o.label === label);
138
153
  if (!option) {
@@ -140,18 +155,20 @@ var BfSelectHarness = class BfSelectHarness extends ComponentHarness {
140
155
  throw new Error(`BfSelectHarness: no option "${label}" (options: ${known.join(", ")})`);
141
156
  }
142
157
  if (await option.element.getProperty("disabled")) throw new Error(`BfSelectHarness: the option "${label}" is disabled`);
143
- const select = await this.select();
158
+ const select = await this.control();
144
159
  await select.setInputValue(await option.element.getProperty("value"));
145
160
  await select.dispatchEvent("change");
146
161
  }
147
162
  async isDisabled() {
148
- return (await this.select()).getProperty("disabled");
163
+ if (!await this.nativeSelect()) return await (await this.control()).getAttribute("aria-disabled") === "true";
164
+ return (await this.control()).getProperty("disabled");
149
165
  }
150
166
  async isReadonly() {
151
- return await (await this.select()).getAttribute("aria-readonly") === "true";
167
+ return await (await this.control()).getAttribute("aria-readonly") === "true";
152
168
  }
153
169
  async isRequired() {
154
- return (await this.select()).getProperty("required");
170
+ if (!await this.nativeSelect()) return await (await this.control()).getAttribute("aria-required") === "true";
171
+ return (await this.control()).getProperty("required");
155
172
  }
156
173
  async getErrorText() {
157
174
  return (await (await this.error())?.text())?.trim() ?? "";
@@ -160,10 +177,61 @@ var BfSelectHarness = class BfSelectHarness extends ComponentHarness {
160
177
  return (await (await this.help())?.text())?.trim() ?? "";
161
178
  }
162
179
  async focus() {
163
- await (await this.select()).focus();
180
+ await (await this.control()).focus();
164
181
  }
165
182
  async blur() {
166
- await (await this.select()).blur();
183
+ await (await this.control()).blur();
184
+ }
185
+ async control() {
186
+ const native = await this.nativeSelect();
187
+ if (native) return native;
188
+ const field = await this.combobox();
189
+ if (!field) throw new Error("BfSelectHarness: found neither a native select nor a combobox field");
190
+ return field;
191
+ }
192
+ async open() {
193
+ if (!await this.isOpen()) await (await this.control()).click();
194
+ }
195
+ async close() {
196
+ if (await this.isOpen()) await (await this.control()).sendKeys(TestKey.ESCAPE);
197
+ }
198
+ async listOptions() {
199
+ const id = await (await this.control()).getAttribute("aria-controls");
200
+ if (!id) return [];
201
+ const root = this.documentRootLocatorFactory();
202
+ const [elements, labels] = await Promise.all([root.locatorForAll(`[id="${id}"] [role="option"]`)(), root.locatorForAll(`[id="${id}"] [role="option"] [data-label]`)()]);
203
+ return Promise.all(elements.map(async (element, i) => ({
204
+ element,
205
+ label: (await (labels[i] ?? element).text()).trim()
206
+ })));
207
+ }
208
+ async styledChoices() {
209
+ return Promise.all((await this.styledOptions()).map(async (element) => {
210
+ const [label, disabled] = await Promise.all([element.text(), element.getAttribute("data-disabled")]);
211
+ return {
212
+ label: label.trim(),
213
+ disabled: disabled !== null
214
+ };
215
+ }));
216
+ }
217
+ async selectStyled(label) {
218
+ const choices = await this.styledChoices();
219
+ const choice = choices.find((o) => o.label === label);
220
+ if (!choice) {
221
+ const known = choices.map((o) => o.label);
222
+ throw new Error(`BfSelectHarness: no option "${label}" (options: ${known.join(", ")})`);
223
+ }
224
+ if (choice.disabled) throw new Error(`BfSelectHarness: the option "${label}" is disabled`);
225
+ if (await this.isReadonly()) return;
226
+ await this.open();
227
+ const option = (await this.listOptions()).find((o) => o.label === label);
228
+ if (!option) {
229
+ await this.close();
230
+ throw new Error(`BfSelectHarness: the open list has no option "${label}"`);
231
+ }
232
+ await option.element.click();
233
+ await new Promise((resolve) => setTimeout(resolve));
234
+ await this.forceStabilize();
167
235
  }
168
236
  async options() {
169
237
  return Promise.all((await this.nativeOptions()).map(async (element, index) => {
@@ -403,7 +471,7 @@ var BfTableHarness = class BfTableHarness extends ComponentHarness {
403
471
  headers = this.locatorForAll("thead th[data-bf-column]");
404
472
  rows = this.locatorForAll("tbody tr[data-bf-table-row]");
405
473
  selectAllBox = this.locatorForOptional("thead input[type=\"checkbox\"]");
406
- rowBoxes = this.locatorForAll("tbody tr[data-bf-table-row] input[type=\"checkbox\"]");
474
+ rowBoxes = this.locatorForAll("tbody tr[data-bf-table-row] > td:not([data-bf-cell]) input[type=\"checkbox\"]");
407
475
  sortSelect = this.locatorForOptional(BfSelectHarness.with({ ancestor: "[data-bf-table-stack-bar]" }));
408
476
  static with(filters = {}) {
409
477
  return new HarnessPredicate(BfTableHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
@@ -422,6 +490,13 @@ var BfTableHarness = class BfTableHarness extends ComponentHarness {
422
490
  if (row < 0 || row >= await this.getRowCount()) throw new Error(`BfTableHarness: no row ${row}`);
423
491
  return (await (await this.locatorForAll(`tbody tr[data-bf-table-row]:nth-of-type(${row + 1}) [data-bf-cell]`)())[index]?.text())?.trim() ?? "";
424
492
  }
493
+ async getCellHarness(row, column, query) {
494
+ const index = await this.columnIndex(column);
495
+ if (row < 0 || row >= await this.getRowCount()) throw new Error(`BfTableHarness: no row ${row}`);
496
+ const offset = await this.locatorForOptional("tbody tr[data-bf-table-row] > td:not([data-bf-cell])")() ? 1 : 0;
497
+ const cell = `tbody tr[data-bf-table-row]:nth-of-type(${row + 1}) > td:nth-child(${index + offset + 1})`;
498
+ return (await this.locatorFactory.harnessLoaderFor(cell)).getHarness(query);
499
+ }
425
500
  async sortBy(column) {
426
501
  const key = await (await this.headers())[await this.columnIndex(column)]?.getAttribute("data-bf-column");
427
502
  const button = key ? await this.locatorForOptional(`thead th[data-bf-column="${key}"] button`)() : null;
@@ -1034,6 +1109,326 @@ var BfSidebarToggleHarness = class BfSidebarToggleHarness extends ComponentHarne
1034
1109
  await (await this.button()).click();
1035
1110
  }
1036
1111
  };
1037
- export { BfCalendarHarness, BfChartHarness, BfComboboxHarness, BfDateInputHarness, BfDateTimeInputHarness, BfDesktopToastHarness, BfDialogHarness, BfMenuButtonHarness, BfMenuHarness, BfMenuItemHarness, BfPaginationHarness, BfPopoverHarness, BfRadioGroupHarness, BfSegmentedHarness, BfSelectHarness, BfSidePanelHarness, BfSidebarToggleHarness, BfTabGroupHarness, BfTableHarness, BfTimeInputHarness, BfTooltipHarness, installBfJsdomShims, provideBfTesting };
1112
+ var BfToggleHarness = class BfToggleHarness extends ComponentHarness {
1113
+ static hostSelector = "bf-toggle";
1114
+ input = this.locatorFor("input[role=\"switch\"]");
1115
+ label = this.locatorFor("label");
1116
+ error = this.locatorForOptional("p[id$=\"-error\"]");
1117
+ static with(filters = {}) {
1118
+ return new HarnessPredicate(BfToggleHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
1119
+ }
1120
+ async getLabel() {
1121
+ const input = await this.input();
1122
+ const ids = (await input.getAttribute("aria-labelledby"))?.split(/\s+/).filter(Boolean) ?? [];
1123
+ if (ids.length > 0) {
1124
+ const root = this.documentRootLocatorFactory();
1125
+ return (await Promise.all(ids.map(async (id) => await (await root.locatorForOptional(`[id="${id}"]`)())?.text() ?? ""))).map((text) => text.trim()).filter(Boolean).join(" ");
1126
+ }
1127
+ const ariaLabel = await input.getAttribute("aria-label");
1128
+ if (ariaLabel) return ariaLabel;
1129
+ return (await (await this.label()).text()).trim();
1130
+ }
1131
+ async isChecked() {
1132
+ return (await this.input()).getProperty("checked");
1133
+ }
1134
+ async isDisabled() {
1135
+ return (await this.input()).getProperty("disabled");
1136
+ }
1137
+ async isReadonly() {
1138
+ return await (await this.input()).getAttribute("aria-readonly") === "true";
1139
+ }
1140
+ async toggle() {
1141
+ if (await this.isDisabled()) throw new Error(`BfToggleHarness: the toggle "${await this.getLabel()}" is disabled`);
1142
+ await (await this.input()).click();
1143
+ }
1144
+ async check() {
1145
+ if (!await this.isChecked()) await this.toggle();
1146
+ }
1147
+ async uncheck() {
1148
+ if (await this.isChecked()) await this.toggle();
1149
+ }
1150
+ async getErrorText() {
1151
+ return (await (await this.error())?.text())?.trim() ?? "";
1152
+ }
1153
+ };
1154
+ var BfFileInputHarness = class BfFileInputHarness extends ComponentHarness {
1155
+ static hostSelector = "bf-file-input";
1156
+ label = this.locatorForOptional("label");
1157
+ error = this.locatorForOptional("p[id$=\"-error\"]");
1158
+ box = this.locatorForOptional("[data-box]");
1159
+ pick = this.locatorForOptional("[data-pick]");
1160
+ ariaLabelText = this.locatorForOptional("[data-aria-label]");
1161
+ dropzone = this.locatorForOptional("[data-dropzone]");
1162
+ nativeInput = this.locatorFor("input[type=\"file\"]");
1163
+ names = this.locatorForAll("[data-file] [data-name]");
1164
+ removeButtons = this.locatorForAll("[data-file] button");
1165
+ rejectedNames = this.locatorForAll("[data-rejected] [data-rejected-name]");
1166
+ rejectedMessages = this.locatorForAll("[data-rejected] [data-rejected-message]");
1167
+ static with(filters = {}) {
1168
+ return new HarnessPredicate(BfFileInputHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
1169
+ }
1170
+ async getLabel() {
1171
+ const label = await this.label();
1172
+ if (label) return (await label.text({ exclude: "[aria-hidden=\"true\"]" })).trim();
1173
+ return (await (await this.ariaLabelText())?.text())?.trim() ?? "";
1174
+ }
1175
+ async getErrorText() {
1176
+ return (await (await this.error())?.text())?.trim() ?? "";
1177
+ }
1178
+ async getVariant() {
1179
+ return await this.dropzone() !== null || await this.box() === null ? "dropzone" : "field";
1180
+ }
1181
+ async isDisabled() {
1182
+ return (await this.nativeInput()).getProperty("disabled");
1183
+ }
1184
+ async isReadonly() {
1185
+ return await this.pick() === null;
1186
+ }
1187
+ async addFiles(files) {
1188
+ const target = await this.box();
1189
+ if (!target || await this.isDisabled() || await this.isReadonly()) return;
1190
+ const data = { dataTransfer: { files } };
1191
+ await target.dispatchEvent("drop", data);
1192
+ }
1193
+ async getFileNames() {
1194
+ return Promise.all((await this.names()).map(async (name) => (await name.text()).trim()));
1195
+ }
1196
+ async getRejected() {
1197
+ const [names, messages] = await Promise.all([this.rejectedNames(), this.rejectedMessages()]);
1198
+ return Promise.all(names.map(async (name, index) => ({
1199
+ name: (await name.text()).trim(),
1200
+ message: (await messages[index]?.text() ?? "").trim()
1201
+ })));
1202
+ }
1203
+ async removeFile(name) {
1204
+ const index = (await this.getFileNames()).indexOf(name);
1205
+ if (index < 0) throw new Error(`BfFileInputHarness: no chosen file is named "${name}"`);
1206
+ const button = (await this.removeButtons())[index];
1207
+ if (!button) throw new Error(`BfFileInputHarness: "${name}" cannot be removed from this field`);
1208
+ await button.click();
1209
+ }
1210
+ };
1211
+ var BfImageInputHarness = class BfImageInputHarness extends ComponentHarness {
1212
+ static hostSelector = "bf-image-input";
1213
+ label = this.locatorForOptional("label");
1214
+ error = this.locatorForOptional("p[id$=\"-error\"]");
1215
+ tile = this.locatorFor("[data-tile]");
1216
+ preview = this.locatorForOptional("[data-tile] img");
1217
+ removeButton = this.locatorForOptional("[data-remove]");
1218
+ rejection = this.locatorForOptional("[data-rejection]");
1219
+ nativeInput = this.locatorFor("input[type=\"file\"]");
1220
+ static with(filters = {}) {
1221
+ return new HarnessPredicate(BfImageInputHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
1222
+ }
1223
+ async getLabel() {
1224
+ const label = await this.label();
1225
+ if (label) return (await label.text({ exclude: "[aria-hidden=\"true\"]" })).trim();
1226
+ return await (await this.tile()).getAttribute("aria-label") ?? "";
1227
+ }
1228
+ async getErrorText() {
1229
+ return (await (await this.error())?.text())?.trim() ?? "";
1230
+ }
1231
+ async isDisabled() {
1232
+ return (await this.nativeInput()).getProperty("disabled");
1233
+ }
1234
+ async isReadonly() {
1235
+ return await (await this.tile()).getProperty("tagName") !== "BUTTON";
1236
+ }
1237
+ async hasPreview() {
1238
+ return await this.preview() !== null;
1239
+ }
1240
+ async getPreviewSrc() {
1241
+ return (await this.preview())?.getAttribute("src") ?? null;
1242
+ }
1243
+ async addFile(file) {
1244
+ if (await this.isDisabled() || await this.isReadonly()) return;
1245
+ const data = { dataTransfer: { files: [file] } };
1246
+ await (await this.tile()).dispatchEvent("drop", data);
1247
+ }
1248
+ async remove() {
1249
+ const button = await this.removeButton();
1250
+ if (!button) throw new Error("BfImageInputHarness: there is no Remove button, so nothing can be removed");
1251
+ await button.click();
1252
+ }
1253
+ async getRejectedMessage() {
1254
+ return (await (await this.rejection())?.text())?.trim() ?? "";
1255
+ }
1256
+ };
1257
+ var BfMediaCardHarness = class BfMediaCardHarness extends ComponentHarness {
1258
+ static hostSelector = "bf-media-card";
1259
+ media = this.locatorFor("[data-media]");
1260
+ image = this.locatorForOptional("[data-media] img");
1261
+ heading = this.locatorForOptional("[data-body] h1, [data-body] h2, [data-body] h3, [data-body] h4, [data-body] h5, [data-body] h6");
1262
+ loadingLabel = this.locatorForOptional("[data-loading-label]");
1263
+ failedLabel = this.locatorForOptional("[data-failed-label]");
1264
+ selectButton = this.locatorForOptional("button[data-select]");
1265
+ static with(filters = {}) {
1266
+ return new HarnessPredicate(BfMediaCardHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getTitle(), label));
1267
+ }
1268
+ async getTitle() {
1269
+ return (await (await this.heading())?.text())?.trim() ?? "";
1270
+ }
1271
+ async getState() {
1272
+ const state = await (await this.media()).getAttribute("data-state");
1273
+ return state === "loading" || state === "loaded" || state === "failed" ? state : "empty";
1274
+ }
1275
+ async getSrc() {
1276
+ return await (await this.image())?.getAttribute("src") ?? "";
1277
+ }
1278
+ async getAlt() {
1279
+ return await (await this.image())?.getAttribute("alt") ?? "";
1280
+ }
1281
+ async getLoadingLabel() {
1282
+ return (await (await this.loadingLabel())?.text())?.trim() ?? "";
1283
+ }
1284
+ async getFailedMessage() {
1285
+ return (await (await this.failedLabel())?.text())?.trim() ?? "";
1286
+ }
1287
+ async loadImage() {
1288
+ await (await this.requireImage()).dispatchEvent("load");
1289
+ }
1290
+ async failImage() {
1291
+ await (await this.requireImage()).dispatchEvent("error");
1292
+ }
1293
+ async isSelectable() {
1294
+ return await this.selectButton() !== null;
1295
+ }
1296
+ async isSelected() {
1297
+ return await (await this.selectButton())?.getAttribute("aria-pressed") === "true";
1298
+ }
1299
+ async toggleSelected() {
1300
+ const button = await this.selectButton();
1301
+ if (!button) throw new Error("BfMediaCardHarness: the card is not selectable");
1302
+ await button.click();
1303
+ }
1304
+ async requireImage() {
1305
+ const image = await this.image();
1306
+ if (!image) throw new Error("BfMediaCardHarness: the card shows no image (no src, or it failed)");
1307
+ return image;
1308
+ }
1309
+ };
1310
+ var BfImageViewerHarness = class BfImageViewerHarness extends ComponentHarness {
1311
+ static hostSelector = "bf-image-viewer";
1312
+ image = this.locatorForOptional("img[data-image]");
1313
+ frame = this.locatorFor("[data-frame]");
1314
+ caption = this.locatorForOptional("[data-caption]");
1315
+ counter = this.locatorFor("[data-counter]");
1316
+ closeButton = this.locatorFor("[data-close] button");
1317
+ nextButton = this.locatorForOptional("[data-next] button");
1318
+ previousButton = this.locatorForOptional("[data-previous] button");
1319
+ static with(filters = {}) {
1320
+ return new HarnessPredicate(BfImageViewerHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
1321
+ }
1322
+ async getLabel() {
1323
+ return await (await this.host()).getAttribute("data-label") ?? "";
1324
+ }
1325
+ async getIndex() {
1326
+ return Number(await (await this.host()).getAttribute("data-index"));
1327
+ }
1328
+ async getCount() {
1329
+ return Number(await (await this.host()).getAttribute("data-count"));
1330
+ }
1331
+ async getCounterText() {
1332
+ return await this.getCount() < 2 ? "" : (await (await this.counter()).text()).trim();
1333
+ }
1334
+ async getSrc() {
1335
+ return await (await this.image())?.getAttribute("src") ?? "";
1336
+ }
1337
+ async getAlt() {
1338
+ return await (await this.image())?.getAttribute("alt") ?? "";
1339
+ }
1340
+ async getCaption() {
1341
+ return (await (await this.caption())?.text())?.trim() ?? "";
1342
+ }
1343
+ async isLoaded() {
1344
+ return await (await this.frame()).getAttribute("data-status") === "loaded";
1345
+ }
1346
+ async hasFailed() {
1347
+ return await (await this.frame()).getAttribute("data-status") === "error";
1348
+ }
1349
+ async loadImage() {
1350
+ await (await this.requireImage()).dispatchEvent("load");
1351
+ }
1352
+ async failImage() {
1353
+ await (await this.requireImage()).dispatchEvent("error");
1354
+ }
1355
+ async next() {
1356
+ const button = await this.nextButton();
1357
+ if (!button) throw new Error("BfImageViewerHarness: a viewer of one image has no next button");
1358
+ await button.click();
1359
+ }
1360
+ async previous() {
1361
+ const button = await this.previousButton();
1362
+ if (!button) throw new Error("BfImageViewerHarness: a viewer of one image has no previous button");
1363
+ await button.click();
1364
+ }
1365
+ async pressKey(key) {
1366
+ const keys = {
1367
+ ArrowLeft: TestKey.LEFT_ARROW,
1368
+ ArrowRight: TestKey.RIGHT_ARROW,
1369
+ Home: TestKey.HOME,
1370
+ End: TestKey.END,
1371
+ Escape: TestKey.ESCAPE
1372
+ };
1373
+ await (await this.closeButton()).sendKeys(keys[key]);
1374
+ }
1375
+ async close() {
1376
+ await (await this.closeButton()).click();
1377
+ }
1378
+ async requireImage() {
1379
+ const image = await this.image();
1380
+ if (!image) throw new Error("BfImageViewerHarness: the viewer shows no image (it failed to load)");
1381
+ return image;
1382
+ }
1383
+ };
1384
+ var BfChatComposerHarness = class BfChatComposerHarness extends ComponentHarness {
1385
+ static hostSelector = "bf-chat-composer";
1386
+ textarea = this.locatorFor("textarea");
1387
+ label = this.locatorForOptional("label");
1388
+ button = this.locatorFor("button[data-action]");
1389
+ static with(filters = {}) {
1390
+ return new HarnessPredicate(BfChatComposerHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
1391
+ }
1392
+ async getLabel() {
1393
+ const label = await this.label();
1394
+ return (label ? (await label.text()).trim() : "") || (await (await this.textarea()).getAttribute("aria-label") ?? "");
1395
+ }
1396
+ async getValue() {
1397
+ return (await this.textarea()).getProperty("value");
1398
+ }
1399
+ async setValue(text) {
1400
+ const textarea = await this.textarea();
1401
+ await textarea.clear();
1402
+ if (text) {
1403
+ await textarea.setInputValue(text);
1404
+ await textarea.dispatchEvent("input");
1405
+ }
1406
+ }
1407
+ async send() {
1408
+ if (await this.isRunning() || !await this.canAct()) return;
1409
+ await (await this.button()).click();
1410
+ }
1411
+ async pressEnter() {
1412
+ await (await this.textarea()).sendKeys(TestKey.ENTER);
1413
+ }
1414
+ async stop() {
1415
+ if (!await this.isRunning() || !await this.canAct()) return;
1416
+ await (await this.button()).click();
1417
+ }
1418
+ async isRunning() {
1419
+ return await (await this.button()).getAttribute("data-mode") === "stop";
1420
+ }
1421
+ async isDisabled() {
1422
+ return (await this.textarea()).getProperty("disabled");
1423
+ }
1424
+ async canAct() {
1425
+ const button = await this.button();
1426
+ return !await button.getProperty("disabled") && await button.getAttribute("aria-disabled") !== "true";
1427
+ }
1428
+ async getButtonLabel() {
1429
+ return (await (await this.button()).text()).trim();
1430
+ }
1431
+ };
1432
+ export { BfCalendarHarness, BfChartHarness, BfChatComposerHarness, BfComboboxHarness, BfDateInputHarness, BfDateTimeInputHarness, BfDesktopToastHarness, BfDialogHarness, BfFileInputHarness, BfImageInputHarness, BfImageViewerHarness, BfMediaCardHarness, BfMenuButtonHarness, BfMenuHarness, BfMenuItemHarness, BfPaginationHarness, BfPopoverHarness, BfRadioGroupHarness, BfSegmentedHarness, BfSelectHarness, BfSidePanelHarness, BfSidebarToggleHarness, BfTabGroupHarness, BfTableHarness, BfTimeInputHarness, BfToggleHarness, BfTooltipHarness, installBfJsdomShims, provideBfTesting };
1038
1433
 
1039
1434
  //# sourceMappingURL=bitforgehq-ui-testing.mjs.map