@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,1662 @@
1
+ 'use strict';
2
+
3
+ var index = require('./index-Dzm6P89U.js');
4
+ var controlStrategy = require('./control-strategy-DhhYTbse.js');
5
+
6
+ /**
7
+ * Base class for every slimmer.
8
+ *
9
+ * A slimmer owns exactly one responsibility lifted out of a component class —
10
+ * the workflow machine, OTP input behaviour, a throttle timer. It reaches back
11
+ * into its host component only through the two mapping decorators, which keeps
12
+ * the coupling declarative and greppable.
13
+ */
14
+ class UdsSlimmer {
15
+ }
16
+
17
+ /** Narrows the `Object` a decorator is handed to something indexable. */
18
+ const asBag = (target) => target;
19
+
20
+ /**
21
+ * Internal markers used to attach slimmer wiring to component and slimmer
22
+ * instances without colliding with any real field name.
23
+ */
24
+ /** On a slimmer prototype: the list of component methods it hooks into. */
25
+ const SlimmerMappedMethod = Symbol('SlimmerMappedMethod');
26
+ /** On a slimmer instance: the component that injected it. */
27
+ const SlimmerConsumedBy = Symbol('SlimmerConsumedBy');
28
+ /** On a component instance: the slimmers it has injected, keyed by field name. */
29
+ const ConsumerSlimmers = Symbol('ConsumerSlimmers');
30
+
31
+ /**
32
+ * Marks a component field as a slimmer injection point.
33
+ *
34
+ * ```ts
35
+ * class UdsPatternLoginCard {
36
+ * @InjectSlimmer() private workflow = new WorkflowSlimmer();
37
+ * }
38
+ * ```
39
+ *
40
+ * The decorator replaces the field with an accessor pair, so assigning the
41
+ * instance in the field initialiser is what performs the injection: the slimmer
42
+ * is handed a back-reference to the component and any `@MapToComponentMethod`
43
+ * hooks it declares are attached.
44
+ *
45
+ * Note this relies on field initialisers compiling to assignment rather than
46
+ * `Object.defineProperty` — i.e. `useDefineForClassFields` must be off, which is
47
+ * the default below an ES2022 target. With define semantics the initialiser
48
+ * would shadow this accessor on the instance and nothing would be wired up.
49
+ */
50
+ function InjectSlimmer() {
51
+ return function (componentPrototype, propertyKey) {
52
+ Object.defineProperty(componentPrototype, propertyKey, {
53
+ set: createSlimmerFieldSetter(propertyKey),
54
+ get: createSlimmerFieldGetter(propertyKey),
55
+ configurable: true,
56
+ });
57
+ };
58
+ }
59
+ function createSlimmerFieldSetter(fieldName) {
60
+ return function (slimmer) {
61
+ if (!this[ConsumerSlimmers]) {
62
+ this[ConsumerSlimmers] = new Map();
63
+ }
64
+ this[ConsumerSlimmers].set(fieldName, slimmer);
65
+ asBag(slimmer)[SlimmerConsumedBy] = this;
66
+ registerSlimmerMethods(slimmer, this);
67
+ };
68
+ }
69
+ function createSlimmerFieldGetter(fieldName) {
70
+ return function () {
71
+ return this[ConsumerSlimmers]?.get(fieldName);
72
+ };
73
+ }
74
+ /** Attaches every `@MapToComponentMethod` hook the slimmer declares. */
75
+ function registerSlimmerMethods(slimmer, component) {
76
+ const slimmerPrototype = asBag(Object.getPrototypeOf(slimmer));
77
+ const entries = slimmerPrototype[SlimmerMappedMethod];
78
+ if (!entries) {
79
+ return;
80
+ }
81
+ for (const entry of entries) {
82
+ component[entry.componentMethodName] = chainSlimmerMethod(component, slimmer, component[entry.componentMethodName], entry);
83
+ }
84
+ }
85
+ /**
86
+ * Appends the slimmer method to the component method, preserving any hook that
87
+ * an earlier slimmer already attached so several slimmers can share a target.
88
+ *
89
+ * When the component method returns a promise the slimmer method is chained
90
+ * onto it, so an async `componentWillLoad` still completes in order.
91
+ */
92
+ function chainSlimmerMethod(component, slimmer, originalMethod, mapEntry) {
93
+ return function (...args) {
94
+ const result = originalMethod?.apply(component, args);
95
+ const slimmerMethod = asBag(slimmer)[mapEntry.slimmerMethodName];
96
+ if (result && typeof result.then === 'function') {
97
+ return result.then(() => slimmerMethod.apply(slimmer, args));
98
+ }
99
+ return slimmerMethod.apply(slimmer, args);
100
+ };
101
+ }
102
+
103
+ /** Console wrapper carrying a GP prefix, so framework diagnostics are traceable. */
104
+ class LogUtility {
105
+ static log(...message) {
106
+ console.log(`%c${this.PREFIX}`, this.PREFIX_STYLE, ...message);
107
+ }
108
+ static warn(...message) {
109
+ console.warn(`%c${this.PREFIX}`, this.PREFIX_STYLE, ...message);
110
+ }
111
+ static error(...message) {
112
+ console.error(`%c${this.PREFIX}`, this.PREFIX_STYLE, ...message);
113
+ }
114
+ }
115
+ LogUtility.PREFIX = '[GP]';
116
+ LogUtility.PREFIX_STYLE = 'font-weight: bold';
117
+
118
+ /**
119
+ * Binds a slimmer field to a field on the component that injected it:
120
+ *
121
+ * - reading the slimmer field returns the component's current value;
122
+ * - writing it updates the component (only when `writable` is set).
123
+ *
124
+ * The slimmer therefore holds no copy of the state. That matters inside a
125
+ * Stencil component, where the component's `@State()` fields are the values the
126
+ * renderer actually observes — a slimmer-local duplicate would drift and would
127
+ * not trigger re-renders.
128
+ *
129
+ * The slimmer field name is used as the component field name unless
130
+ * `componentFieldName` says otherwise.
131
+ */
132
+ function MapToComponentField(options) {
133
+ return function (slimmerPrototype, propertyKey) {
134
+ Object.defineProperty(slimmerPrototype, propertyKey, {
135
+ get: createMappedFieldGetter(propertyKey, options),
136
+ set: createMappedFieldSetter(propertyKey, options),
137
+ configurable: true,
138
+ });
139
+ };
140
+ }
141
+ function createMappedFieldSetter(slimmerFieldName, options = {}) {
142
+ return function (value) {
143
+ if (!options.writable) {
144
+ LogUtility.error(`${String(slimmerFieldName)} is not writable`);
145
+ return;
146
+ }
147
+ const component = this[SlimmerConsumedBy];
148
+ component[options.componentFieldName ?? slimmerFieldName] = value;
149
+ };
150
+ }
151
+ function createMappedFieldGetter(slimmerFieldName, options = {}) {
152
+ return function () {
153
+ const component = this[SlimmerConsumedBy];
154
+ return component[options.componentFieldName ?? slimmerFieldName];
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Hooks a slimmer method onto a component method of the given name.
160
+ *
161
+ * When the component method runs, the slimmer method runs after it and its
162
+ * return value becomes the result. The hook is one-way: calling the slimmer
163
+ * method directly does not invoke the component's.
164
+ *
165
+ * Typical targets are Stencil lifecycle methods (`componentWillLoad`,
166
+ * `disconnectedCallback`), `@Watch` handlers, and `@Method()` members.
167
+ */
168
+ function MapToComponentMethod(componentMethodName) {
169
+ return function (slimmerPrototype, propertyKey) {
170
+ const bag = asBag(slimmerPrototype);
171
+ initMappedMethodList(bag);
172
+ bag[SlimmerMappedMethod].push({
173
+ componentMethodName,
174
+ slimmerMethodName: String(propertyKey),
175
+ });
176
+ };
177
+ }
178
+ /**
179
+ * Gives each prototype in an inheritance chain its own copy of the list, seeded
180
+ * from the parent's. Without the `hasOwnProperty` check a subclass would push
181
+ * onto the parent's array and every sibling would inherit its hooks.
182
+ */
183
+ function initMappedMethodList(slimmerPrototype) {
184
+ if (!Object.prototype.hasOwnProperty.call(slimmerPrototype, SlimmerMappedMethod)) {
185
+ const inherited = slimmerPrototype[SlimmerMappedMethod];
186
+ slimmerPrototype[SlimmerMappedMethod] = inherited ? [...inherited] : [];
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Runtime validation for the one boundary where data genuinely enters the
192
+ * pattern from outside: a provider's `UdsAuthResult`.
193
+ *
194
+ * ## Why validate here and nowhere else
195
+ *
196
+ * `UdsAuthResult` is a *DTO* in the strict sense — it crosses a trust boundary.
197
+ * An adapter is written by a consuming team, usually by mapping a vendor SDK's
198
+ * response, and TypeScript's guarantees stop at the seam: a compiled adapter
199
+ * can still hand back `{ status: 'otp_requred' }` from a mistyped mapping, or
200
+ * `{ status: 'mfa_required' }` with no `methods` because the vendor returned
201
+ * `null`.
202
+ *
203
+ * Unvalidated, those land as *silence*. The workflow's `switch` falls through,
204
+ * the card stays on its current step, no error is raised, and the user sees a
205
+ * button that does nothing. Debugging that means reading our source. Validated,
206
+ * the same mistake names itself at the point it was made.
207
+ *
208
+ * The view model deliberately has no equivalent: it never crosses a boundary,
209
+ * only moves between layers that are compiled together, so checking it at
210
+ * runtime would buy nothing the compiler has not already proven.
211
+ *
212
+ * ## Failure policy
213
+ *
214
+ * A malformed result is converted into an `unknown` error — the user always
215
+ * sees approved copy — and reported on the console with the offending value.
216
+ * The pattern never throws into a consumer's promise chain for this.
217
+ */
218
+ const RESULT_STATUSES = [
219
+ 'success',
220
+ 'password_required',
221
+ 'mfa_required',
222
+ 'otp_required',
223
+ 'password_reset_required',
224
+ 'reset_link_sent',
225
+ 'username_sent',
226
+ 'locked',
227
+ 'error',
228
+ ];
229
+ const ERROR_CODES = [
230
+ 'invalid_credentials',
231
+ 'invalid_identifier',
232
+ 'invalid_code',
233
+ 'code_expired',
234
+ 'account_locked',
235
+ 'account_not_found',
236
+ 'account_exists',
237
+ 'password_policy',
238
+ 'passwords_do_not_match',
239
+ 'rate_limited',
240
+ 'network',
241
+ 'unknown',
242
+ ];
243
+ /** Thrown only internally; never surfaces to a consumer. */
244
+ class InvalidAuthResultError extends Error {
245
+ }
246
+ /**
247
+ * Validates a provider's response, returning it narrowed to `UdsAuthResult`.
248
+ *
249
+ * @param value what the adapter returned
250
+ * @param source the provider method called, for the diagnostic message
251
+ */
252
+ function parseAuthResult(value, source) {
253
+ try {
254
+ return assertAuthResult(value, source);
255
+ }
256
+ catch (error) {
257
+ if (!(error instanceof InvalidAuthResultError)) {
258
+ throw error;
259
+ }
260
+ // eslint-disable-next-line no-console
261
+ console.error(`[uds-pattern-login-card] ${error.message}`, value);
262
+ return { status: 'error', code: 'unknown' };
263
+ }
264
+ }
265
+ function assertAuthResult(value, source) {
266
+ const result = value;
267
+ if (!result || typeof result !== 'object') {
268
+ throw new InvalidAuthResultError(`${source} resolved with ${describe(value)}, expected a UdsAuthResult object.`);
269
+ }
270
+ const status = result.status;
271
+ if (!RESULT_STATUSES.includes(status)) {
272
+ throw new InvalidAuthResultError(`${source} resolved with status ${describe(status)}. Expected one of: ${RESULT_STATUSES.join(', ')}.`);
273
+ }
274
+ switch (status) {
275
+ case 'mfa_required':
276
+ assertMethods(result.methods, source);
277
+ break;
278
+ case 'otp_required':
279
+ if (result.method !== undefined) {
280
+ assertMethod(result.method, source, 'method');
281
+ }
282
+ assertOptionalNumber(result.codeLength, source, 'codeLength');
283
+ assertOptionalNumber(result.resendAfterMs, source, 'resendAfterMs');
284
+ break;
285
+ case 'locked':
286
+ assertOptionalNumber(result.unlockAfterMs, source, 'unlockAfterMs');
287
+ break;
288
+ case 'error':
289
+ if (!ERROR_CODES.includes(result.code)) {
290
+ throw new InvalidAuthResultError(`${source} resolved with error code ${describe(result.code)}. Expected one of: ${ERROR_CODES.join(', ')}.`);
291
+ }
292
+ break;
293
+ }
294
+ return result;
295
+ }
296
+ function assertMethods(value, source) {
297
+ if (!Array.isArray(value) || value.length === 0) {
298
+ throw new InvalidAuthResultError(`${source} resolved with status 'mfa_required' but methods was ${describe(value)}. ` +
299
+ 'Expected a non-empty array of UdsMfaMethod — the card has nothing to offer the user otherwise.');
300
+ }
301
+ value.forEach((method, index) => assertMethod(method, source, `methods[${index}]`));
302
+ }
303
+ function assertMethod(value, source, path) {
304
+ const method = value;
305
+ if (!method || typeof method !== 'object' || typeof method.id !== 'string' || typeof method.label !== 'string') {
306
+ throw new InvalidAuthResultError(`${source} resolved with ${path} = ${describe(value)}. Expected { id: string, label: string }.`);
307
+ }
308
+ }
309
+ function assertOptionalNumber(value, source, path) {
310
+ if (value !== undefined && (typeof value !== 'number' || !Number.isFinite(value))) {
311
+ throw new InvalidAuthResultError(`${source} resolved with ${path} = ${describe(value)}. Expected a finite number or undefined.`);
312
+ }
313
+ }
314
+ function describe(value) {
315
+ if (value === null)
316
+ return 'null';
317
+ if (value === undefined)
318
+ return 'undefined';
319
+ if (typeof value === 'string')
320
+ return `'${value}'`;
321
+ if (Array.isArray(value))
322
+ return `an array of ${value.length}`;
323
+ return typeof value === 'object' ? 'an object' : String(value);
324
+ }
325
+
326
+ var __decorate$6 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
327
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
328
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
329
+ r = Reflect.decorate(decorators, target, key, desc);
330
+ else
331
+ for (var i = decorators.length - 1; i >= 0; i--)
332
+ if (d = decorators[i])
333
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
334
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
335
+ };
336
+ /**
337
+ * The login state machine.
338
+ *
339
+ * This is the pattern's core asset: the translation from "what the identity
340
+ * platform needs next" into "what the user sees next". Keeping it in one class,
341
+ * away from markup and input plumbing, is what makes the sequencing reviewable.
342
+ *
343
+ * Every decision it makes passes through the host's `controlStrategy` first, so
344
+ * a consumer can redirect the flow without reimplementing it.
345
+ */
346
+ class LoginWorkflowSlimmer extends UdsSlimmer {
347
+ constructor() {
348
+ super();
349
+ /** The identifier being authenticated, carried across steps. */
350
+ this.pendingIdentifier = '';
351
+ }
352
+ // --- Lifecycle ----------------------------------------------------------
353
+ start() {
354
+ this.step = this.initialStep;
355
+ this.pendingIdentifier = this.memory.initialIdentifier();
356
+ this.resetStepState();
357
+ }
358
+ /** Returns the workflow to its first step and clears all collected input. */
359
+ async restart() {
360
+ this.methods = [];
361
+ this.errorText = undefined;
362
+ this.destination = undefined;
363
+ this.unlockMinutes = undefined;
364
+ this.otpCount = undefined;
365
+ this.resend.stop();
366
+ this.applyTransition(controlStrategy.goTo(this.initialStep), 'reset');
367
+ }
368
+ // --- Capabilities -------------------------------------------------------
369
+ /**
370
+ * Which affordances to render. The presence of an optional provider method
371
+ * *is* the configuration, so a route can never be offered for something the
372
+ * backend cannot service.
373
+ */
374
+ get capabilities() {
375
+ const a = this.auth;
376
+ return {
377
+ signUp: typeof a?.signUp === 'function',
378
+ reset: typeof a?.requestPasswordReset === 'function',
379
+ username: typeof a?.requestUsernameReminder === 'function',
380
+ resend: typeof a?.resendOtp === 'function',
381
+ };
382
+ }
383
+ // --- Navigation ---------------------------------------------------------
384
+ /** In-workflow navigation, triggered by a link in the card. */
385
+ navigateTo(step) {
386
+ this.errorText = undefined;
387
+ this.applyTransition(controlStrategy.goTo(step), 'link');
388
+ }
389
+ /**
390
+ * Performs a transition, giving the strategy its say on every step change —
391
+ * including the ones the workflow initiates for itself.
392
+ */
393
+ applyTransition(transition, cause) {
394
+ if (transition.type === 'stay') {
395
+ return;
396
+ }
397
+ if (transition.type === 'fail') {
398
+ this.showError(transition.code, transition.message);
399
+ return;
400
+ }
401
+ const decided = controlStrategy.intercept(this.controlStrategy?.navigate, { from: this.step, to: transition.step, cause }, () => transition);
402
+ // A strategy may answer a navigation with a different kind of outcome
403
+ // entirely — stay put, or surface a failure instead of moving.
404
+ if (decided.type === 'stay') {
405
+ return;
406
+ }
407
+ if (decided.type === 'fail') {
408
+ this.showError(decided.code, decided.message);
409
+ return;
410
+ }
411
+ this.setStep(decided.step);
412
+ }
413
+ setStep(next) {
414
+ const previousStep = this.step;
415
+ this.step = next;
416
+ this.resetStepState();
417
+ if (previousStep !== next) {
418
+ this.udsLoginStepChange.emit({ step: next, previousStep });
419
+ }
420
+ }
421
+ /** Clears per-step input, seeding the identifier field where appropriate. */
422
+ resetStepState() {
423
+ const seedsIdentifier = this.step === 'identifier' || this.step === 'forgot-password';
424
+ this.values = seedsIdentifier && this.pendingIdentifier ? { identifier: this.pendingIdentifier } : {};
425
+ }
426
+ /** How many characters the passcode entry should render. */
427
+ get codeLength() {
428
+ return Math.max(1, this.otpCount ?? this.otpLength);
429
+ }
430
+ // --- Result translation -------------------------------------------------
431
+ /**
432
+ * Translates a provider result into the next step. This inversion is what
433
+ * makes the pattern a pattern: the identity platform reports what it needs,
434
+ * and the card — not the consumer — decides what that looks like.
435
+ */
436
+ applyResult(result) {
437
+ this.errorText = undefined;
438
+ const transition = controlStrategy.intercept(this.controlStrategy?.transition, { result, from: this.step }, () => this.defaultTransitionFor(result));
439
+ // Side effects that belong to the result rather than the destination are
440
+ // staged before the move, so the step renders with its data already in place.
441
+ this.stageResultState(result);
442
+ if (result.status === 'mfa_required' && this.methods.length === 1 && transition.type === 'stay') {
443
+ // A single option is not a choice — skip the picker.
444
+ void this.selectMfaMethod(this.methods[0].id);
445
+ return;
446
+ }
447
+ this.applyTransition(transition, 'auto');
448
+ if (result.status === 'success' && transition.type === 'goto' && transition.step === 'success') {
449
+ controlStrategy.intercept(this.controlStrategy?.complete, { session: result.session }, () => this.udsLoginSuccess.emit({ session: result.session }));
450
+ }
451
+ }
452
+ defaultTransitionFor(result) {
453
+ switch (result.status) {
454
+ case 'success':
455
+ return controlStrategy.goTo('success');
456
+ case 'password_required':
457
+ return controlStrategy.goTo('password');
458
+ case 'mfa_required':
459
+ // Staying signals "the picker is not needed"; `applyResult` turns that
460
+ // into an auto-selection when the provider offered exactly one method.
461
+ return result.methods.length === 1 ? controlStrategy.stay() : controlStrategy.goTo('mfa-method');
462
+ case 'otp_required':
463
+ return controlStrategy.goTo('otp');
464
+ case 'password_reset_required':
465
+ return controlStrategy.goTo('reset-password');
466
+ case 'reset_link_sent':
467
+ case 'username_sent':
468
+ return controlStrategy.goTo('check-email');
469
+ case 'locked':
470
+ return controlStrategy.goTo('locked');
471
+ case 'error':
472
+ return controlStrategy.fail(result.code, result.message);
473
+ }
474
+ }
475
+ /** Captures the data a result carries, independent of where the flow goes. */
476
+ stageResultState(result) {
477
+ switch (result.status) {
478
+ case 'mfa_required':
479
+ this.methods = result.methods;
480
+ return;
481
+ case 'otp_required':
482
+ this.otpMethodLabel = result.method?.label;
483
+ this.otpCount = result.codeLength ?? this.otpLength;
484
+ this.resend.start(result.resendAfterMs);
485
+ return;
486
+ case 'reset_link_sent':
487
+ case 'username_sent':
488
+ this.destination = result.destination;
489
+ return;
490
+ case 'locked':
491
+ this.unlockMinutes = result.unlockAfterMs ? Math.ceil(result.unlockAfterMs / 60000) : undefined;
492
+ return;
493
+ default:
494
+ return;
495
+ }
496
+ }
497
+ // --- Failures -----------------------------------------------------------
498
+ showError(code, message) {
499
+ const outcome = controlStrategy.intercept(this.controlStrategy?.failure, { code, step: this.step, message }, () => ({
500
+ text: this.copy.errors[code] ?? this.copy.errors.unknown,
501
+ }));
502
+ this.errorText = outcome.text;
503
+ this.udsLoginError.emit({ code, step: this.step, message });
504
+ if (outcome.transition) {
505
+ this.applyTransition(outcome.transition, 'auto');
506
+ }
507
+ }
508
+ // --- Provider calls -----------------------------------------------------
509
+ /**
510
+ * Wraps a provider call with the in-flight guard and the network catch.
511
+ *
512
+ * `busy` is released *before* the result is applied, because applying a
513
+ * result may legitimately chain another provider call (auto-selecting a lone
514
+ * MFA method, for example). Holding the guard across `applyResult` would make
515
+ * that continuation a no-op and strand the user on the previous step. The
516
+ * chained call re-acquires the guard synchronously, so no un-busy frame is
517
+ * ever painted.
518
+ *
519
+ * Every provider response is validated here, at the one point where data
520
+ * crosses into the pattern from a consumer's adapter. See `parseAuthResult`.
521
+ */
522
+ async run(call, source) {
523
+ if (this.busy) {
524
+ return;
525
+ }
526
+ this.busy = true;
527
+ let result;
528
+ try {
529
+ result = parseAuthResult(await call(), `auth.${source}`);
530
+ }
531
+ catch {
532
+ this.busy = false;
533
+ this.showError('network');
534
+ return;
535
+ }
536
+ this.busy = false;
537
+ this.applyResult(result);
538
+ }
539
+ async selectMfaMethod(methodId) {
540
+ if (!this.auth?.selectMfaMethod) {
541
+ this.showError('unknown');
542
+ return;
543
+ }
544
+ await this.run(() => this.auth.selectMfaMethod({ methodId }), 'selectMfaMethod');
545
+ }
546
+ async resendCode() {
547
+ if (!this.resend.canResend || !this.auth?.resendOtp) {
548
+ return;
549
+ }
550
+ await this.run(() => this.auth.resendOtp(), 'resendOtp');
551
+ }
552
+ // --- Submission ---------------------------------------------------------
553
+ /**
554
+ * Submits the current step.
555
+ *
556
+ * The strategy wraps the whole provider call, so a consumer can gate it
557
+ * (CAPTCHA, device check, consent) or answer it outright without the pattern
558
+ * needing a dedicated prop for each case.
559
+ */
560
+ async submit() {
561
+ if (this.busy) {
562
+ return;
563
+ }
564
+ const step = this.step;
565
+ const values = { ...this.values };
566
+ this.rememberIdentifierIfRequested(step);
567
+ await this.run(() => controlStrategy.intercept(this.controlStrategy?.submit, { step, values, identifier: this.pendingIdentifier }, () => this.callProvider(step)), `submit(${step})`);
568
+ }
569
+ rememberIdentifierIfRequested(step) {
570
+ if (step !== 'identifier') {
571
+ return;
572
+ }
573
+ const identifier = this.str('identifier').trim();
574
+ this.pendingIdentifier = identifier;
575
+ this.memory.remember(identifier, this.values.rememberIdentifier === true);
576
+ }
577
+ callProvider(step) {
578
+ const auth = this.auth;
579
+ if (!auth) {
580
+ return Promise.resolve({ status: 'error', code: 'unknown' });
581
+ }
582
+ const unsupported = Promise.resolve({ status: 'error', code: 'unknown' });
583
+ switch (step) {
584
+ case 'identifier':
585
+ return auth.signIn({
586
+ identifier: this.pendingIdentifier,
587
+ rememberIdentifier: this.values.rememberIdentifier === true,
588
+ });
589
+ case 'password': {
590
+ const password = this.str('password');
591
+ const identifier = this.pendingIdentifier;
592
+ return auth.verifyPassword
593
+ ? auth.verifyPassword({ identifier, password })
594
+ : auth.signIn({ identifier, password });
595
+ }
596
+ case 'mfa-method':
597
+ return auth.selectMfaMethod?.({ methodId: this.str('mfaMethod') }) ?? unsupported;
598
+ case 'otp':
599
+ return auth.verifyOtp?.({ code: this.str('code') }) ?? unsupported;
600
+ case 'forgot-password':
601
+ return auth.requestPasswordReset?.({ identifier: this.str('identifier').trim() }) ?? unsupported;
602
+ case 'forgot-username':
603
+ return auth.requestUsernameReminder?.({ email: this.str('email').trim() }) ?? unsupported;
604
+ case 'reset-password':
605
+ return auth.resetPassword?.({ password: this.str('password') }) ?? unsupported;
606
+ case 'sign-up':
607
+ return (auth.signUp?.({
608
+ firstName: this.str('firstName'),
609
+ lastName: this.str('lastName'),
610
+ identifier: this.str('identifier').trim(),
611
+ }) ?? unsupported);
612
+ default:
613
+ return unsupported;
614
+ }
615
+ }
616
+ str(name) {
617
+ return String(this.values[name] ?? '');
618
+ }
619
+ }
620
+ __decorate$6([
621
+ MapToComponentField()
622
+ ], LoginWorkflowSlimmer.prototype, "resend", void 0);
623
+ __decorate$6([
624
+ MapToComponentField()
625
+ ], LoginWorkflowSlimmer.prototype, "memory", void 0);
626
+ __decorate$6([
627
+ MapToComponentField()
628
+ ], LoginWorkflowSlimmer.prototype, "udsLoginStepChange", void 0);
629
+ __decorate$6([
630
+ MapToComponentField()
631
+ ], LoginWorkflowSlimmer.prototype, "udsLoginError", void 0);
632
+ __decorate$6([
633
+ MapToComponentField()
634
+ ], LoginWorkflowSlimmer.prototype, "udsLoginSuccess", void 0);
635
+ __decorate$6([
636
+ MapToComponentField()
637
+ ], LoginWorkflowSlimmer.prototype, "auth", void 0);
638
+ __decorate$6([
639
+ MapToComponentField()
640
+ ], LoginWorkflowSlimmer.prototype, "controlStrategy", void 0);
641
+ __decorate$6([
642
+ MapToComponentField()
643
+ ], LoginWorkflowSlimmer.prototype, "initialStep", void 0);
644
+ __decorate$6([
645
+ MapToComponentField()
646
+ ], LoginWorkflowSlimmer.prototype, "otpLength", void 0);
647
+ __decorate$6([
648
+ MapToComponentField()
649
+ ], LoginWorkflowSlimmer.prototype, "copy", void 0);
650
+ __decorate$6([
651
+ MapToComponentField({ writable: true })
652
+ ], LoginWorkflowSlimmer.prototype, "step", void 0);
653
+ __decorate$6([
654
+ MapToComponentField({ writable: true })
655
+ ], LoginWorkflowSlimmer.prototype, "busy", void 0);
656
+ __decorate$6([
657
+ MapToComponentField({ writable: true })
658
+ ], LoginWorkflowSlimmer.prototype, "errorText", void 0);
659
+ __decorate$6([
660
+ MapToComponentField({ writable: true })
661
+ ], LoginWorkflowSlimmer.prototype, "methods", void 0);
662
+ __decorate$6([
663
+ MapToComponentField({ writable: true })
664
+ ], LoginWorkflowSlimmer.prototype, "otpMethodLabel", void 0);
665
+ __decorate$6([
666
+ MapToComponentField({ writable: true })
667
+ ], LoginWorkflowSlimmer.prototype, "destination", void 0);
668
+ __decorate$6([
669
+ MapToComponentField({ writable: true })
670
+ ], LoginWorkflowSlimmer.prototype, "unlockMinutes", void 0);
671
+ __decorate$6([
672
+ MapToComponentField({ writable: true })
673
+ ], LoginWorkflowSlimmer.prototype, "values", void 0);
674
+ __decorate$6([
675
+ MapToComponentMethod('componentWillLoad')
676
+ ], LoginWorkflowSlimmer.prototype, "start", null);
677
+ __decorate$6([
678
+ MapToComponentMethod('reset')
679
+ ], LoginWorkflowSlimmer.prototype, "restart", null);
680
+
681
+ var __decorate$5 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
682
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
683
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
684
+ r = Reflect.decorate(decorators, target, key, desc);
685
+ else
686
+ for (var i = decorators.length - 1; i >= 0; i--)
687
+ if (d = decorators[i])
688
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
689
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
690
+ };
691
+ /**
692
+ * The resend throttle and its countdown.
693
+ *
694
+ * Small, but exactly the kind of thing that disappears into a large component
695
+ * and then gets subtly broken: an interval that must be cleared on disconnect,
696
+ * and a tick that must drive a re-render.
697
+ */
698
+ class ResendThrottleSlimmer extends UdsSlimmer {
699
+ constructor() {
700
+ super();
701
+ }
702
+ /** Begins a throttle window. A missing or non-positive duration clears it. */
703
+ start(resendAfterMs) {
704
+ this.stop();
705
+ if (!resendAfterMs || resendAfterMs <= 0) {
706
+ this.resendAt = undefined;
707
+ return;
708
+ }
709
+ this.resendAt = Date.now() + resendAfterMs;
710
+ this.nowTick = Date.now();
711
+ this.timer = setInterval(() => {
712
+ this.nowTick = Date.now();
713
+ if (this.resendAt !== undefined && this.nowTick >= this.resendAt) {
714
+ this.stop();
715
+ this.resendAt = undefined;
716
+ }
717
+ }, 1000);
718
+ }
719
+ stop() {
720
+ if (this.timer) {
721
+ clearInterval(this.timer);
722
+ this.timer = undefined;
723
+ }
724
+ }
725
+ /** Seconds left on the throttle; zero when resending is allowed. */
726
+ get secondsRemaining() {
727
+ if (this.resendAt === undefined) {
728
+ return 0;
729
+ }
730
+ return Math.max(0, Math.ceil((this.resendAt - this.nowTick) / 1000));
731
+ }
732
+ get canResend() {
733
+ return this.secondsRemaining === 0;
734
+ }
735
+ }
736
+ __decorate$5([
737
+ MapToComponentField({ writable: true })
738
+ ], ResendThrottleSlimmer.prototype, "resendAt", void 0);
739
+ __decorate$5([
740
+ MapToComponentField({ writable: true })
741
+ ], ResendThrottleSlimmer.prototype, "nowTick", void 0);
742
+ __decorate$5([
743
+ MapToComponentMethod('disconnectedCallback')
744
+ ], ResendThrottleSlimmer.prototype, "stop", null);
745
+
746
+ var __decorate$4 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
747
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
748
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
749
+ r = Reflect.decorate(decorators, target, key, desc);
750
+ else
751
+ for (var i = decorators.length - 1; i >= 0; i--)
752
+ if (d = decorators[i])
753
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
754
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
755
+ };
756
+ /**
757
+ * Persistence of the remembered identifier.
758
+ *
759
+ * Isolated because it is the one place the pattern touches storage, and storage
760
+ * can fail for reasons that have nothing to do with signing in — private
761
+ * browsing, blocked cookies, a strict enterprise policy. Those failures are
762
+ * swallowed deliberately: remembering an email address is a convenience, never
763
+ * a precondition for authenticating.
764
+ */
765
+ class IdentityMemorySlimmer extends UdsSlimmer {
766
+ constructor() {
767
+ super();
768
+ }
769
+ /** The identifier the card should start with. */
770
+ initialIdentifier() {
771
+ return this.identifier || this.read() || '';
772
+ }
773
+ read() {
774
+ if (!this.rememberStorageKey) {
775
+ return '';
776
+ }
777
+ try {
778
+ return globalThis.localStorage?.getItem(this.rememberStorageKey) ?? '';
779
+ }
780
+ catch {
781
+ return '';
782
+ }
783
+ }
784
+ /** Stores or clears the identifier according to the user's choice. */
785
+ remember(identifier, remember) {
786
+ if (!this.rememberStorageKey) {
787
+ return;
788
+ }
789
+ try {
790
+ if (remember) {
791
+ globalThis.localStorage?.setItem(this.rememberStorageKey, identifier);
792
+ }
793
+ else {
794
+ globalThis.localStorage?.removeItem(this.rememberStorageKey);
795
+ }
796
+ }
797
+ catch {
798
+ // See the class comment: persistence is best-effort by design.
799
+ }
800
+ }
801
+ }
802
+ __decorate$4([
803
+ MapToComponentField()
804
+ ], IdentityMemorySlimmer.prototype, "rememberStorageKey", void 0);
805
+ __decorate$4([
806
+ MapToComponentField()
807
+ ], IdentityMemorySlimmer.prototype, "identifier", void 0);
808
+
809
+ /**
810
+ * The pattern's own `vega-form` validation rules.
811
+ *
812
+ * ## Why rules rather than a pre-submit check
813
+ *
814
+ * `vega-form` already gates submission: its submit handler awaits `valid()` and
815
+ * only emits `vegaFormSubmit` when every field passes. Anything the pattern
816
+ * checks *outside* that gate is a second, competing validation pass — it fires
817
+ * at a different moment, reports through a different surface (a card-level
818
+ * banner rather than the field at fault), and has to be kept in step by hand.
819
+ *
820
+ * Expressing the same checks as rules puts them inside the one flow. The field
821
+ * that is wrong is the field that says so, the error clears as the user fixes
822
+ * it, and the submit gate needs no pattern-specific code at all.
823
+ *
824
+ * ## Why these two, and nothing else
825
+ *
826
+ * Everything Vega already derives from a prop — `required`, `type="email"`,
827
+ * `minLength` — is left to Vega, including its wording. Restating "this field
828
+ * is required" here would fork copy that the design system maintains for every
829
+ * Global Payments product. Only checks Vega cannot express from a prop alone
830
+ * live here: one that needs a sibling field's value, and one that needs the
831
+ * configured code length.
832
+ *
833
+ * ## Why they read through callbacks
834
+ *
835
+ * A rule is handed to `vega-input` as a prop and re-read when its identity
836
+ * changes, so rebuilding the array on each render would churn Vega's watchers
837
+ * every time any unrelated state moved. These are built once and resolve their
838
+ * inputs — the value to compare against, the message to show — when they are
839
+ * evaluated, so a single stable instance always sees current state.
840
+ */
841
+ /** Evaluated only once the field has a value; emptiness is `required`'s job. */
842
+ class NonEmptyStringRule {
843
+ canEvaluate(input, status) {
844
+ if (typeof input !== 'string' || input.length === 0) {
845
+ return false;
846
+ }
847
+ return status ? !status.disabled && status.visible : true;
848
+ }
849
+ /**
850
+ * Mirrors Vega's built-in rules: say nothing until the user has engaged with
851
+ * the field, so a freshly rendered step is not pre-littered with errors.
852
+ */
853
+ shouldShowError(status) {
854
+ return status ? !status.disabled && status.visible && (status.touched || status.modified) : true;
855
+ }
856
+ }
857
+ /**
858
+ * Requires a field to equal another field's current value.
859
+ *
860
+ * Used for the "confirm new password" pairing, which no single-field rule can
861
+ * express: the answer depends on a sibling that may change after this field was
862
+ * last touched.
863
+ */
864
+ class MatchesFieldRule extends NonEmptyStringRule {
865
+ constructor(expected, message) {
866
+ super();
867
+ this.expected = expected;
868
+ this.message = message;
869
+ }
870
+ evaluate(input) {
871
+ const isValid = input === this.expected();
872
+ return { isValid, message: isValid ? '' : this.message() };
873
+ }
874
+ }
875
+ /**
876
+ * Requires every character of a passcode to be present.
877
+ *
878
+ * `required` alone is not enough here. It only asks whether the value is
879
+ * non-empty, so a half-typed code — three boxes of six — satisfies it and
880
+ * reaches the provider, which then has to answer a question the pattern could
881
+ * have answered without a round trip.
882
+ */
883
+ class CompleteCodeRule extends NonEmptyStringRule {
884
+ constructor(length, message) {
885
+ super();
886
+ this.length = length;
887
+ this.message = message;
888
+ }
889
+ evaluate(input) {
890
+ const expected = this.length();
891
+ const isValid = input.length >= expected;
892
+ return { isValid, message: isValid ? '' : this.message(expected) };
893
+ }
894
+ }
895
+
896
+ var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
897
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
898
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
899
+ r = Reflect.decorate(decorators, target, key, desc);
900
+ else
901
+ for (var i = decorators.length - 1; i >= 0; i--)
902
+ if (d = decorators[i])
903
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
904
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
905
+ };
906
+ /**
907
+ * Renders the individual form controls, composed from Vega design-system
908
+ * elements rather than raw HTML.
909
+ *
910
+ * ## Why these are Vega elements and not our own markup
911
+ *
912
+ * A pattern's job is workflow choreography; the look, the focus ring, the label
913
+ * association, the error presentation and the contrast ratios are the design
914
+ * system's job. Hand-rolling an `<input>` here would fork all of that: the DS
915
+ * could ship an accessibility fix to `vega-input` and this card would never
916
+ * receive it. Composing the DS element instead means the pattern inherits those
917
+ * guarantees — and keeps inheriting them.
918
+ *
919
+ * It is the same principle that led us to withdraw `::part` (ADR 0003), applied
920
+ * from the inside: we do not override design-system guardrails, and we do not
921
+ * quietly re-implement them either.
922
+ *
923
+ * Note how much behaviour this removes. `vega-input` carries its own password
924
+ * reveal affordance, so the card no longer tracks a `revealSecret` flag or
925
+ * renders an eye icon.
926
+ */
927
+ class LoginFieldRenderer extends UdsSlimmer {
928
+ constructor() {
929
+ super();
930
+ /**
931
+ * Rule instances are built once and kept.
932
+ *
933
+ * `validationRules` is a watched Vega prop, so handing it a freshly built
934
+ * array on every render would re-register the rules each time any unrelated
935
+ * state moved. These close over `this.viewModel`, which the slimmer re-reads
936
+ * from the component, so a single stable instance always evaluates against
937
+ * current state.
938
+ */
939
+ this.matchRules = new Map();
940
+ this.completeCode = [
941
+ new CompleteCodeRule(() => this.viewModel.otpLength, count => controlStrategy.formatLabel(this.viewModel.copy.otpIncomplete, { count })),
942
+ ];
943
+ }
944
+ /** One rule per pairing, memoised so the array identity is stable per field. */
945
+ matchRuleFor(sibling) {
946
+ let rule = this.matchRules.get(sibling);
947
+ if (!rule) {
948
+ rule = [
949
+ new MatchesFieldRule(() => this.viewModel.valueOf(sibling), () => this.viewModel.copy.errors.passwords_do_not_match),
950
+ ];
951
+ this.matchRules.set(sibling, rule);
952
+ }
953
+ return rule;
954
+ }
955
+ /** A labelled text input bound to a collected value. */
956
+ textField(options) {
957
+ const vm = this.viewModel;
958
+ const { name, label, type = 'text', autocomplete, mustMatch } = options;
959
+ return (index.h("vega-input", { key: name, class: "field", name: name, label: label, type: type, autocomplete: autocomplete, value: vm.valueOf(name), disabled: vm.busy, showClearIcon: false,
960
+ // `required` and `type="email"` are the whole of this field's
961
+ // validation: Vega derives the rules, the messages and the error
962
+ // presentation from them, and `vega-form` will not submit until they
963
+ // pass. `autoValidation` is what keeps the form's own `isValid` current
964
+ // between submits, and lets an error clear as the user corrects it.
965
+ required: true, autoValidation: true, validationRules: mustMatch ? this.matchRuleFor(mustMatch) : undefined, onVegaChange: (event) => this.handlers.onValueChange(name, event.detail) }));
966
+ }
967
+ /** The passcode entry for the OTP step. */
968
+ passcodeField() {
969
+ const vm = this.viewModel;
970
+ return (index.h("vega-input-passcode", { class: "field", name: "code", label: vm.copy.otpFieldLabel, length: vm.otpLength, inputType: "numeric", disabled: vm.busy,
971
+ // `required` only asks whether the value is non-empty, so a half-typed
972
+ // code would pass it; `CompleteCodeRule` is what holds the submit until
973
+ // every box is filled.
974
+ required: true, autoValidation: true, validationRules: this.completeCode, value: vm.valueOf('code'), onVegaChange: (event) => this.handlers.onValueChange('code', event.detail) }));
975
+ }
976
+ /** A checkbox bound to a collected boolean. */
977
+ checkbox(name, label) {
978
+ const vm = this.viewModel;
979
+ return (index.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));
980
+ }
981
+ /** The step's primary action. */
982
+ submit(label) {
983
+ const vm = this.viewModel;
984
+ return index.h("vega-button", { class: "submit", type: "submit", variant: "primary", block: true, label: label, loading: vm.busy });
985
+ }
986
+ /**
987
+ * An inline textual action.
988
+ *
989
+ * `eventful` stops `vega-button-link` from navigating on its own so the
990
+ * workflow — not the browser — decides what the click means.
991
+ */
992
+ link(label, onClick) {
993
+ return (index.h("vega-button-link", { class: "link", label: label, eventful: true, disabled: this.viewModel.busy, onVegaClick: (event) => onClick(event) }));
994
+ }
995
+ /**
996
+ * The MFA method chooser, as a design-system selection-tile group.
997
+ *
998
+ * The group — not the individual tiles — is the component Vega expects to be
999
+ * composed: it owns the radio `name`, the selected `value`, the change event
1000
+ * and the group's accessible labelling. Rendering loose `vega-selection-tile`
1001
+ * elements inside a plain `role="radiogroup"` div looked correct and even
1002
+ * behaved correctly, but threw inside Vega's runtime when the tiles
1003
+ * unmounted, because each tile reaches for a group ancestor that was not
1004
+ * there.
1005
+ */
1006
+ methodGroup(methods, label) {
1007
+ const vm = this.viewModel;
1008
+ return (index.h("vega-selection-tile-group", { class: "options", name: "mfaMethod", label: label, selectType: "radio", layout: "vertical", disabled: vm.busy,
1009
+ // No `autoValidation` counterpart on the group — a tile selection has
1010
+ // no intermediate state to validate against, so the submit gate is
1011
+ // the only moment the rule can fire.
1012
+ required: true, value: vm.valueOf('mfaMethod'), onVegaChange: (event) => this.handlers.onValueChange('mfaMethod', event.detail) }, methods.map(method => (index.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 })))));
1013
+ }
1014
+ }
1015
+ __decorate$3([
1016
+ MapToComponentField()
1017
+ ], LoginFieldRenderer.prototype, "viewModel", void 0);
1018
+ __decorate$3([
1019
+ MapToComponentField()
1020
+ ], LoginFieldRenderer.prototype, "handlers", void 0);
1021
+
1022
+ var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1023
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1024
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
1025
+ r = Reflect.decorate(decorators, target, key, desc);
1026
+ else
1027
+ for (var i = decorators.length - 1; i >= 0; i--)
1028
+ if (d = decorators[i])
1029
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1030
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1031
+ };
1032
+ /**
1033
+ * Renders the body of whichever step the workflow is on.
1034
+ *
1035
+ * ## Why a renderer slimmer
1036
+ *
1037
+ * This is the same Slimmer mechanism the stateful layer uses, pointed at
1038
+ * rendering: one class, one responsibility, bound to the component by field
1039
+ * mapping rather than by parameter passing. Vega applies the convention the
1040
+ * same way — `slimmers/renderers/<name>-renderer.tsx`, a class ending in
1041
+ * `Renderer`, exposing `render()` — so a reader moving between the design
1042
+ * system and the pattern library meets one structure, not two.
1043
+ *
1044
+ * The gain over the plain `view/` modules this replaces is the binding. A free
1045
+ * function has to be handed everything it needs on each call, so its signature
1046
+ * grows with its appetite and callers thread arguments through layers that do
1047
+ * not use them. `@MapToComponentField()` reaches the component's `viewModel`
1048
+ * and `handlers` directly, and types them by indexing the component class, so
1049
+ * the renderer cannot drift out of step with what the component actually
1050
+ * exposes — rename the getter and this stops compiling.
1051
+ *
1052
+ * Renderers only read. Every mutation goes back through `handlers`, into the
1053
+ * workflow slimmer, and through the control strategy's checkpoints.
1054
+ */
1055
+ class LoginStepRenderer extends UdsSlimmer {
1056
+ constructor() {
1057
+ super();
1058
+ this.fields = new LoginFieldRenderer();
1059
+ }
1060
+ render() {
1061
+ const vm = this.viewModel;
1062
+ switch (vm.step) {
1063
+ case 'identifier':
1064
+ return this.identifierStep(vm);
1065
+ case 'password':
1066
+ return this.passwordStep(vm);
1067
+ case 'mfa-method':
1068
+ return this.mfaMethodStep(vm);
1069
+ case 'otp':
1070
+ return this.otpStep(vm);
1071
+ case 'forgot-password':
1072
+ case 'forgot-username':
1073
+ return this.forgotStep(vm);
1074
+ case 'reset-password':
1075
+ return this.resetPasswordStep(vm);
1076
+ case 'sign-up':
1077
+ return this.signUpStep(vm);
1078
+ default:
1079
+ return [];
1080
+ }
1081
+ }
1082
+ identifierStep(vm) {
1083
+ const { copy } = vm;
1084
+ return [
1085
+ this.fields.textField({
1086
+ name: 'identifier',
1087
+ label: copy.identifierFieldLabel,
1088
+ type: 'email',
1089
+ autocomplete: 'username',
1090
+ }),
1091
+ this.fields.checkbox('rememberIdentifier', copy.rememberIdentifier),
1092
+ this.fields.submit(copy.continue),
1093
+ index.h("div", { class: "prose" }, index.h("p", { class: "prose__lead" }, copy.troubleSigningInPrompt), index.h("p", null, "Follow the steps in \u201C", this.fields.link(copy.troubleSigningIn, this.handlers.onAction('trouble-signing-in')), "\u201D."), vm.can.signUp && (index.h("p", null, copy.noAccountPrompt, " ", this.fields.link(copy.createAccount, this.handlers.onNavigate('sign-up'))))),
1094
+ ];
1095
+ }
1096
+ passwordStep(vm) {
1097
+ const { copy, can } = vm;
1098
+ const recovery = [];
1099
+ if (can.reset) {
1100
+ recovery.push(this.fields.link(copy.forgotPassword, this.handlers.onNavigate('forgot-password')));
1101
+ }
1102
+ if (can.username) {
1103
+ recovery.push(this.fields.link(copy.forgotUsername, this.handlers.onNavigate('forgot-username')));
1104
+ }
1105
+ return [
1106
+ // `vega-input` carries its own password reveal control, so the card no
1107
+ // longer renders — or tracks the state for — an eye toggle of its own.
1108
+ this.fields.textField({
1109
+ name: 'password',
1110
+ label: copy.passwordFieldLabel,
1111
+ type: 'password',
1112
+ autocomplete: 'current-password',
1113
+ }),
1114
+ ...(recovery.length ? [index.h("div", { class: "inline-links" }, recovery)] : []),
1115
+ this.fields.submit(copy.continue),
1116
+ ];
1117
+ }
1118
+ mfaMethodStep(vm) {
1119
+ const { copy } = vm;
1120
+ return [
1121
+ this.fields.methodGroup(vm.methods, copy.mfaMethodHeading),
1122
+ this.fields.submit(copy.continue),
1123
+ ];
1124
+ }
1125
+ otpStep(vm) {
1126
+ const { copy } = vm;
1127
+ const tail = [];
1128
+ if (vm.methods.length > 1) {
1129
+ tail.push(this.fields.link(copy.changeMfaMethod, this.handlers.onNavigate('mfa-method')));
1130
+ }
1131
+ if (vm.can.resend) {
1132
+ tail.push(vm.isResendThrottled ? (
1133
+ // Rendered as text rather than a disabled link: a countdown is a
1134
+ // status, and announcing it politely is more useful than offering a
1135
+ // dead target.
1136
+ index.h("span", { class: "countdown", "aria-live": "polite" }, vm.resendCountdownText)) : (this.fields.link(copy.resendCode, this.handlers.onResend)));
1137
+ }
1138
+ return [
1139
+ // `vega-input-passcode` owns the whole multi-box entry: per-character
1140
+ // focus advance, backspace retreat, paste distribution and masking. That
1141
+ // replaces the card's own OTP slimmer outright.
1142
+ this.fields.passcodeField(),
1143
+ this.fields.submit(copy.otpSubmit),
1144
+ ...(tail.length
1145
+ ? [
1146
+ index.h("div", { class: "prose" }, index.h("p", null, copy.changeMfaMethodPrompt), index.h("p", { class: "inline-links" }, tail)),
1147
+ ]
1148
+ : []),
1149
+ ];
1150
+ }
1151
+ forgotStep(vm) {
1152
+ const { copy } = vm;
1153
+ return [
1154
+ this.fields.textField({
1155
+ name: vm.step === 'forgot-password' ? 'identifier' : 'email',
1156
+ label: copy.identifierFieldLabel,
1157
+ type: 'email',
1158
+ autocomplete: 'username',
1159
+ }),
1160
+ this.fields.submit(copy.continue),
1161
+ index.h("p", { class: "prose" }, this.fields.link(copy.back, this.handlers.onNavigate('identifier'))),
1162
+ ];
1163
+ }
1164
+ resetPasswordStep(vm) {
1165
+ const { copy } = vm;
1166
+ return [
1167
+ this.fields.textField({
1168
+ name: 'password',
1169
+ label: copy.newPasswordFieldLabel,
1170
+ type: 'password',
1171
+ autocomplete: 'new-password',
1172
+ }),
1173
+ // The pairing is checked on the field that is wrong, inside the same
1174
+ // `vega-form` gate as every other rule — not as a pre-submit test
1175
+ // reported through the card-level banner.
1176
+ this.fields.textField({
1177
+ name: 'confirmPassword',
1178
+ label: copy.confirmPasswordFieldLabel,
1179
+ type: 'password',
1180
+ autocomplete: 'new-password',
1181
+ mustMatch: 'password',
1182
+ }),
1183
+ this.fields.submit(copy.continue),
1184
+ ];
1185
+ }
1186
+ signUpStep(vm) {
1187
+ const { copy } = vm;
1188
+ return [
1189
+ this.fields.textField({ name: 'firstName', label: copy.firstNameFieldLabel, autocomplete: 'given-name' }),
1190
+ this.fields.textField({ name: 'lastName', label: copy.lastNameFieldLabel, autocomplete: 'family-name' }),
1191
+ this.fields.textField({
1192
+ name: 'identifier',
1193
+ label: copy.identifierFieldLabel,
1194
+ type: 'email',
1195
+ autocomplete: 'email',
1196
+ }),
1197
+ this.fields.submit(copy.signUpSubmit),
1198
+ index.h("p", { class: "prose" }, copy.haveAccountPrompt, " ", this.fields.link(copy.signIn, this.handlers.onNavigate('identifier'))),
1199
+ ];
1200
+ }
1201
+ }
1202
+ __decorate$2([
1203
+ InjectSlimmer()
1204
+ ], LoginStepRenderer.prototype, "fields", void 0);
1205
+ __decorate$2([
1206
+ MapToComponentField()
1207
+ ], LoginStepRenderer.prototype, "viewModel", void 0);
1208
+ __decorate$2([
1209
+ MapToComponentField()
1210
+ ], LoginStepRenderer.prototype, "handlers", void 0);
1211
+
1212
+ var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1213
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1214
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
1215
+ r = Reflect.decorate(decorators, target, key, desc);
1216
+ else
1217
+ for (var i = decorators.length - 1; i >= 0; i--)
1218
+ if (d = decorators[i])
1219
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1220
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1221
+ };
1222
+ /**
1223
+ * Renders the card's body — heading, description, error, form and the terminal
1224
+ * outcome — and delegates the step's own controls to {@link LoginStepRenderer}.
1225
+ *
1226
+ * ## Why a renderer slimmer, and not the `view/` modules this replaces
1227
+ *
1228
+ * This is the same Slimmer mechanism the stateful layer already uses, pointed
1229
+ * at rendering: one class, one responsibility, bound to the component by field
1230
+ * mapping rather than by parameter passing. Vega applies the convention the
1231
+ * same way — `slimmers/renderers/<name>-renderer.tsx`, a class ending in
1232
+ * `Renderer`, exposing `render()` — so a reader moving between the design
1233
+ * system and the pattern library meets one structure, not two.
1234
+ *
1235
+ * The gain over free functions is the binding. A free function has to be handed
1236
+ * everything it needs on each call, so its signature grows with its appetite
1237
+ * and callers thread arguments through layers that never use them — which is
1238
+ * exactly what the old `StepViewModel` parameter had become.
1239
+ * `@MapToComponentField()` reaches the component's `viewModel` and `handlers`
1240
+ * directly, and types them by indexing the component class, so a renderer
1241
+ * cannot drift out of step with what the component actually exposes: rename the
1242
+ * getter and this stops compiling.
1243
+ *
1244
+ * Renderers only read. Every mutation goes back through `handlers`, into the
1245
+ * workflow slimmer, and through the control strategy's checkpoints.
1246
+ *
1247
+ * ## What stays behind in the component
1248
+ *
1249
+ * `render()` itself and both `<slot>` elements remain in `UdsPatternLoginCard`. Stencil
1250
+ * reads slots out of the component's source statically when it compiles, so a
1251
+ * slot that only existed inside a slimmer would never be registered.
1252
+ */
1253
+ class LoginCardRenderer extends UdsSlimmer {
1254
+ constructor() {
1255
+ super();
1256
+ this.steps = new LoginStepRenderer();
1257
+ }
1258
+ render() {
1259
+ const vm = this.viewModel;
1260
+ return [
1261
+ index.h("h1", { class: "card__heading", id: "uds-pattern-login-heading", tabindex: "-1" }, vm.heading),
1262
+ ...(vm.description ? [index.h("p", { class: "card__description" }, vm.description)] : []),
1263
+ // `vega-banner` carries the design system's error presentation — icon,
1264
+ // colour, contrast and live-region semantics — so the pattern does not
1265
+ // maintain a parallel version that can drift away from it.
1266
+ ...(vm.errorText
1267
+ ? [index.h("vega-banner", { class: "alert", type: "error", content: vm.errorText, isOpen: true, showCloseButton: false })]
1268
+ : []),
1269
+ ...(vm.isTerminal ? this.terminalBody(vm) : [this.form()]),
1270
+ ];
1271
+ }
1272
+ form() {
1273
+ return (
1274
+ // `vega-form` rather than a native `<form>`: a `vega-button[type=submit]`
1275
+ // lives in its own shadow root, so it cannot submit a native ancestor
1276
+ // form — the click would silently do nothing. Vega's fields and buttons
1277
+ // coordinate submission and validation through this element instead.
1278
+ //
1279
+ // It is also the validation gate. Its submit handler awaits `valid()`
1280
+ // across every field it controls and only emits `vegaFormSubmit` once
1281
+ // they all pass, so the step below never has to pre-check anything.
1282
+ //
1283
+ // `disableBlurValidation` suppresses validation on blur specifically.
1284
+ // Without it, clicking Submit blurs the field underneath it, an error
1285
+ // message appears, the button moves down and the click lands on whatever
1286
+ // took its place — so the first click is swallowed. Errors still appear
1287
+ // on submit, and still clear as the user types.
1288
+ index.h("vega-form", { class: "form", useNativeForm: true, disableBlurValidation: true, onVegaFormSubmit: (event) => this.handlers.onSubmit(event) }, index.h("div", { class: "form__body" }, this.steps.render())));
1289
+ }
1290
+ /** Terminal steps render no form; `check-email` still offers a way back. */
1291
+ terminalBody(vm) {
1292
+ if (vm.step !== 'check-email') {
1293
+ return [];
1294
+ }
1295
+ return [
1296
+ index.h("p", { class: "prose" }, index.h("vega-button-link", { class: "link", label: vm.copy.back, eventful: true, onVegaClick: (event) => this.handlers.onNavigate('identifier')(event) })),
1297
+ ];
1298
+ }
1299
+ }
1300
+ __decorate$1([
1301
+ InjectSlimmer()
1302
+ ], LoginCardRenderer.prototype, "steps", void 0);
1303
+ __decorate$1([
1304
+ MapToComponentField()
1305
+ ], LoginCardRenderer.prototype, "viewModel", void 0);
1306
+ __decorate$1([
1307
+ MapToComponentField()
1308
+ ], LoginCardRenderer.prototype, "handlers", void 0);
1309
+
1310
+ /**
1311
+ * The data a renderer needs to draw one step of the login workflow.
1312
+ *
1313
+ * ## Why this is a class with a factory, and not an inline object
1314
+ *
1315
+ * Deriving "what the user should see" from "what the workflow currently holds"
1316
+ * is real logic: which affordances are available, what the heading says, which
1317
+ * description variant applies, whether a countdown is running. Assembled inline
1318
+ * inside `render()` that derivation is untestable and re-read on every change;
1319
+ * named here it has one home, one test surface and one place to review.
1320
+ *
1321
+ * ## Why it carries no callbacks
1322
+ *
1323
+ * A view model is *data*. Handlers are supplied alongside it
1324
+ * (`LoginCardHandlers`) rather than folded in, because a model that carries
1325
+ * behaviour cannot be compared, snapshotted or reasoned about independently of
1326
+ * the component that produced it. Keeping the two apart is what lets the
1327
+ * renderers be checked as pure functions of their inputs.
1328
+ *
1329
+ * ## Pattern-library standard
1330
+ *
1331
+ * Every pattern component should follow this shape:
1332
+ *
1333
+ * - `view-model/<pattern>.view-model.ts` exporting a plain data class;
1334
+ * - construction through a single `static from(state)` factory;
1335
+ * - no inheritance, and no logic beyond derivation from the state passed in;
1336
+ * - consumed by renderer slimmers, never mutated by them.
1337
+ *
1338
+ * Note this is a *view model*, not a DTO: it never crosses a process or
1339
+ * serialisation boundary, only the boundary between the stateful layer
1340
+ * (slimmers) and the rendering layer. DTO semantics — validation, wire format —
1341
+ * belong at the provider seam, where data genuinely enters from outside.
1342
+ */
1343
+ class LoginStepViewModel {
1344
+ constructor(
1345
+ /** The step being rendered. */
1346
+ step,
1347
+ /** Resolved copy, after merging any consumer overrides. */
1348
+ copy,
1349
+ /** Heading for this step. */
1350
+ heading,
1351
+ /** Supporting description, when this step has one. */
1352
+ description,
1353
+ /** Error copy to surface, already mapped to approved wording. */
1354
+ errorText,
1355
+ /** Whether a provider call is in flight. */
1356
+ busy,
1357
+ /** Which affordances the provider can service. */
1358
+ can,
1359
+ /** MFA methods offered by the provider. */
1360
+ methods,
1361
+ /** Values collected on the current step. */
1362
+ values,
1363
+ /** How many characters the passcode entry expects. */
1364
+ otpLength,
1365
+ /** Seconds left on the resend throttle; zero when resending is allowed. */
1366
+ resendSeconds) {
1367
+ this.step = step;
1368
+ this.copy = copy;
1369
+ this.heading = heading;
1370
+ this.description = description;
1371
+ this.errorText = errorText;
1372
+ this.busy = busy;
1373
+ this.can = can;
1374
+ this.methods = methods;
1375
+ this.values = values;
1376
+ this.otpLength = otpLength;
1377
+ this.resendSeconds = resendSeconds;
1378
+ }
1379
+ static from(state) {
1380
+ const { step, copy } = state;
1381
+ return new LoginStepViewModel(step, copy, headingFor(step, copy), descriptionFor(step, copy, state), state.errorText, state.busy, state.can, state.methods, state.values, state.otpLength, state.resendSeconds);
1382
+ }
1383
+ /** Steps that render no form: terminal outcomes and confirmations. */
1384
+ get isTerminal() {
1385
+ return this.step === 'success' || this.step === 'locked' || this.step === 'check-email';
1386
+ }
1387
+ /** Whether a resend is currently throttled. */
1388
+ get isResendThrottled() {
1389
+ return this.resendSeconds > 0;
1390
+ }
1391
+ /** Copy for the throttle countdown, with the remaining seconds applied. */
1392
+ get resendCountdownText() {
1393
+ return controlStrategy.formatLabel(this.copy.resendCodeIn, { seconds: this.resendSeconds });
1394
+ }
1395
+ /** The current value of a collected field, as a string. */
1396
+ valueOf(name) {
1397
+ const value = this.values[name];
1398
+ return typeof value === 'string' ? value : '';
1399
+ }
1400
+ /** Whether a boolean field is set. */
1401
+ isChecked(name) {
1402
+ return this.values[name] === true;
1403
+ }
1404
+ }
1405
+ function headingFor(step, copy) {
1406
+ const headings = {
1407
+ identifier: copy.identifierHeading,
1408
+ password: copy.passwordHeading,
1409
+ 'mfa-method': copy.mfaMethodHeading,
1410
+ otp: copy.otpHeading,
1411
+ 'forgot-password': copy.forgotPasswordHeading,
1412
+ 'forgot-username': copy.forgotUsernameHeading,
1413
+ 'reset-password': copy.resetPasswordHeading,
1414
+ 'sign-up': copy.signUpHeading,
1415
+ 'check-email': copy.checkEmailHeading,
1416
+ locked: copy.lockedHeading,
1417
+ success: copy.successHeading,
1418
+ };
1419
+ return headings[step];
1420
+ }
1421
+ function descriptionFor(step, copy, ctx) {
1422
+ switch (step) {
1423
+ case 'mfa-method':
1424
+ return copy.mfaMethodDescription;
1425
+ case 'otp':
1426
+ return ctx.otpMethodLabel
1427
+ ? controlStrategy.formatLabel(copy.otpDescriptionForMethod, { method: ctx.otpMethodLabel })
1428
+ : copy.otpDescription;
1429
+ case 'forgot-password':
1430
+ return copy.forgotPasswordDescription;
1431
+ case 'forgot-username':
1432
+ return copy.forgotUsernameDescription;
1433
+ case 'sign-up':
1434
+ return copy.signUpDescription;
1435
+ case 'check-email':
1436
+ return ctx.destination
1437
+ ? controlStrategy.formatLabel(copy.checkEmailDescriptionWithDestination, { destination: ctx.destination })
1438
+ : copy.checkEmailDescription;
1439
+ case 'locked':
1440
+ return ctx.unlockMinutes
1441
+ ? controlStrategy.formatLabel(copy.lockedDescriptionWithTime, { minutes: ctx.unlockMinutes })
1442
+ : copy.lockedDescription;
1443
+ default:
1444
+ return undefined;
1445
+ }
1446
+ }
1447
+
1448
+ const udsPatternLoginCardCss = () => `:host{display:block;width:100%;max-width:var(--uds-pattern-login-card-max-width);box-sizing:border-box;--uds-pattern-login-card-max-width:35rem}:host *,:host *::before,:host *::after{box-sizing:inherit}.card{padding:var(--uds-pattern-space-2xl);background:var(--uds-pattern-color-surface);border-radius:var(--uds-pattern-radius-lg);box-shadow:var(--uds-pattern-shadow-card)}.card__heading{margin:0 0 var(--uds-pattern-space-sm);font-size:var(--uds-pattern-font-size-lg);font-weight:var(--uds-pattern-font-weight-bold);line-height:var(--uds-pattern-line-height-md)}.card__heading:focus,.card__heading:focus-visible{outline:none}.card__description{margin:0 0 var(--uds-pattern-space-lg);font-size:var(--uds-pattern-font-size-sm);color:var(--uds-pattern-color-text-subtle)}.card__footer{margin-top:var(--uds-pattern-space-xl);padding-top:var(--uds-pattern-space-xl);border-top:1px solid var(--uds-pattern-color-border);font-size:var(--uds-pattern-font-size-sm);color:var(--uds-pattern-color-text-subtle)}.card__footer p{margin:0}.form{margin-top:var(--uds-pattern-space-lg)}.form__body{display:flex;flex-direction:column;gap:var(--uds-pattern-space-xl);align-items:stretch}.alert{display:block;margin-bottom:var(--uds-pattern-space-md)}.options{display:block}.prose{margin:0;font-size:var(--uds-pattern-font-size-sm);color:var(--uds-pattern-color-text-subtle)}.prose p{margin:0 0 var(--uds-pattern-space-xs)}.prose p:last-child{margin-bottom:0}.prose__lead{font-weight:var(--uds-pattern-font-weight-bold);color:var(--uds-pattern-color-text)}.link{display:inline-block;vertical-align:baseline}.inline-links{display:flex;flex-wrap:wrap;gap:var(--uds-pattern-space-md)}.countdown{color:var(--uds-pattern-color-text-subtle)}`;
1449
+
1450
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1451
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1452
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
1453
+ r = Reflect.decorate(decorators, target, key, desc);
1454
+ else
1455
+ for (var i = decorators.length - 1; i >= 0; i--)
1456
+ if (d = decorators[i])
1457
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1458
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1459
+ };
1460
+ const UdsPatternLoginCard = class {
1461
+ constructor(hostRef) {
1462
+ index.registerInstance(this, hostRef);
1463
+ this.udsLoginSuccess = index.createEvent(this, "udsLoginSuccess");
1464
+ this.udsLoginStepChange = index.createEvent(this, "udsLoginStepChange");
1465
+ this.udsLoginError = index.createEvent(this, "udsLoginError");
1466
+ this.udsLoginAction = index.createEvent(this, "udsLoginAction");
1467
+ // --- Slimmers -----------------------------------------------------------
1468
+ // Each owns one responsibility and binds back to the state below.
1469
+ this.workflow = new LoginWorkflowSlimmer();
1470
+ this.resend = new ResendThrottleSlimmer();
1471
+ this.memory = new IdentityMemorySlimmer();
1472
+ this.renderer = new LoginCardRenderer();
1473
+ // --- Props --------------------------------------------------------------
1474
+ /**
1475
+ * Everything that cannot be expressed as an attribute: the `auth` adapter,
1476
+ * the `control` strategy and any `labels` overrides.
1477
+ *
1478
+ * The split is mechanical rather than a matter of taste — see
1479
+ * `UdsLoginConfig`. Scalars below are attributes because they can be;
1480
+ * objects and functions are gathered here because they cannot be.
1481
+ *
1482
+ * Also accepts a JSON string, so the `labels` member alone can be set from
1483
+ * plain HTML markup.
1484
+ */
1485
+ this.config = {};
1486
+ /** The step the workflow starts on. */
1487
+ this.initialStep = 'identifier';
1488
+ /** Pre-populates the identifier field, overriding any remembered value. */
1489
+ this.identifier = '';
1490
+ /** Default number of digit inputs, when the provider does not specify one. */
1491
+ this.otpLength = 6;
1492
+ /**
1493
+ * Where the "Remember my email" value is persisted. Set to an empty string to
1494
+ * disable persistence entirely.
1495
+ */
1496
+ this.rememberStorageKey = 'uds-pattern-login.identifier';
1497
+ /** Renders the "Need help signing in?" footer. */
1498
+ this.showHelpFooter = true;
1499
+ // --- State --------------------------------------------------------------
1500
+ // Written by the slimmers through `@MapToComponentField({ writable: true })`.
1501
+ this.step = 'identifier';
1502
+ this.busy = false;
1503
+ this.methods = [];
1504
+ this.nowTick = Date.now();
1505
+ this.values = {};
1506
+ /**
1507
+ * The intents a renderer may raise. Held as one stable object — not rebuilt
1508
+ * per render — so that binding to it from a slimmer is a fixed reference and
1509
+ * the data half of the contract stays free of behaviour.
1510
+ */
1511
+ this.handlers = {
1512
+ onSubmit: (event) => {
1513
+ event.preventDefault();
1514
+ void this.workflow.submit();
1515
+ },
1516
+ onNavigate: (step) => (event) => {
1517
+ event.preventDefault();
1518
+ this.workflow.navigateTo(step);
1519
+ },
1520
+ onAction: (action) => (event) => {
1521
+ event.preventDefault();
1522
+ this.udsLoginAction.emit({ action, step: this.step });
1523
+ },
1524
+ onResend: (event) => {
1525
+ event.preventDefault();
1526
+ void this.workflow.resendCode();
1527
+ },
1528
+ onValueChange: (name, value) => {
1529
+ this.values = { ...this.values, [name]: value };
1530
+ },
1531
+ };
1532
+ }
1533
+ // --- Lifecycle ----------------------------------------------------------
1534
+ // Bodies are intentionally empty: slimmers hook onto these by name via
1535
+ // `@MapToComponentMethod`, and the hooks run after the method itself.
1536
+ componentWillLoad() {
1537
+ /* LoginWorkflowSlimmer.start */
1538
+ }
1539
+ disconnectedCallback() {
1540
+ /* ResendThrottleSlimmer.stop */
1541
+ }
1542
+ componentDidRender() {
1543
+ if (this.lastRenderedStep !== undefined && this.lastRenderedStep !== this.step) {
1544
+ this.focusFirstControl();
1545
+ }
1546
+ this.lastRenderedStep = this.step;
1547
+ }
1548
+ /**
1549
+ * Returns the workflow to its first step and clears all collected input.
1550
+ * Useful after a sign-out, or to recover from a terminal step.
1551
+ */
1552
+ async reset() {
1553
+ /* LoginWorkflowSlimmer.restart */
1554
+ }
1555
+ // --- Derived ------------------------------------------------------------
1556
+ /** The resolved configuration, accepting a JSON string for HTML consumers. */
1557
+ get resolvedConfig() {
1558
+ return parseJsonProp(this.config, {});
1559
+ }
1560
+ /** The authentication seam. Read by the workflow slimmer. */
1561
+ get auth() {
1562
+ return this.resolvedConfig.auth;
1563
+ }
1564
+ /** The control seam. Read by the workflow slimmer at each checkpoint. */
1565
+ get controlStrategy() {
1566
+ return this.resolvedConfig.control;
1567
+ }
1568
+ /** Merged copy. Read by the slimmers as well, for error text. */
1569
+ get copy() {
1570
+ const overrides = this.resolvedConfig.labels ?? {};
1571
+ return {
1572
+ ...controlStrategy.DEFAULT_LOGIN_LABELS,
1573
+ ...overrides,
1574
+ errors: { ...controlStrategy.DEFAULT_LOGIN_LABELS.errors, ...(overrides.errors ?? {}) },
1575
+ };
1576
+ }
1577
+ /**
1578
+ * Everything the renderers need to draw the current step, and nothing else.
1579
+ *
1580
+ * Derivation happens in one place — `LoginStepViewModel.from` — rather than
1581
+ * being assembled inline in `render()`, so it can be tested without a DOM and
1582
+ * reviewed without reading JSX.
1583
+ */
1584
+ get viewModel() {
1585
+ return LoginStepViewModel.from({
1586
+ step: this.step,
1587
+ copy: this.copy,
1588
+ errorText: this.errorText,
1589
+ busy: this.busy,
1590
+ can: this.workflow.capabilities,
1591
+ methods: this.methods,
1592
+ values: this.values,
1593
+ otpLength: this.workflow.codeLength,
1594
+ resendSeconds: this.resend.secondsRemaining,
1595
+ otpMethodLabel: this.otpMethodLabel,
1596
+ destination: this.destination,
1597
+ unlockMinutes: this.unlockMinutes,
1598
+ });
1599
+ }
1600
+ focusFirstControl() {
1601
+ // `shadow: true`, so a shadow root is always attached by the time a render
1602
+ // has completed. The ambient type is nullable; the value is not.
1603
+ const root = this.hostEl.shadowRoot;
1604
+ // Vega fields are custom elements, so the focusable control is inside their
1605
+ // own shadow root. Delegating to the element's own `focus()` lets Vega
1606
+ // decide which of its internals should receive focus.
1607
+ //
1608
+ // Every step renders a heading, so the fallback always resolves — the
1609
+ // first arm is the one that matters, moving focus into the form.
1610
+ const target = root.querySelector('.form .field:not([disabled])') ??
1611
+ root.querySelector('.card__heading');
1612
+ target.focus();
1613
+ }
1614
+ // --- Rendering ----------------------------------------------------------
1615
+ render() {
1616
+ return (index.h(index.Host, { key: 'e8d37d2f818b9b2260fb2ccd3d784393a28385c4' }, index.h("section", { key: '1c0ce6ee5c73d24b06212d8a4b2f293c89e6cb70', class: "card", "aria-busy": String(this.busy) }, index.h("slot", { key: 'c87b3ceba0d974e15958aec53c19e76dc3996096', name: "brand" }), this.renderer.render(), this.showHelpFooter && (index.h("div", { key: '003268c5b8abf8755d55ba54e433fc8114171113', class: "card__footer" }, index.h("slot", { key: '376accf53b60a5cb489abde889ff74bf7fab3fe6', name: "footer" }, index.h("p", { key: 'a6834071896f9a3d988c089807fc6a77ee928ffc' }, this.copy.helpPrompt, ' ', index.h("vega-button-link", { key: '4645b67e1ccf70b0b386a516025659540a9308a9', class: "link", label: this.copy.help, eventful: true, onVegaClick: (event) => this.handlers.onAction('login-help')(event) }))))))));
1617
+ }
1618
+ get hostEl() { return index.getElement(this); }
1619
+ };
1620
+ __decorate([
1621
+ InjectSlimmer()
1622
+ ], UdsPatternLoginCard.prototype, "workflow", void 0);
1623
+ __decorate([
1624
+ InjectSlimmer()
1625
+ ], UdsPatternLoginCard.prototype, "resend", void 0);
1626
+ __decorate([
1627
+ InjectSlimmer()
1628
+ ], UdsPatternLoginCard.prototype, "memory", void 0);
1629
+ __decorate([
1630
+ InjectSlimmer()
1631
+ ], UdsPatternLoginCard.prototype, "renderer", void 0);
1632
+ /** Accepts either a parsed value or a JSON string (for plain-HTML consumers). */
1633
+ function parseJsonProp(value, fallback) {
1634
+ if (typeof value !== 'string') {
1635
+ return value ?? fallback;
1636
+ }
1637
+ try {
1638
+ return JSON.parse(value);
1639
+ }
1640
+ catch {
1641
+ return fallback;
1642
+ }
1643
+ }
1644
+ UdsPatternLoginCard.style = udsPatternLoginCardCss();
1645
+
1646
+ const udsPatternLoginPageCss = () => `:host{display:flex;flex-direction:column;min-height:100vh;box-sizing:border-box;font-family:var(--uds-pattern-font-family);color:var(--uds-pattern-color-text);background:var(--uds-pattern-color-background)}.masthead{display:flex;justify-content:center;align-items:center;padding:var(--uds-pattern-space-md) var(--uds-pattern-space-lg);background:var(--uds-pattern-color-surface)}.masthead__logo{display:block;max-height:2rem}.content{display:flex;flex:1;justify-content:center;align-items:center;padding:var(--uds-pattern-space-xl) var(--uds-pattern-space-md)}.page-footer:not(:empty){padding:var(--uds-pattern-space-md) var(--uds-pattern-space-lg);text-align:center;font-size:var(--uds-pattern-font-size-sm);color:var(--uds-pattern-color-text-subtle)}`;
1647
+
1648
+ const UdsPatternLoginPage = class {
1649
+ constructor(hostRef) {
1650
+ index.registerInstance(this, hostRef);
1651
+ /** Accessible name for the masthead logo. */
1652
+ this.logoAlt = '';
1653
+ }
1654
+ render() {
1655
+ const logo = this.logoSrc ? index.h("img", { class: "masthead__logo", src: this.logoSrc, alt: this.logoAlt }) : null;
1656
+ return (index.h(index.Host, { key: '7c2d0d6ae80dc7e52c0780c457de982738ef8faa' }, index.h("header", { key: '7eac231827e53a5e4018591506f7632aff8ddda5', class: "masthead" }, index.h("slot", { key: 'e80b31f6301079a15f45066189c646ed4c444e24', name: "brand" }, this.logoHref && logo ? index.h("a", { href: this.logoHref }, logo) : logo)), index.h("main", { key: '3081bf6ec017cff44900f6cd0d9c98b38baf5126', class: "content" }, index.h("slot", { key: '3c84048fae834578b00254e6bdbad4eecc74b5e2' })), index.h("footer", { key: 'dfb064a71e3f39c8e99212b523d043f9f9f20538', class: "page-footer" }, index.h("slot", { key: '5ea9ef897c20a96f3c42b2b4f54496155516a423', name: "footer" }))));
1657
+ }
1658
+ };
1659
+ UdsPatternLoginPage.style = udsPatternLoginPageCss();
1660
+
1661
+ exports.uds_pattern_login_card = UdsPatternLoginCard;
1662
+ exports.uds_pattern_login_page = UdsPatternLoginPage;