@globalpayments-private/gpds-pattern-web 0.0.0-stage → 0.1.0-pr.7.gdcb74cd

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 (97) hide show
  1. package/README.md +567 -2
  2. package/dist/cjs/control-strategy-DhhYTbse.js +113 -0
  3. package/dist/cjs/gpds-pattern-web.cjs.js +24 -0
  4. package/dist/cjs/index-Dzm6P89U.js +1565 -0
  5. package/dist/cjs/index.cjs.js +180 -0
  6. package/dist/cjs/loader.cjs.js +12 -0
  7. package/dist/cjs/uds-pattern-login-card_2.cjs.entry.js +1662 -0
  8. package/dist/collection/collection-manifest.json +14 -0
  9. package/dist/collection/components/uds-pattern-login-card/slimmers/identity-memory.slimmer.js +63 -0
  10. package/dist/collection/components/uds-pattern-login-card/slimmers/login-workflow.slimmer.js +357 -0
  11. package/dist/collection/components/uds-pattern-login-card/slimmers/renderers/login-card-renderer.js +100 -0
  12. package/dist/collection/components/uds-pattern-login-card/slimmers/renderers/login-field-renderer.js +129 -0
  13. package/dist/collection/components/uds-pattern-login-card/slimmers/renderers/login-step-renderer.js +192 -0
  14. package/dist/collection/components/uds-pattern-login-card/slimmers/resend-throttle.slimmer.js +65 -0
  15. package/dist/collection/components/uds-pattern-login-card/uds-pattern-login-card.css +154 -0
  16. package/dist/collection/components/uds-pattern-login-card/uds-pattern-login-card.js +508 -0
  17. package/dist/collection/components/uds-pattern-login-card/validation/login-validation-rules.js +86 -0
  18. package/dist/collection/components/uds-pattern-login-card/view-model/login-card-handlers.js +1 -0
  19. package/dist/collection/components/uds-pattern-login-card/view-model/login-step.view-model.js +138 -0
  20. package/dist/collection/components/uds-pattern-login-page/uds-pattern-login-page.css +37 -0
  21. package/dist/collection/components/uds-pattern-login-page/uds-pattern-login-page.js +97 -0
  22. package/dist/collection/index.js +5 -0
  23. package/dist/collection/providers/auth-provider.js +28 -0
  24. package/dist/collection/providers/control-strategy.js +21 -0
  25. package/dist/collection/providers/login-config.js +4 -0
  26. package/dist/collection/providers/memory-auth-provider.js +133 -0
  27. package/dist/collection/providers/parse-auth-result.js +135 -0
  28. package/dist/collection/slimmer/common/log.js +14 -0
  29. package/dist/collection/slimmer/common/symbols.js +10 -0
  30. package/dist/collection/slimmer/common/types.js +2 -0
  31. package/dist/collection/slimmer/decorators/inject-slimmer.js +73 -0
  32. package/dist/collection/slimmer/decorators/map-to-component-field.js +41 -0
  33. package/dist/collection/slimmer/decorators/map-to-component-method.js +33 -0
  34. package/dist/collection/slimmer/decorators/slimmer.js +10 -0
  35. package/dist/collection/slimmer/index.js +5 -0
  36. package/dist/collection/utils/labels.js +81 -0
  37. package/dist/collection/utils/types.js +1 -0
  38. package/dist/components/index.d.ts +35 -0
  39. package/dist/components/index.js +1 -0
  40. package/dist/components/p-DOrg55po.js +1 -0
  41. package/dist/components/p-_G8A9gg0.js +1 -0
  42. package/dist/components/uds-pattern-login-card.d.ts +11 -0
  43. package/dist/components/uds-pattern-login-card.js +1 -0
  44. package/dist/components/uds-pattern-login-page.d.ts +11 -0
  45. package/dist/components/uds-pattern-login-page.js +1 -0
  46. package/dist/docs.d.ts +480 -0
  47. package/dist/docs.json +549 -0
  48. package/dist/esm/control-strategy-DOrg55po.js +105 -0
  49. package/dist/esm/gpds-pattern-web.js +20 -0
  50. package/dist/esm/index-DsXlLyGj.js +1537 -0
  51. package/dist/esm/index.js +170 -0
  52. package/dist/esm/loader.js +10 -0
  53. package/dist/esm/uds-pattern-login-card_2.entry.js +1659 -0
  54. package/dist/gpds-pattern-web/gpds-pattern-web.css +1 -0
  55. package/dist/gpds-pattern-web/gpds-pattern-web.esm.js +1 -0
  56. package/dist/gpds-pattern-web/index.esm.js +1 -0
  57. package/dist/gpds-pattern-web/p-2c97d2da.entry.js +1 -0
  58. package/dist/gpds-pattern-web/p-DOrg55po.js +1 -0
  59. package/dist/gpds-pattern-web/p-DsXlLyGj.js +3 -0
  60. package/dist/index.cjs.js +1 -0
  61. package/dist/index.js +1 -0
  62. package/dist/types/components/uds-pattern-login-card/slimmers/identity-memory.slimmer.d.ts +21 -0
  63. package/dist/types/components/uds-pattern-login-card/slimmers/login-workflow.slimmer.d.ts +100 -0
  64. package/dist/types/components/uds-pattern-login-card/slimmers/renderers/login-card-renderer.d.ts +44 -0
  65. package/dist/types/components/uds-pattern-login-card/slimmers/renderers/login-field-renderer.d.ts +91 -0
  66. package/dist/types/components/uds-pattern-login-card/slimmers/renderers/login-step-renderer.d.ts +40 -0
  67. package/dist/types/components/uds-pattern-login-card/slimmers/resend-throttle.slimmer.d.ts +21 -0
  68. package/dist/types/components/uds-pattern-login-card/uds-pattern-login-card.d.ts +134 -0
  69. package/dist/types/components/uds-pattern-login-card/validation/login-validation-rules.d.ts +72 -0
  70. package/dist/types/components/uds-pattern-login-card/view-model/login-card-handlers.d.ts +21 -0
  71. package/dist/types/components/uds-pattern-login-card/view-model/login-step.view-model.d.ts +96 -0
  72. package/dist/types/components/uds-pattern-login-page/uds-pattern-login-page.d.ts +21 -0
  73. package/dist/types/components.d.ts +327 -0
  74. package/dist/types/index.d.ts +10 -0
  75. package/dist/types/providers/auth-provider.d.ts +168 -0
  76. package/dist/types/providers/control-strategy.d.ts +144 -0
  77. package/dist/types/providers/login-config.d.ts +52 -0
  78. package/dist/types/providers/memory-auth-provider.d.ts +37 -0
  79. package/dist/types/providers/parse-auth-result.d.ts +8 -0
  80. package/dist/types/slimmer/common/log.d.ts +8 -0
  81. package/dist/types/slimmer/common/symbols.d.ts +10 -0
  82. package/dist/types/slimmer/common/types.d.ts +30 -0
  83. package/dist/types/slimmer/decorators/inject-slimmer.d.ts +24 -0
  84. package/dist/types/slimmer/decorators/map-to-component-field.d.ts +16 -0
  85. package/dist/types/slimmer/decorators/map-to-component-method.d.ts +11 -0
  86. package/dist/types/slimmer/decorators/slimmer.d.ts +10 -0
  87. package/dist/types/slimmer/index.d.ts +6 -0
  88. package/dist/types/stencil-public-runtime.d.ts +1879 -0
  89. package/dist/types/utils/labels.d.ts +13 -0
  90. package/dist/types/utils/types.d.ts +106 -0
  91. package/dist/types/vega.d.ts +11 -0
  92. package/loader/cdn.js +1 -0
  93. package/loader/index.cjs.js +1 -0
  94. package/loader/index.d.ts +24 -0
  95. package/loader/index.es2017.js +1 -0
  96. package/loader/index.js +2 -0
  97. package/package.json +82 -4
