@herdingbits/trailhead-webawesome 0.1.2 → 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.
- package/dist/adapter.d.ts +2 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +142 -37
- package/dist/shell.css +20 -114
- package/package.json +4 -4
package/dist/adapter.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Web Awesome Design System Adapter
|
|
3
3
|
*/
|
|
4
|
-
import type { DesignSystemAdapter, FeedbackAdapter } from '@herdingbits/trailhead-types/adapters';
|
|
4
|
+
import type { DesignSystemAdapter, FeedbackAdapter, AuthAdapter } from '@herdingbits/trailhead-types/adapters';
|
|
5
5
|
export interface WebAwesomeAdapterConfig {
|
|
6
6
|
/** URL where Web Awesome is hosted (CDN or local path). Defaults to `${shellUrl}/webawesome`. */
|
|
7
7
|
webAwesomeUrl?: string;
|
|
@@ -10,6 +10,7 @@ export declare class WebAwesomeAdapter implements DesignSystemAdapter {
|
|
|
10
10
|
name: string;
|
|
11
11
|
version: string;
|
|
12
12
|
feedback: FeedbackAdapter;
|
|
13
|
+
auth: AuthAdapter;
|
|
13
14
|
private readonly config?;
|
|
14
15
|
constructor(config?: WebAwesomeAdapterConfig);
|
|
15
16
|
init(shellUrl: string): Promise<void>;
|
package/dist/adapter.d.ts.map
CHANGED
|
@@ -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,MAAM,uCAAuC,CAAC;
|
|
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.
|
|
26
|
+
this.busyDialog = null;
|
|
27
|
+
this.busyActive = false;
|
|
4
28
|
this.toastContainer = null;
|
|
5
29
|
}
|
|
6
30
|
showBusy(message) {
|
|
7
|
-
if (!this.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
<
|
|
37
|
+
<wa-spinner></wa-spinner>
|
|
13
38
|
<div class="shell-busy-message"></div>
|
|
14
39
|
</div>
|
|
15
40
|
`;
|
|
16
|
-
|
|
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.
|
|
51
|
+
const messageEl = this.busyDialog.querySelector(".shell-busy-message");
|
|
19
52
|
if (messageEl) {
|
|
20
53
|
messageEl.textContent = message;
|
|
21
54
|
}
|
|
22
|
-
this.
|
|
55
|
+
this.busyActive = true;
|
|
56
|
+
this.busyDialog.open = true;
|
|
23
57
|
}
|
|
24
58
|
clearBusy() {
|
|
25
|
-
|
|
26
|
-
|
|
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("
|
|
36
|
-
toast.
|
|
37
|
-
toast.
|
|
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,35 +83,100 @@ class WebAwesomeFeedbackAdapter {
|
|
|
44
83
|
}
|
|
45
84
|
showDialog(config) {
|
|
46
85
|
return new Promise((resolve) => {
|
|
47
|
-
|
|
48
|
-
dialog
|
|
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
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
class WebAwesomeAuthAdapter {
|
|
124
|
+
promptCredentials(errorMessage) {
|
|
125
|
+
let settle;
|
|
126
|
+
const result = new Promise((resolve) => {
|
|
127
|
+
settle = resolve;
|
|
128
|
+
});
|
|
129
|
+
let settled = false;
|
|
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";
|
|
135
|
+
dialog.innerHTML = `
|
|
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>
|
|
149
|
+
`;
|
|
150
|
+
const settleOnce = (value) => {
|
|
151
|
+
if (settled)
|
|
152
|
+
return;
|
|
153
|
+
settled = true;
|
|
154
|
+
settle(value);
|
|
155
|
+
dialog.open = false;
|
|
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));
|
|
161
|
+
const form = dialog.querySelector("form");
|
|
162
|
+
form.addEventListener("submit", (e) => {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
const data = new FormData(form);
|
|
165
|
+
settleOnce({
|
|
166
|
+
username: String(data.get("username") ?? ""),
|
|
167
|
+
password: String(data.get("password") ?? ""),
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
dialog.querySelector('[data-action="cancel"]')?.addEventListener("click", () => settleOnce(null));
|
|
171
|
+
document.body.appendChild(dialog);
|
|
172
|
+
dialog.open = true;
|
|
173
|
+
requestAnimationFrame(() => {
|
|
174
|
+
dialog.querySelector('wa-input[name="username"]')?.focus();
|
|
75
175
|
});
|
|
176
|
+
return {
|
|
177
|
+
result,
|
|
178
|
+
close: () => settleOnce(null),
|
|
179
|
+
};
|
|
76
180
|
}
|
|
77
181
|
}
|
|
78
182
|
export class WebAwesomeAdapter {
|
|
@@ -81,6 +185,7 @@ export class WebAwesomeAdapter {
|
|
|
81
185
|
this.version = "1.0.0";
|
|
82
186
|
this.config = config;
|
|
83
187
|
this.feedback = new WebAwesomeFeedbackAdapter();
|
|
188
|
+
this.auth = new WebAwesomeAuthAdapter();
|
|
84
189
|
}
|
|
85
190
|
async init(shellUrl) {
|
|
86
191
|
try {
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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,97 +161,33 @@ 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
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
justify-content: flex-end;
|
|
174
|
+
/* Re-authentication prompt */
|
|
175
|
+
.shell-auth-dialog {
|
|
176
|
+
--width: 400px;
|
|
250
177
|
}
|
|
251
178
|
|
|
252
|
-
.shell-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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;
|
|
179
|
+
.shell-auth-message {
|
|
180
|
+
color: #64748b;
|
|
181
|
+
margin-bottom: 1rem;
|
|
182
|
+
line-height: 1.5;
|
|
278
183
|
}
|
|
279
184
|
|
|
280
|
-
.shell-
|
|
281
|
-
|
|
282
|
-
color: #1e293b;
|
|
185
|
+
.shell-auth-error {
|
|
186
|
+
margin-bottom: 1rem;
|
|
283
187
|
}
|
|
284
188
|
|
|
285
|
-
.shell-
|
|
286
|
-
|
|
189
|
+
.shell-auth-form {
|
|
190
|
+
display: flex;
|
|
191
|
+
flex-direction: column;
|
|
192
|
+
gap: 0.75rem;
|
|
287
193
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herdingbits/trailhead-webawesome",
|
|
3
|
-
"version": "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",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"build": "npm run clean && tsc && cp src/shell.css dist/"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@
|
|
25
|
-
"@
|
|
24
|
+
"@awesome.me/webawesome": "^3.6.0",
|
|
25
|
+
"@herdingbits/trailhead-core": "^0.3.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@herdingbits/trailhead-types": "^0.1.0",
|
|
29
28
|
"@awesome.me/webawesome": "^3.10.0",
|
|
29
|
+
"@herdingbits/trailhead-types": "^0.3.0",
|
|
30
30
|
"typescript": "^6.0.3"
|
|
31
31
|
},
|
|
32
32
|
"keywords": [
|