@herdingbits/trailhead-webawesome 0.2.0 → 0.3.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.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAA4C,WAAW,EAAuC,MAAM,uCAAuC,CAAC;AAuJ9L,MAAM,WAAW,uBAAuB;IACtC,iGAAiG;IACjG,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,iBAAkB,YAAW,mBAAmB;IAC3D,IAAI,SAAgB;IACpB,OAAO,SAAW;IAClB,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAA0B;gBAEtC,MAAM,CAAC,EAAE,uBAAuB;IAMtC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAkB5C"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAA4C,WAAW,EAAuC,MAAM,uCAAuC,CAAC;AAsM9L,MAAM,WAAW,uBAAuB;IACtC,iGAAiG;IACjG,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,iBAAkB,YAAW,mBAAmB;IAC3D,IAAI,SAAgB;IACpB,OAAO,SAAW;IAClB,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAA0B;gBAEtC,MAAM,CAAC,EAAE,uBAAuB;IAMtC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAkB5C"}
package/dist/adapter.js CHANGED
@@ -1,29 +1,64 @@
1
+ const TOAST_ICONS = {
2
+ success: "circle-check",
3
+ error: "circle-exclamation",
4
+ warning: "triangle-exclamation",
5
+ info: "circle-info",
6
+ };
7
+ // wa-callout has no "error"/"info" variant — map onto its brand/danger vocabulary.
8
+ function toastCalloutVariant(variant) {
9
+ if (variant === "error")
10
+ return "danger";
11
+ if (variant === "info")
12
+ return "brand";
13
+ return variant;
14
+ }
15
+ // DialogButton.variant is a free-form string set by callers (only "primary"/"secondary" are
16
+ // used anywhere in practice); map it onto wa-button's variant/appearance vocabulary.
17
+ function dialogButtonAppearance(variant) {
18
+ if (variant === "primary")
19
+ return { variant: "brand", appearance: "filled" };
20
+ if (variant === "secondary")
21
+ return { variant: "neutral", appearance: "outlined" };
22
+ return { variant: "neutral", appearance: "plain" };
23
+ }
1
24
  class WebAwesomeFeedbackAdapter {
2
25
  constructor() {
3
- this.busyOverlay = null;
26
+ this.busyDialog = null;
27
+ this.busyActive = false;
4
28
  this.toastContainer = null;
5
29
  }
6
30
  showBusy(message) {
7
- if (!this.busyOverlay) {
8
- this.busyOverlay = document.createElement("div");
9
- this.busyOverlay.id = "shell-busy-overlay";
10
- this.busyOverlay.innerHTML = `
31
+ if (!this.busyDialog) {
32
+ const dialog = document.createElement("wa-dialog");
33
+ dialog.setAttribute("without-header", "");
34
+ dialog.className = "shell-busy-dialog";
35
+ dialog.innerHTML = `
11
36
  <div class="shell-busy-content">
12
- <div class="shell-spinner"></div>
37
+ <wa-spinner></wa-spinner>
13
38
  <div class="shell-busy-message"></div>
14
39
  </div>
15
40
  `;
16
- document.body.appendChild(this.busyOverlay);
41
+ // Busy is a blocking state — Escape (or any other close trigger) must not dismiss it
42
+ // early; only our own clearBusy() call is allowed to close it. See "Preventing the
43
+ // Dialog from Closing" in the wa-dialog docs.
44
+ dialog.addEventListener("wa-hide", (e) => {
45
+ if (this.busyActive)
46
+ e.preventDefault();
47
+ });
48
+ document.body.appendChild(dialog);
49
+ this.busyDialog = dialog;
17
50
  }
18
- const messageEl = this.busyOverlay.querySelector(".shell-busy-message");
51
+ const messageEl = this.busyDialog.querySelector(".shell-busy-message");
19
52
  if (messageEl) {
20
53
  messageEl.textContent = message;
21
54
  }
22
- this.busyOverlay.style.display = "flex";
55
+ this.busyActive = true;
56
+ this.busyDialog.open = true;
23
57
  }
24
58
  clearBusy() {
25
- if (this.busyOverlay) {
26
- this.busyOverlay.style.display = "none";
59
+ this.busyActive = false;
60
+ if (this.busyDialog) {
61
+ this.busyDialog.open = false;
27
62
  }
28
63
  }
29
64
  showToast(message, variant, duration = 3000) {
@@ -32,9 +67,13 @@ class WebAwesomeFeedbackAdapter {
32
67
  this.toastContainer.id = "shell-toast-container";
33
68
  document.body.appendChild(this.toastContainer);
34
69
  }
35
- const toast = document.createElement("div");
36
- toast.className = `shell-toast shell-toast-${variant}`;
37
- toast.textContent = message;
70
+ const toast = document.createElement("wa-callout");
71
+ toast.setAttribute("variant", toastCalloutVariant(variant));
72
+ toast.setAttribute("appearance", "filled");
73
+ toast.className = "shell-toast";
74
+ // "solid" (the default) is the only style free Font Awesome kits are guaranteed to carry —
75
+ // "regular"/"light"/"thin" are Pro-only and 403 silently on a free kit.
76
+ toast.innerHTML = `<wa-icon slot="icon" name="${TOAST_ICONS[variant]}"></wa-icon>${message}`;
38
77
  this.toastContainer.appendChild(toast);
39
78
  setTimeout(() => toast.classList.add("shell-toast-show"), 10);
40
79
  setTimeout(() => {
@@ -44,34 +83,40 @@ class WebAwesomeFeedbackAdapter {
44
83
  }
45
84
  showDialog(config) {
46
85
  return new Promise((resolve) => {
47
- const dialog = document.createElement("div");
48
- dialog.className = "shell-dialog-overlay";
86
+ let settled = false;
87
+ const dialog = document.createElement("wa-dialog");
88
+ if (config.title) {
89
+ dialog.setAttribute("label", config.title);
90
+ }
91
+ else {
92
+ dialog.setAttribute("without-header", "");
93
+ }
94
+ dialog.setAttribute("light-dismiss", "");
95
+ dialog.className = "shell-dialog";
49
96
  dialog.innerHTML = `
50
- <div class="shell-dialog">
51
- ${config.title ? `<div class="shell-dialog-title">${config.title}</div>` : ''}
52
- <div class="shell-dialog-message">${config.message}</div>
53
- <div class="shell-dialog-buttons">
54
- ${config.buttons
55
- .map((btn) => `<button class="shell-btn shell-btn-${btn.variant || "default"}" data-value="${btn.value}">${btn.label}</button>`)
97
+ <p class="shell-dialog-message">${config.message}</p>
98
+ ${config.buttons
99
+ .map((btn) => {
100
+ const { variant, appearance } = dialogButtonAppearance(btn.variant);
101
+ return `<wa-button slot="footer" variant="${variant}" appearance="${appearance}" data-value="${btn.value}">${btn.label}</wa-button>`;
102
+ })
56
103
  .join("")}
57
- </div>
58
- </div>
59
104
  `;
60
- dialog.querySelectorAll("button").forEach((btn) => {
61
- btn.onclick = () => {
62
- const value = btn.getAttribute("data-value");
63
- dialog.remove();
64
- resolve({ value });
65
- };
66
- });
67
- // Click outside to cancel
68
- dialog.onclick = (e) => {
69
- if (e.target === dialog) {
70
- dialog.remove();
71
- resolve({ value: null });
72
- }
105
+ const settleOnce = (value) => {
106
+ if (settled)
107
+ return;
108
+ settled = true;
109
+ resolve({ value });
110
+ dialog.open = false;
73
111
  };
112
+ dialog.addEventListener("wa-after-hide", () => dialog.remove());
113
+ // Escape, the header close button, and light-dismiss all fire wa-hide — treat as no selection.
114
+ dialog.addEventListener("wa-hide", () => settleOnce(null));
115
+ dialog.querySelectorAll("wa-button[data-value]").forEach((btn) => {
116
+ btn.addEventListener("click", () => settleOnce(btn.getAttribute("data-value")));
117
+ });
74
118
  document.body.appendChild(dialog);
119
+ dialog.open = true;
75
120
  });
76
121
  }
77
122
  }
@@ -82,30 +127,37 @@ class WebAwesomeAuthAdapter {
82
127
  settle = resolve;
83
128
  });
84
129
  let settled = false;
85
- const dialog = document.createElement("div");
86
- dialog.className = "shell-dialog-overlay";
130
+ const formId = `shell-auth-form-${Math.random().toString(36).slice(2)}`;
131
+ const dialog = document.createElement("wa-dialog");
132
+ dialog.setAttribute("label", "Session Expired");
133
+ dialog.setAttribute("light-dismiss", "");
134
+ dialog.className = "shell-auth-dialog";
87
135
  dialog.innerHTML = `
88
- <div class="shell-dialog">
89
- <div class="shell-dialog-title">Session Expired</div>
90
- <div class="shell-dialog-message">Sign in to continue where you left off.</div>
91
- ${errorMessage ? `<div class="shell-dialog-error">${errorMessage}</div>` : ""}
92
- <form class="shell-auth-form">
93
- <input type="text" name="username" placeholder="Username" autocomplete="username" required />
94
- <input type="password" name="password" placeholder="Password" autocomplete="current-password" required />
95
- <div class="shell-dialog-buttons">
96
- <button type="button" class="shell-btn shell-btn-secondary" data-action="cancel">Cancel</button>
97
- <button type="submit" class="shell-btn shell-btn-primary">Sign In</button>
98
- </div>
99
- </form>
100
- </div>
136
+ <p class="shell-auth-message">Sign in to continue where you left off.</p>
137
+ ${errorMessage
138
+ ? `<wa-callout variant="danger" size="small" class="shell-auth-error">
139
+ <wa-icon slot="icon" name="circle-exclamation"></wa-icon>
140
+ ${errorMessage}
141
+ </wa-callout>`
142
+ : ""}
143
+ <form id="${formId}" class="shell-auth-form">
144
+ <wa-input name="username" label="Username" autocomplete="username" autofocus required></wa-input>
145
+ <wa-input name="password" type="password" label="Password" autocomplete="current-password" password-toggle required></wa-input>
146
+ </form>
147
+ <wa-button slot="footer" appearance="outlined" data-action="cancel">Cancel</wa-button>
148
+ <wa-button slot="footer" variant="brand" appearance="filled" type="submit" form="${formId}">Sign In</wa-button>
101
149
  `;
102
150
  const settleOnce = (value) => {
103
151
  if (settled)
104
152
  return;
105
153
  settled = true;
106
- dialog.remove();
107
154
  settle(value);
155
+ dialog.open = false;
108
156
  };
157
+ // wa-dialog owns its own close animation; only remove the element once it's finished.
158
+ dialog.addEventListener("wa-after-hide", () => dialog.remove());
159
+ // Escape, the header close button, and light-dismiss all fire wa-hide — treat every one as cancel.
160
+ dialog.addEventListener("wa-hide", () => settleOnce(null));
109
161
  const form = dialog.querySelector("form");
110
162
  form.addEventListener("submit", (e) => {
111
163
  e.preventDefault();
@@ -116,13 +168,11 @@ class WebAwesomeAuthAdapter {
116
168
  });
117
169
  });
118
170
  dialog.querySelector('[data-action="cancel"]')?.addEventListener("click", () => settleOnce(null));
119
- // Click outside to cancel — matches showDialog's existing convention.
120
- dialog.onclick = (e) => {
121
- if (e.target === dialog)
122
- settleOnce(null);
123
- };
124
171
  document.body.appendChild(dialog);
125
- dialog.querySelector('input[name="username"]')?.focus();
172
+ dialog.open = true;
173
+ requestAnimationFrame(() => {
174
+ dialog.querySelector('wa-input[name="username"]')?.focus();
175
+ });
126
176
  return {
127
177
  result,
128
178
  close: () => settleOnce(null),
package/dist/shell.css CHANGED
@@ -123,39 +123,13 @@ body {
123
123
  }
124
124
 
125
125
  /* Busy overlay */
126
- #shell-busy-overlay {
127
- position: fixed;
128
- top: 0;
129
- left: 0;
130
- right: 0;
131
- bottom: 0;
132
- background-color: rgba(0, 0, 0, 0.5);
133
- display: none;
134
- align-items: center;
135
- justify-content: center;
136
- z-index: 9999;
137
- }
138
-
139
126
  .shell-busy-content {
140
- background: white;
141
- padding: 2rem;
142
- border-radius: 8px;
127
+ display: flex;
128
+ flex-direction: column;
129
+ align-items: center;
130
+ gap: 1rem;
131
+ padding: 1rem 2rem 1.5rem;
143
132
  text-align: center;
144
- min-width: 200px;
145
- }
146
-
147
- .shell-spinner {
148
- width: 40px;
149
- height: 40px;
150
- border: 4px solid #e5e7eb;
151
- border-top-color: #3b82f6;
152
- border-radius: 50%;
153
- animation: shell-spin 0.8s linear infinite;
154
- margin: 0 auto 1rem;
155
- }
156
-
157
- @keyframes shell-spin {
158
- to { transform: rotate(360deg); }
159
133
  }
160
134
 
161
135
  .shell-busy-message {
@@ -175,15 +149,11 @@ body {
175
149
  }
176
150
 
177
151
  .shell-toast {
178
- padding: 1rem 1.5rem;
179
- border-radius: 6px;
180
- color: white;
181
- font-size: 0.875rem;
152
+ max-width: 400px;
182
153
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
183
154
  opacity: 0;
184
155
  transform: translateX(100%);
185
156
  transition: all 0.3s;
186
- max-width: 400px;
187
157
  }
188
158
 
189
159
  .shell-toast-show {
@@ -191,109 +161,29 @@ body {
191
161
  transform: translateX(0);
192
162
  }
193
163
 
194
- .shell-toast-success {
195
- background-color: #10b981;
196
- }
197
-
198
- .shell-toast-error {
199
- background-color: #ef4444;
200
- }
201
-
202
- .shell-toast-warning {
203
- background-color: #f59e0b;
204
- }
205
-
206
- .shell-toast-info {
207
- background-color: #3b82f6;
208
- }
209
-
210
164
  /* Dialog */
211
- .shell-dialog-overlay {
212
- position: fixed;
213
- top: 0;
214
- left: 0;
215
- right: 0;
216
- bottom: 0;
217
- background-color: rgba(0, 0, 0, 0.5);
218
- display: flex;
219
- align-items: center;
220
- justify-content: center;
221
- z-index: 10001;
222
- }
223
-
224
165
  .shell-dialog {
225
- background: white;
226
- border-radius: 8px;
227
- padding: 1.5rem;
228
- min-width: 400px;
229
- max-width: 600px;
230
- box-shadow: 0 20px 25px rgba(0, 0, 0, 0.15);
231
- }
232
-
233
- .shell-dialog-title {
234
- font-size: 1.25rem;
235
- font-weight: 600;
236
- color: #1e293b;
237
- margin-bottom: 1rem;
166
+ --width: 440px;
238
167
  }
239
168
 
240
169
  .shell-dialog-message {
241
170
  color: #64748b;
242
- margin-bottom: 1.5rem;
243
171
  line-height: 1.5;
244
172
  }
245
173
 
246
- .shell-dialog-buttons {
247
- display: flex;
248
- gap: 0.75rem;
249
- justify-content: flex-end;
250
- }
251
-
252
- .shell-btn {
253
- padding: 0.5rem 1rem;
254
- border: none;
255
- border-radius: 6px;
256
- font-size: 0.875rem;
257
- font-weight: 500;
258
- cursor: pointer;
259
- transition: all 0.2s;
260
- }
261
-
262
- .shell-btn-primary {
263
- background-color: #3b82f6;
264
- color: white;
265
- }
266
-
267
- .shell-btn-primary:hover {
268
- background-color: #2563eb;
269
- }
270
-
271
- .shell-btn-secondary {
272
- background-color: #e5e7eb;
273
- color: #1e293b;
274
- }
275
-
276
- .shell-btn-secondary:hover {
277
- background-color: #d1d5db;
278
- }
279
-
280
- .shell-btn-default {
281
- background-color: #f3f4f6;
282
- color: #1e293b;
174
+ /* Re-authentication prompt */
175
+ .shell-auth-dialog {
176
+ --width: 400px;
283
177
  }
284
178
 
285
- .shell-btn-default:hover {
286
- background-color: #e5e7eb;
179
+ .shell-auth-message {
180
+ color: #64748b;
181
+ margin-bottom: 1rem;
182
+ line-height: 1.5;
287
183
  }
288
184
 
289
- /* Re-authentication prompt */
290
- .shell-dialog-error {
291
- color: #dc2626;
292
- background-color: #fef2f2;
293
- border-radius: 6px;
294
- padding: 0.5rem 0.75rem;
185
+ .shell-auth-error {
295
186
  margin-bottom: 1rem;
296
- font-size: 0.875rem;
297
187
  }
298
188
 
299
189
  .shell-auth-form {
@@ -301,16 +191,3 @@ body {
301
191
  flex-direction: column;
302
192
  gap: 0.75rem;
303
193
  }
304
-
305
- .shell-auth-form input {
306
- padding: 0.5rem 0.75rem;
307
- border: 1px solid #d1d5db;
308
- border-radius: 6px;
309
- font-size: 0.875rem;
310
- }
311
-
312
- .shell-auth-form input:focus {
313
- outline: none;
314
- border-color: #3b82f6;
315
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
316
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herdingbits/trailhead-webawesome",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Web Awesome web components adapter for Trailhead. Vanilla TypeScript, no React required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,11 +22,11 @@
22
22
  },
23
23
  "peerDependencies": {
24
24
  "@awesome.me/webawesome": "^3.6.0",
25
- "@herdingbits/trailhead-core": "^0.2.0"
25
+ "@herdingbits/trailhead-core": "^0.3.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@awesome.me/webawesome": "^3.10.0",
29
- "@herdingbits/trailhead-types": "^0.2.0",
29
+ "@herdingbits/trailhead-types": "^0.3.0",
30
30
  "typescript": "^6.0.3"
31
31
  },
32
32
  "keywords": [