@@ -0,0 +1,14 @@
1
+ {
2
+ "entries": [
3
+ "components/uds-pattern-login-card/uds-pattern-login-card.js",
4
+ "components/uds-pattern-login-page/uds-pattern-login-page.js"
5
+ ],
6
+ "mixins": [],
7
+ "compiler": {
8
+ "name": "@stencil/core",
9
+ "version": "4.45.2",
10
+ "typescriptVersion": "5.8.3"
11
+ },
12
+ "collections": [],
13
+ "bundles": []
14
+ }
@@ -0,0 +1,63 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
4
+ r = Reflect.decorate(decorators, target, key, desc);
5
+ else
6
+ for (var i = decorators.length - 1; i >= 0; i--)
7
+ if (d = decorators[i])
8
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
9
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
10
+ };
11
+ import { UdsSlimmer, MapToComponentField } from "../../../slimmer";
12
+ /**
13
+ * Persistence of the remembered identifier.
14
+ *
15
+ * Isolated because it is the one place the pattern touches storage, and storage
16
+ * can fail for reasons that have nothing to do with signing in — private
17
+ * browsing, blocked cookies, a strict enterprise policy. Those failures are
18
+ * swallowed deliberately: remembering an email address is a convenience, never
19
+ * a precondition for authenticating.
20
+ */
21
+ export class IdentityMemorySlimmer extends UdsSlimmer {
22
+ constructor() {
23
+ super();
24
+ }
25
+ /** The identifier the card should start with. */
26
+ initialIdentifier() {
27
+ return this.identifier || this.read() || '';
28
+ }
29
+ read() {
30
+ if (!this.rememberStorageKey) {
31
+ return '';
32
+ }
33
+ try {
34
+ return globalThis.localStorage?.getItem(this.rememberStorageKey) ?? '';
35
+ }
36
+ catch {
37
+ return '';
38
+ }
39
+ }
40
+ /** Stores or clears the identifier according to the user's choice. */
41
+ remember(identifier, remember) {
42
+ if (!this.rememberStorageKey) {
43
+ return;
44
+ }
45
+ try {
46
+ if (remember) {
47
+ globalThis.localStorage?.setItem(this.rememberStorageKey, identifier);
48
+ }
49
+ else {
50
+ globalThis.localStorage?.removeItem(this.rememberStorageKey);
51
+ }
52
+ }
53
+ catch {
54
+ // See the class comment: persistence is best-effort by design.
55
+ }
56
+ }
57
+ }
58
+ __decorate([
59
+ MapToComponentField()
60
+ ], IdentityMemorySlimmer.prototype, "rememberStorageKey", void 0);
61
+ __decorate([
62
+ MapToComponentField()
63
+ ], IdentityMemorySlimmer.prototype, "identifier", void 0);
@@ -0,0 +1,357 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
4
+ r = Reflect.decorate(decorators, target, key, desc);
5
+ else
6
+ for (var i = decorators.length - 1; i >= 0; i--)
7
+ if (d = decorators[i])
8
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
9
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
10
+ };
11
+ import { UdsSlimmer, MapToComponentField, MapToComponentMethod } from "../../../slimmer";
12
+ import { fail, goTo, intercept, stay, } from "../../../providers/control-strategy";
13
+ import { parseAuthResult } from "../../../providers/parse-auth-result";
14
+ /**
15
+ * The login state machine.
16
+ *
17
+ * This is the pattern's core asset: the translation from "what the identity
18
+ * platform needs next" into "what the user sees next". Keeping it in one class,
19
+ * away from markup and input plumbing, is what makes the sequencing reviewable.
20
+ *
21
+ * Every decision it makes passes through the host's `controlStrategy` first, so
22
+ * a consumer can redirect the flow without reimplementing it.
23
+ */
24
+ export class LoginWorkflowSlimmer extends UdsSlimmer {
25
+ constructor() {
26
+ super();
27
+ /** The identifier being authenticated, carried across steps. */
28
+ this.pendingIdentifier = '';
29
+ }
30
+ // --- Lifecycle ----------------------------------------------------------
31
+ start() {
32
+ this.step = this.initialStep;
33
+ this.pendingIdentifier = this.memory.initialIdentifier();
34
+ this.resetStepState();
35
+ }
36
+ /** Returns the workflow to its first step and clears all collected input. */
37
+ async restart() {
38
+ this.methods = [];
39
+ this.errorText = undefined;
40
+ this.destination = undefined;
41
+ this.unlockMinutes = undefined;
42
+ this.otpCount = undefined;
43
+ this.resend.stop();
44
+ this.applyTransition(goTo(this.initialStep), 'reset');
45
+ }
46
+ // --- Capabilities -------------------------------------------------------
47
+ /**
48
+ * Which affordances to render. The presence of an optional provider method
49
+ * *is* the configuration, so a route can never be offered for something the
50
+ * backend cannot service.
51
+ */
52
+ get capabilities() {
53
+ const a = this.auth;
54
+ return {
55
+ signUp: typeof a?.signUp === 'function',
56
+ reset: typeof a?.requestPasswordReset === 'function',
57
+ username: typeof a?.requestUsernameReminder === 'function',
58
+ resend: typeof a?.resendOtp === 'function',
59
+ };
60
+ }
61
+ // --- Navigation ---------------------------------------------------------
62
+ /** In-workflow navigation, triggered by a link in the card. */
63
+ navigateTo(step) {
64
+ this.errorText = undefined;
65
+ this.applyTransition(goTo(step), 'link');
66
+ }
67
+ /**
68
+ * Performs a transition, giving the strategy its say on every step change —
69
+ * including the ones the workflow initiates for itself.
70
+ */
71
+ applyTransition(transition, cause) {
72
+ if (transition.type === 'stay') {
73
+ return;
74
+ }
75
+ if (transition.type === 'fail') {
76
+ this.showError(transition.code, transition.message);
77
+ return;
78
+ }
79
+ const decided = intercept(this.controlStrategy?.navigate, { from: this.step, to: transition.step, cause }, () => transition);
80
+ // A strategy may answer a navigation with a different kind of outcome
81
+ // entirely — stay put, or surface a failure instead of moving.
82
+ if (decided.type === 'stay') {
83
+ return;
84
+ }
85
+ if (decided.type === 'fail') {
86
+ this.showError(decided.code, decided.message);
87
+ return;
88
+ }
89
+ this.setStep(decided.step);
90
+ }
91
+ setStep(next) {
92
+ const previousStep = this.step;
93
+ this.step = next;
94
+ this.resetStepState();
95
+ if (previousStep !== next) {
96
+ this.udsLoginStepChange.emit({ step: next, previousStep });
97
+ }
98
+ }
99
+ /** Clears per-step input, seeding the identifier field where appropriate. */
100
+ resetStepState() {
101
+ const seedsIdentifier = this.step === 'identifier' || this.step === 'forgot-password';
102
+ this.values = seedsIdentifier && this.pendingIdentifier ? { identifier: this.pendingIdentifier } : {};
103
+ }
104
+ /** How many characters the passcode entry should render. */
105
+ get codeLength() {
106
+ return Math.max(1, this.otpCount ?? this.otpLength);
107
+ }
108
+ // --- Result translation -------------------------------------------------
109
+ /**
110
+ * Translates a provider result into the next step. This inversion is what
111
+ * makes the pattern a pattern: the identity platform reports what it needs,
112
+ * and the card — not the consumer — decides what that looks like.
113
+ */
114
+ applyResult(result) {
115
+ this.errorText = undefined;
116
+ const transition = intercept(this.controlStrategy?.transition, { result, from: this.step }, () => this.defaultTransitionFor(result));
117
+ // Side effects that belong to the result rather than the destination are
118
+ // staged before the move, so the step renders with its data already in place.
119
+ this.stageResultState(result);
120
+ if (result.status === 'mfa_required' && this.methods.length === 1 && transition.type === 'stay') {
121
+ // A single option is not a choice — skip the picker.
122
+ void this.selectMfaMethod(this.methods[0].id);
123
+ return;
124
+ }
125
+ this.applyTransition(transition, 'auto');
126
+ if (result.status === 'success' && transition.type === 'goto' && transition.step === 'success') {
127
+ intercept(this.controlStrategy?.complete, { session: result.session }, () => this.udsLoginSuccess.emit({ session: result.session }));
128
+ }
129
+ }
130
+ defaultTransitionFor(result) {
131
+ switch (result.status) {
132
+ case 'success':
133
+ return goTo('success');
134
+ case 'password_required':
135
+ return goTo('password');
136
+ case 'mfa_required':
137
+ // Staying signals "the picker is not needed"; `applyResult` turns that
138
+ // into an auto-selection when the provider offered exactly one method.
139
+ return result.methods.length === 1 ? stay() : goTo('mfa-method');
140
+ case 'otp_required':
141
+ return goTo('otp');
142
+ case 'password_reset_required':
143
+ return goTo('reset-password');
144
+ case 'reset_link_sent':
145
+ case 'username_sent':
146
+ return goTo('check-email');
147
+ case 'locked':
148
+ return goTo('locked');
149
+ case 'error':
150
+ return fail(result.code, result.message);
151
+ }
152
+ }
153
+ /** Captures the data a result carries, independent of where the flow goes. */
154
+ stageResultState(result) {
155
+ switch (result.status) {
156
+ case 'mfa_required':
157
+ this.methods = result.methods;
158
+ return;
159
+ case 'otp_required':
160
+ this.otpMethodLabel = result.method?.label;
161
+ this.otpCount = result.codeLength ?? this.otpLength;
162
+ this.resend.start(result.resendAfterMs);
163
+ return;
164
+ case 'reset_link_sent':
165
+ case 'username_sent':
166
+ this.destination = result.destination;
167
+ return;
168
+ case 'locked':
169
+ this.unlockMinutes = result.unlockAfterMs ? Math.ceil(result.unlockAfterMs / 60000) : undefined;
170
+ return;
171
+ default:
172
+ return;
173
+ }
174
+ }
175
+ // --- Failures -----------------------------------------------------------
176
+ showError(code, message) {
177
+ const outcome = intercept(this.controlStrategy?.failure, { code, step: this.step, message }, () => ({
178
+ text: this.copy.errors[code] ?? this.copy.errors.unknown,
179
+ }));
180
+ this.errorText = outcome.text;
181
+ this.udsLoginError.emit({ code, step: this.step, message });
182
+ if (outcome.transition) {
183
+ this.applyTransition(outcome.transition, 'auto');
184
+ }
185
+ }
186
+ // --- Provider calls -----------------------------------------------------
187
+ /**
188
+ * Wraps a provider call with the in-flight guard and the network catch.
189
+ *
190
+ * `busy` is released *before* the result is applied, because applying a
191
+ * result may legitimately chain another provider call (auto-selecting a lone
192
+ * MFA method, for example). Holding the guard across `applyResult` would make
193
+ * that continuation a no-op and strand the user on the previous step. The
194
+ * chained call re-acquires the guard synchronously, so no un-busy frame is
195
+ * ever painted.
196
+ *
197
+ * Every provider response is validated here, at the one point where data
198
+ * crosses into the pattern from a consumer's adapter. See `parseAuthResult`.
199
+ */
200
+ async run(call, source) {
201
+ if (this.busy) {
202
+ return;
203
+ }
204
+ this.busy = true;
205
+ let result;
206
+ try {
207
+ result = parseAuthResult(await call(), `auth.${source}`);
208
+ }
209
+ catch {
210
+ this.busy = false;
211
+ this.showError('network');
212
+ return;
213
+ }
214
+ this.busy = false;
215
+ this.applyResult(result);
216
+ }
217
+ async selectMfaMethod(methodId) {
218
+ if (!this.auth?.selectMfaMethod) {
219
+ this.showError('unknown');
220
+ return;
221
+ }
222
+ await this.run(() => this.auth.selectMfaMethod({ methodId }), 'selectMfaMethod');
223
+ }
224
+ async resendCode() {
225
+ if (!this.resend.canResend || !this.auth?.resendOtp) {
226
+ return;
227
+ }
228
+ await this.run(() => this.auth.resendOtp(), 'resendOtp');
229
+ }
230
+ // --- Submission ---------------------------------------------------------
231
+ /**
232
+ * Submits the current step.
233
+ *
234
+ * The strategy wraps the whole provider call, so a consumer can gate it
235
+ * (CAPTCHA, device check, consent) or answer it outright without the pattern
236
+ * needing a dedicated prop for each case.
237
+ */
238
+ async submit() {
239
+ if (this.busy) {
240
+ return;
241
+ }
242
+ const step = this.step;
243
+ const values = { ...this.values };
244
+ this.rememberIdentifierIfRequested(step);
245
+ await this.run(() => intercept(this.controlStrategy?.submit, { step, values, identifier: this.pendingIdentifier }, () => this.callProvider(step)), `submit(${step})`);
246
+ }
247
+ rememberIdentifierIfRequested(step) {
248
+ if (step !== 'identifier') {
249
+ return;
250
+ }
251
+ const identifier = this.str('identifier').trim();
252
+ this.pendingIdentifier = identifier;
253
+ this.memory.remember(identifier, this.values.rememberIdentifier === true);
254
+ }
255
+ callProvider(step) {
256
+ const auth = this.auth;
257
+ if (!auth) {
258
+ return Promise.resolve({ status: 'error', code: 'unknown' });
259
+ }
260
+ const unsupported = Promise.resolve({ status: 'error', code: 'unknown' });
261
+ switch (step) {
262
+ case 'identifier':
263
+ return auth.signIn({
264
+ identifier: this.pendingIdentifier,
265
+ rememberIdentifier: this.values.rememberIdentifier === true,
266
+ });
267
+ case 'password': {
268
+ const password = this.str('password');
269
+ const identifier = this.pendingIdentifier;
270
+ return auth.verifyPassword
271
+ ? auth.verifyPassword({ identifier, password })
272
+ : auth.signIn({ identifier, password });
273
+ }
274
+ case 'mfa-method':
275
+ return auth.selectMfaMethod?.({ methodId: this.str('mfaMethod') }) ?? unsupported;
276
+ case 'otp':
277
+ return auth.verifyOtp?.({ code: this.str('code') }) ?? unsupported;
278
+ case 'forgot-password':
279
+ return auth.requestPasswordReset?.({ identifier: this.str('identifier').trim() }) ?? unsupported;
280
+ case 'forgot-username':
281
+ return auth.requestUsernameReminder?.({ email: this.str('email').trim() }) ?? unsupported;
282
+ case 'reset-password':
283
+ return auth.resetPassword?.({ password: this.str('password') }) ?? unsupported;
284
+ case 'sign-up':
285
+ return (auth.signUp?.({
286
+ firstName: this.str('firstName'),
287
+ lastName: this.str('lastName'),
288
+ identifier: this.str('identifier').trim(),
289
+ }) ?? unsupported);
290
+ default:
291
+ return unsupported;
292
+ }
293
+ }
294
+ str(name) {
295
+ return String(this.values[name] ?? '');
296
+ }
297
+ }
298
+ __decorate([
299
+ MapToComponentField()
300
+ ], LoginWorkflowSlimmer.prototype, "resend", void 0);
301
+ __decorate([
302
+ MapToComponentField()
303
+ ], LoginWorkflowSlimmer.prototype, "memory", void 0);
304
+ __decorate([
305
+ MapToComponentField()
306
+ ], LoginWorkflowSlimmer.prototype, "udsLoginStepChange", void 0);
307
+ __decorate([
308
+ MapToComponentField()
309
+ ], LoginWorkflowSlimmer.prototype, "udsLoginError", void 0);
310
+ __decorate([
311
+ MapToComponentField()
312
+ ], LoginWorkflowSlimmer.prototype, "udsLoginSuccess", void 0);
313
+ __decorate([
314
+ MapToComponentField()
315
+ ], LoginWorkflowSlimmer.prototype, "auth", void 0);
316
+ __decorate([
317
+ MapToComponentField()
318
+ ], LoginWorkflowSlimmer.prototype, "controlStrategy", void 0);
319
+ __decorate([
320
+ MapToComponentField()
321
+ ], LoginWorkflowSlimmer.prototype, "initialStep", void 0);
322
+ __decorate([
323
+ MapToComponentField()
324
+ ], LoginWorkflowSlimmer.prototype, "otpLength", void 0);
325
+ __decorate([
326
+ MapToComponentField()
327
+ ], LoginWorkflowSlimmer.prototype, "copy", void 0);
328
+ __decorate([
329
+ MapToComponentField({ writable: true })
330
+ ], LoginWorkflowSlimmer.prototype, "step", void 0);
331
+ __decorate([
332
+ MapToComponentField({ writable: true })
333
+ ], LoginWorkflowSlimmer.prototype, "busy", void 0);
334
+ __decorate([
335
+ MapToComponentField({ writable: true })
336
+ ], LoginWorkflowSlimmer.prototype, "errorText", void 0);
337
+ __decorate([
338
+ MapToComponentField({ writable: true })
339
+ ], LoginWorkflowSlimmer.prototype, "methods", void 0);
340
+ __decorate([
341
+ MapToComponentField({ writable: true })
342
+ ], LoginWorkflowSlimmer.prototype, "otpMethodLabel", void 0);
343
+ __decorate([
344
+ MapToComponentField({ writable: true })
345
+ ], LoginWorkflowSlimmer.prototype, "destination", void 0);
346
+ __decorate([
347
+ MapToComponentField({ writable: true })
348
+ ], LoginWorkflowSlimmer.prototype, "unlockMinutes", void 0);
349
+ __decorate([
350
+ MapToComponentField({ writable: true })
351
+ ], LoginWorkflowSlimmer.prototype, "values", void 0);
352
+ __decorate([
353
+ MapToComponentMethod('componentWillLoad')
354
+ ], LoginWorkflowSlimmer.prototype, "start", null);
355
+ __decorate([
356
+ MapToComponentMethod('reset')
357
+ ], LoginWorkflowSlimmer.prototype, "restart", null);
@@ -0,0 +1,100 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
4
+ r = Reflect.decorate(decorators, target, key, desc);
5
+ else
6
+ for (var i = decorators.length - 1; i >= 0; i--)
7
+ if (d = decorators[i])
8
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
9
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
10
+ };
11
+ import { h } from "@stencil/core";
12
+ import { UdsSlimmer, InjectSlimmer, MapToComponentField } from "../../../../slimmer";
13
+ import { LoginStepRenderer } from "./login-step-renderer";
14
+ /**
15
+ * Renders the card's body — heading, description, error, form and the terminal
16
+ * outcome — and delegates the step's own controls to {@link LoginStepRenderer}.
17
+ *
18
+ * ## Why a renderer slimmer, and not the `view/` modules this replaces
19
+ *
20
+ * This is the same Slimmer mechanism the stateful layer already uses, pointed
21
+ * at rendering: one class, one responsibility, bound to the component by field
22
+ * mapping rather than by parameter passing. Vega applies the convention the
23
+ * same way — `slimmers/renderers/<name>-renderer.tsx`, a class ending in
24
+ * `Renderer`, exposing `render()` — so a reader moving between the design
25
+ * system and the pattern library meets one structure, not two.
26
+ *
27
+ * The gain over free functions is the binding. A free function has to be handed
28
+ * everything it needs on each call, so its signature grows with its appetite
29
+ * and callers thread arguments through layers that never use them — which is
30
+ * exactly what the old `StepViewModel` parameter had become.
31
+ * `@MapToComponentField()` reaches the component's `viewModel` and `handlers`
32
+ * directly, and types them by indexing the component class, so a renderer
33
+ * cannot drift out of step with what the component actually exposes: rename the
34
+ * getter and this stops compiling.
35
+ *
36
+ * Renderers only read. Every mutation goes back through `handlers`, into the
37
+ * workflow slimmer, and through the control strategy's checkpoints.
38
+ *
39
+ * ## What stays behind in the component
40
+ *
41
+ * `render()` itself and both `<slot>` elements remain in `UdsPatternLoginCard`. Stencil
42
+ * reads slots out of the component's source statically when it compiles, so a
43
+ * slot that only existed inside a slimmer would never be registered.
44
+ */
45
+ export class LoginCardRenderer extends UdsSlimmer {
46
+ constructor() {
47
+ super();
48
+ this.steps = new LoginStepRenderer();
49
+ }
50
+ render() {
51
+ const vm = this.viewModel;
52
+ return [
53
+ h("h1", { class: "card__heading", id: "uds-pattern-login-heading", tabindex: "-1" }, vm.heading),
54
+ ...(vm.description ? [h("p", { class: "card__description" }, vm.description)] : []),
55
+ // `vega-banner` carries the design system's error presentation — icon,
56
+ // colour, contrast and live-region semantics — so the pattern does not
57
+ // maintain a parallel version that can drift away from it.
58
+ ...(vm.errorText
59
+ ? [h("vega-banner", { class: "alert", type: "error", content: vm.errorText, isOpen: true, showCloseButton: false })]
60
+ : []),
61
+ ...(vm.isTerminal ? this.terminalBody(vm) : [this.form()]),
62
+ ];
63
+ }
64
+ form() {
65
+ return (
66
+ // `vega-form` rather than a native `<form>`: a `vega-button[type=submit]`
67
+ // lives in its own shadow root, so it cannot submit a native ancestor
68
+ // form — the click would silently do nothing. Vega's fields and buttons
69
+ // coordinate submission and validation through this element instead.
70
+ //
71
+ // It is also the validation gate. Its submit handler awaits `valid()`
72
+ // across every field it controls and only emits `vegaFormSubmit` once
73
+ // they all pass, so the step below never has to pre-check anything.
74
+ //
75
+ // `disableBlurValidation` suppresses validation on blur specifically.
76
+ // Without it, clicking Submit blurs the field underneath it, an error
77
+ // message appears, the button moves down and the click lands on whatever
78
+ // took its place — so the first click is swallowed. Errors still appear
79
+ // on submit, and still clear as the user types.
80
+ h("vega-form", { class: "form", useNativeForm: true, disableBlurValidation: true, onVegaFormSubmit: (event) => this.handlers.onSubmit(event) }, h("div", { class: "form__body" }, this.steps.render())));
81
+ }
82
+ /** Terminal steps render no form; `check-email` still offers a way back. */
83
+ terminalBody(vm) {
84
+ if (vm.step !== 'check-email') {
85
+ return [];
86
+ }
87
+ return [
88
+ h("p", { class: "prose" }, h("vega-button-link", { class: "link", label: vm.copy.back, eventful: true, onVegaClick: (event) => this.handlers.onNavigate('identifier')(event) })),
89
+ ];
90
+ }
91
+ }
92
+ __decorate([
93
+ InjectSlimmer()
94
+ ], LoginCardRenderer.prototype, "steps", void 0);
95
+ __decorate([
96
+ MapToComponentField()
97
+ ], LoginCardRenderer.prototype, "viewModel", void 0);
98
+ __decorate([
99
+ MapToComponentField()
100
+ ], LoginCardRenderer.prototype, "handlers", void 0);
@@ -0,0 +1,129 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
4
+ r = Reflect.decorate(decorators, target, key, desc);
5
+ else
6
+ for (var i = decorators.length - 1; i >= 0; i--)
7
+ if (d = decorators[i])
8
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
9
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
10
+ };
11
+ import { h } from "@stencil/core";
12
+ import { UdsSlimmer, MapToComponentField } from "../../../../slimmer";
13
+ import { CompleteCodeRule, MatchesFieldRule } from "../../validation/login-validation-rules";
14
+ import { formatLabel } from "../../../../utils/labels";
15
+ /**
16
+ * Renders the individual form controls, composed from Vega design-system
17
+ * elements rather than raw HTML.
18
+ *
19
+ * ## Why these are Vega elements and not our own markup
20
+ *
21
+ * A pattern's job is workflow choreography; the look, the focus ring, the label
22
+ * association, the error presentation and the contrast ratios are the design
23
+ * system's job. Hand-rolling an `<input>` here would fork all of that: the DS
24
+ * could ship an accessibility fix to `vega-input` and this card would never
25
+ * receive it. Composing the DS element instead means the pattern inherits those
26
+ * guarantees — and keeps inheriting them.
27
+ *
28
+ * It is the same principle that led us to withdraw `::part` (ADR 0003), applied
29
+ * from the inside: we do not override design-system guardrails, and we do not
30
+ * quietly re-implement them either.
31
+ *
32
+ * Note how much behaviour this removes. `vega-input` carries its own password
33
+ * reveal affordance, so the card no longer tracks a `revealSecret` flag or
34
+ * renders an eye icon.
35
+ */
36
+ export class LoginFieldRenderer extends UdsSlimmer {
37
+ constructor() {
38
+ super();
39
+ /**
40
+ * Rule instances are built once and kept.
41
+ *
42
+ * `validationRules` is a watched Vega prop, so handing it a freshly built
43
+ * array on every render would re-register the rules each time any unrelated
44
+ * state moved. These close over `this.viewModel`, which the slimmer re-reads
45
+ * from the component, so a single stable instance always evaluates against
46
+ * current state.
47
+ */
48
+ this.matchRules = new Map();
49
+ this.completeCode = [
50
+ new CompleteCodeRule(() => this.viewModel.otpLength, count => formatLabel(this.viewModel.copy.otpIncomplete, { count })),
51
+ ];
52
+ }
53
+ /** One rule per pairing, memoised so the array identity is stable per field. */
54
+ matchRuleFor(sibling) {
55
+ let rule = this.matchRules.get(sibling);
56
+ if (!rule) {
57
+ rule = [
58
+ new MatchesFieldRule(() => this.viewModel.valueOf(sibling), () => this.viewModel.copy.errors.passwords_do_not_match),
59
+ ];
60
+ this.matchRules.set(sibling, rule);
61
+ }
62
+ return rule;
63
+ }
64
+ /** A labelled text input bound to a collected value. */
65
+ textField(options) {
66
+ const vm = this.viewModel;
67
+ const { name, label, type = 'text', autocomplete, mustMatch } = options;
68
+ return (h("vega-input", { key: name, class: "field", name: name, label: label, type: type, autocomplete: autocomplete, value: vm.valueOf(name), disabled: vm.busy, showClearIcon: false,
69
+ // `required` and `type="email"` are the whole of this field's
70
+ // validation: Vega derives the rules, the messages and the error
71
+ // presentation from them, and `vega-form` will not submit until they
72
+ // pass. `autoValidation` is what keeps the form's own `isValid` current
73
+ // between submits, and lets an error clear as the user corrects it.
74
+ required: true, autoValidation: true, validationRules: mustMatch ? this.matchRuleFor(mustMatch) : undefined, onVegaChange: (event) => this.handlers.onValueChange(name, event.detail) }));
75
+ }
76
+ /** The passcode entry for the OTP step. */
77
+ passcodeField() {
78
+ const vm = this.viewModel;
79
+ return (h("vega-input-passcode", { class: "field", name: "code", label: vm.copy.otpFieldLabel, length: vm.otpLength, inputType: "numeric", disabled: vm.busy,
80
+ // `required` only asks whether the value is non-empty, so a half-typed
81
+ // code would pass it; `CompleteCodeRule` is what holds the submit until
82
+ // every box is filled.
83
+ required: true, autoValidation: true, validationRules: this.completeCode, value: vm.valueOf('code'), onVegaChange: (event) => this.handlers.onValueChange('code', event.detail) }));
84
+ }
85
+ /** A checkbox bound to a collected boolean. */
86
+ checkbox(name, label) {
87
+ const vm = this.viewModel;
88
+ return (h("vega-checkbox", { class: "field", name: name, identifier: name, checked: vm.isChecked(name), disabled: vm.busy, onVegaUserChange: (event) => this.handlers.onValueChange(name, event.detail !== false) }, label));
89
+ }
90
+ /** The step's primary action. */
91
+ submit(label) {
92
+ const vm = this.viewModel;
93
+ return h("vega-button", { class: "submit", type: "submit", variant: "primary", block: true, label: label, loading: vm.busy });
94
+ }
95
+ /**
96
+ * An inline textual action.
97
+ *
98
+ * `eventful` stops `vega-button-link` from navigating on its own so the
99
+ * workflow — not the browser — decides what the click means.
100
+ */
101
+ link(label, onClick) {
102
+ return (h("vega-button-link", { class: "link", label: label, eventful: true, disabled: this.viewModel.busy, onVegaClick: (event) => onClick(event) }));
103
+ }
104
+ /**
105
+ * The MFA method chooser, as a design-system selection-tile group.
106
+ *
107
+ * The group — not the individual tiles — is the component Vega expects to be
108
+ * composed: it owns the radio `name`, the selected `value`, the change event
109
+ * and the group's accessible labelling. Rendering loose `vega-selection-tile`
110
+ * elements inside a plain `role="radiogroup"` div looked correct and even
111
+ * behaved correctly, but threw inside Vega's runtime when the tiles
112
+ * unmounted, because each tile reaches for a group ancestor that was not
113
+ * there.
114
+ */
115
+ methodGroup(methods, label) {
116
+ const vm = this.viewModel;
117
+ return (h("vega-selection-tile-group", { class: "options", name: "mfaMethod", label: label, selectType: "radio", layout: "vertical", disabled: vm.busy,
118
+ // No `autoValidation` counterpart on the group — a tile selection has
119
+ // no intermediate state to validate against, so the submit gate is
120
+ // the only moment the rule can fire.
121
+ required: true, value: vm.valueOf('mfaMethod'), onVegaChange: (event) => this.handlers.onValueChange('mfaMethod', event.detail) }, methods.map(method => (h("vega-selection-tile", { key: method.id, class: "option", value: method.id, titleText: method.label, note: method.description, selectType: "radio", checked: vm.valueOf('mfaMethod') === method.id, disabled: method.disabled === true || vm.busy })))));
122
+ }
123
+ }
124
+ __decorate([
125
+ MapToComponentField()
126
+ ], LoginFieldRenderer.prototype, "viewModel", void 0);
127
+ __decorate([
128
+ MapToComponentField()
129
+ ], LoginFieldRenderer.prototype, "handlers", void 0);