@godxjp/ui 31.29.0 → 31.30.1

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.
@@ -4,8 +4,18 @@ import * as React from "react";
4
4
  import { useTranslation } from "../../i18n/use-translation.js";
5
5
  import { ChevronDown, ChevronLeft, ChevronRight, GripVertical } from "lucide-react";
6
6
  import { Button } from "../general/button.js";
7
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
8
+ import { CONTRAST_PIVOT, relativeLuminance } from "../../app/tenant-theme.js";
7
9
  import { Text } from "../general/typography.js";
8
10
  import { cn } from "../../lib/utils.js";
11
+ function inkOn(color) {
12
+ if (!color) return void 0;
13
+ const luminance = relativeLuminance(color);
14
+ if (luminance == null) return void 0;
15
+ return luminance > CONTRAST_PIVOT ? "black" : "white";
16
+ }
17
+ const LABEL_WIDTH_STEP = 8;
18
+ const LABEL_WIDTH_BIG_STEP = 32;
9
19
  const RangeTimeline = React.forwardRef(
10
20
  function RangeTimeline2({
11
21
  label,
@@ -20,10 +30,30 @@ const RangeTimeline = React.forwardRef(
20
30
  defaultExpandedValues,
21
31
  onExpandedValuesChange,
22
32
  sticky = false,
33
+ resizableLabel = false,
34
+ labelWidth,
35
+ defaultLabelWidth,
36
+ onLabelWidthChange,
37
+ busy = false,
23
38
  className,
24
39
  ...props
25
40
  }, ref) {
26
41
  const { t } = useTranslation();
42
+ const resize = resizableLabel ? {
43
+ min: typeof resizableLabel === "object" && resizableLabel.min || 160,
44
+ max: typeof resizableLabel === "object" && resizableLabel.max || 640
45
+ } : null;
46
+ const [ownLabelWidth, setOwnLabelWidth] = React.useState(defaultLabelWidth);
47
+ const currentLabelWidth = labelWidth ?? ownLabelWidth;
48
+ const labelCell = React.useRef(null);
49
+ const labelDrag = React.useRef(null);
50
+ const setLabelWidth = (next) => {
51
+ if (!resize) return;
52
+ const bounded = Math.round(Math.min(resize.max, Math.max(resize.min, next)));
53
+ if (labelWidth === void 0) setOwnLabelWidth(bounded);
54
+ onLabelWidthChange?.(bounded);
55
+ };
56
+ const measuredLabelWidth = () => currentLabelWidth ?? Math.round(labelCell.current?.getBoundingClientRect().width ?? 0);
27
57
  const reactId = React.useId();
28
58
  const depthOf = (row) => Math.max(0, Math.floor(row.depth ?? 0));
29
59
  const nested = rows.some((row) => depthOf(row) > 0);
@@ -85,7 +115,7 @@ const RangeTimeline = React.forwardRef(
85
115
  const row = rows.find((row2) => row2.id === current.id);
86
116
  const delta = deltaAt(event.clientX);
87
117
  cancel();
88
- if (!row) return;
118
+ if (!row || row.start === null || row.end === null) return;
89
119
  const bounded = current.edge === "start" ? Math.min(delta, row.end - row.start) : Math.max(delta, row.start - row.end);
90
120
  if (bounded) onRangeChange?.(row.id, current.edge, bounded);
91
121
  };
@@ -107,7 +137,53 @@ const RangeTimeline = React.forwardRef(
107
137
  )) })
108
138
  ] }),
109
139
  /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-header", children: [
110
- /* @__PURE__ */ jsx("div", { className: "ui-range-timeline-label", children: /* @__PURE__ */ jsx(Text, { weight: "bold", children: label }) }),
140
+ /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-label", ref: labelCell, children: [
141
+ /* @__PURE__ */ jsx(Text, { weight: "bold", children: label }),
142
+ resize && /* @__PURE__ */ jsx(
143
+ "div",
144
+ {
145
+ role: "separator",
146
+ tabIndex: 0,
147
+ className: "ui-range-timeline-label-resizer",
148
+ "aria-orientation": "vertical",
149
+ "aria-label": t("rangeTimeline.resizeLabel"),
150
+ "aria-valuemin": resize.min,
151
+ "aria-valuemax": resize.max,
152
+ "aria-valuenow": currentLabelWidth ?? void 0,
153
+ onPointerDown: (event) => {
154
+ if (event.button !== 0) return;
155
+ event.preventDefault();
156
+ event.currentTarget.setPointerCapture(event.pointerId);
157
+ labelDrag.current = {
158
+ x: event.clientX,
159
+ width: measuredLabelWidth(),
160
+ direction: getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
161
+ };
162
+ },
163
+ onPointerMove: (event) => {
164
+ const current = labelDrag.current;
165
+ if (current)
166
+ setLabelWidth(current.width + (event.clientX - current.x) * current.direction);
167
+ },
168
+ onPointerUp: () => labelDrag.current = null,
169
+ onPointerCancel: () => labelDrag.current = null,
170
+ onLostPointerCapture: () => labelDrag.current = null,
171
+ onKeyDown: (event) => {
172
+ const step = event.shiftKey ? LABEL_WIDTH_BIG_STEP : LABEL_WIDTH_STEP;
173
+ const direction = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1;
174
+ let next = null;
175
+ if (event.key === "ArrowRight") next = measuredLabelWidth() + step * direction;
176
+ else if (event.key === "ArrowLeft")
177
+ next = measuredLabelWidth() - step * direction;
178
+ else if (event.key === "Home") next = resize.min;
179
+ else if (event.key === "End") next = resize.max;
180
+ if (next === null) return;
181
+ event.preventDefault();
182
+ setLabelWidth(next);
183
+ }
184
+ }
185
+ )
186
+ ] }),
111
187
  /* @__PURE__ */ jsx("div", { className: "ui-range-timeline-columns", ref: track, children: columns.map((column, index) => /* @__PURE__ */ jsx(
112
188
  "div",
113
189
  {
@@ -117,7 +193,8 @@ const RangeTimeline = React.forwardRef(
117
193
  },
118
194
  index
119
195
  )) })
120
- ] })
196
+ ] }),
197
+ busy && /* @__PURE__ */ jsx("div", { className: "ui-range-timeline-busy", "aria-hidden": "true" })
121
198
  ] });
122
199
  const body = /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-body", role: nested ? "list" : void 0, children: [
123
200
  (bordered || columns.some((column) => column.muted)) && // Decorative: the same unit tracks as the header columns, laid once behind every row so
@@ -136,10 +213,17 @@ const RangeTimeline = React.forwardRef(
136
213
  ] }),
137
214
  visibleRows.map((row, index) => {
138
215
  const idBase = `${reactId}-row-${rows.indexOf(row)}`;
216
+ const dated = row.start !== null && row.end !== null;
217
+ const rowStart = row.start ?? 0, rowEnd = row.end ?? 0;
139
218
  const delta = preview?.id === row.id ? preview.delta : 0;
140
- const start = Math.min(row.end, row.start + (preview?.edge === "start" ? delta : 0));
141
- const end = Math.max(row.start, row.end + (preview?.edge === "end" ? delta : 0));
219
+ const start = Math.min(rowEnd, rowStart + (preview?.edge === "start" ? delta : 0));
220
+ const end = Math.max(rowStart, rowEnd + (preview?.edge === "end" ? delta : 0));
142
221
  const left = Math.max(0, start), right = Math.min(units, end + 1);
222
+ const plan = dated && row.plan && row.plan.start !== null && row.plan.end !== null ? row.plan : null;
223
+ const planLeft = plan ? Math.max(0, plan.start) : 0, planRight = plan ? Math.min(units, plan.end + 1) : 0;
224
+ const overrun = plan !== null && end > plan.end;
225
+ const early = plan !== null && end < plan.end;
226
+ const overrunLeft = overrun ? Math.max(left, Math.min(units, plan.end + 1)) : 0;
143
227
  return /* @__PURE__ */ jsxs(
144
228
  "div",
145
229
  {
@@ -182,7 +266,14 @@ const RangeTimeline = React.forwardRef(
182
266
  /* @__PURE__ */ jsx("div", { id: `${idBase}-label`, className: "ui-range-timeline-label-content", children: row.label })
183
267
  ]
184
268
  }
185
- ) : /* @__PURE__ */ jsx("div", { className: "ui-range-timeline-label", children: row.label }),
269
+ ) : /* @__PURE__ */ jsx(
270
+ "div",
271
+ {
272
+ className: "ui-range-timeline-label",
273
+ id: row.tooltip != null ? `${idBase}-label` : void 0,
274
+ children: row.label
275
+ }
276
+ ),
186
277
  /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-track", children: [
187
278
  today != null && today >= 0 && today < units && /* @__PURE__ */ jsx(
188
279
  "span",
@@ -192,71 +283,135 @@ const RangeTimeline = React.forwardRef(
192
283
  style: { insetInlineStart: `${(today + 0.5) / units * 100}%` }
193
284
  }
194
285
  ),
195
- right <= left && (end < 0 ? /* @__PURE__ */ jsxs("span", { className: "ui-range-timeline-outside", "data-direction": "before", children: [
286
+ !dated && /* @__PURE__ */ jsx("span", { className: "ui-range-timeline-outside", "data-direction": "none", children: /* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: t("rangeTimeline.noDates") }) }),
287
+ dated && right <= left && (end < 0 ? /* @__PURE__ */ jsxs("span", { className: "ui-range-timeline-outside", "data-direction": "before", children: [
196
288
  /* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": "true", className: "ui-range-timeline-outside-icon" }),
197
289
  /* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: t("rangeTimeline.outsideBefore") })
198
290
  ] }) : /* @__PURE__ */ jsxs("span", { className: "ui-range-timeline-outside", "data-direction": "after", children: [
199
291
  /* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: t("rangeTimeline.outsideAfter") }),
200
292
  /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true", className: "ui-range-timeline-outside-icon" })
201
293
  ] })),
202
- right > left && /* @__PURE__ */ jsx(
294
+ plan && planRight > planLeft && /* @__PURE__ */ jsx(
295
+ "span",
296
+ {
297
+ "aria-hidden": "true",
298
+ className: "ui-range-timeline-plan",
299
+ style: {
300
+ insetInlineStart: `${planLeft / units * 100}%`,
301
+ inlineSize: `${(planRight - planLeft) / units * 100}%`
302
+ }
303
+ }
304
+ ),
305
+ overrun && right > overrunLeft && /* @__PURE__ */ jsxs(
306
+ "span",
307
+ {
308
+ className: "ui-range-timeline-overrun",
309
+ style: {
310
+ insetInlineStart: `${overrunLeft / units * 100}%`,
311
+ inlineSize: `${(right - overrunLeft) / units * 100}%`
312
+ },
313
+ children: [
314
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("rangeTimeline.overrun") }),
315
+ row.varianceLabel !== false && /* @__PURE__ */ jsx("span", { className: "ui-range-timeline-variance", children: row.varianceLabel ?? t("rangeTimeline.lateBy", { n: String(end - plan.end) }) })
316
+ ]
317
+ }
318
+ ),
319
+ dated && !plan && row.planNote != null && right > left && /* @__PURE__ */ jsx(
320
+ "span",
321
+ {
322
+ className: "ui-range-timeline-plan-note",
323
+ style: { insetInlineStart: `${right / units * 100}%` },
324
+ children: row.planNote
325
+ }
326
+ ),
327
+ early && right > left && right <= units && /* @__PURE__ */ jsx(
328
+ "span",
329
+ {
330
+ className: "ui-range-timeline-early",
331
+ style: { insetInlineStart: `${right / units * 100}%` },
332
+ children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("rangeTimeline.early") })
333
+ }
334
+ ),
335
+ dated && right > left && /* @__PURE__ */ jsxs(
203
336
  "div",
204
337
  {
205
338
  className: "ui-range-timeline-bar",
339
+ "data-muted": row.muted ? "true" : void 0,
340
+ "data-overdue": row.overdue ? "true" : void 0,
206
341
  style: {
207
342
  insetInlineStart: `${left / units * 100}%`,
208
- inlineSize: `${(right - left) / units * 100}%`
343
+ inlineSize: `${(right - left) / units * 100}%`,
344
+ ...row.color && {
345
+ "--range-timeline-bar-color": row.color,
346
+ "--range-timeline-bar-ink": inkOn(row.color)
347
+ }
209
348
  },
210
- children: onRangeChange && (right - left) * Math.max(1, columns.length) >= units && ["start", "end"].filter(
211
- (edge) => edge === "start" ? start >= 0 && start < units : end >= 0 && end < units
212
- ).map((edge) => /* @__PURE__ */ jsx(
213
- Button,
214
- {
215
- variant: "ghost",
216
- size: "icon-xs",
217
- className: "ui-range-timeline-handle",
218
- "data-edge": edge,
219
- "aria-label": edge === "start" ? row.startLabel : row.endLabel,
220
- onPointerDown: (event) => {
221
- const width = track.current?.getBoundingClientRect().width ?? 0;
222
- if (!width || event.button !== 0) return;
223
- event.currentTarget.setPointerCapture(event.pointerId);
224
- drag.current = {
225
- id: row.id,
226
- edge,
227
- x: event.clientX,
228
- width,
229
- direction: getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
230
- };
231
- },
232
- onPointerMove: (event) => {
233
- if (drag.current)
234
- setPreview({
235
- id: drag.current.id,
236
- edge: drag.current.edge,
237
- delta: deltaAt(event.clientX)
238
- });
239
- },
240
- onPointerUp: complete,
241
- onPointerCancel: cancel,
242
- onLostPointerCapture: cancel,
243
- onKeyDown: (event) => {
244
- if (event.key === "Escape") {
245
- cancel();
246
- return;
349
+ children: [
350
+ row.tooltip != null && /* @__PURE__ */ jsxs(Tooltip, { children: [
351
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
352
+ "span",
353
+ {
354
+ className: "ui-range-timeline-bar-hit ui-focus-ring",
355
+ tabIndex: 0,
356
+ "aria-labelledby": `${idBase}-label`
247
357
  }
248
- const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
249
- if (!step) return;
250
- event.preventDefault();
251
- const direction = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1;
252
- const delta2 = step * direction;
253
- if (edge === "start" ? row.start + delta2 <= row.end : row.end + delta2 >= row.start)
254
- onRangeChange(row.id, edge, delta2);
358
+ ) }),
359
+ /* @__PURE__ */ jsx(TooltipContent, { children: row.tooltip })
360
+ ] }),
361
+ row.overdue && /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("rangeTimeline.overdue") }),
362
+ onRangeChange && (right - left) * Math.max(1, columns.length) >= units && ["start", "end"].filter(
363
+ (edge) => edge === "start" ? start >= 0 && start < units : end >= 0 && end < units
364
+ ).filter(
365
+ (edge) => row.editable === void 0 || row.editable === true ? true : row.editable === false ? false : row.editable[edge] !== false
366
+ ).map((edge) => /* @__PURE__ */ jsx(
367
+ Button,
368
+ {
369
+ variant: "ghost",
370
+ size: "icon-xs",
371
+ className: "ui-range-timeline-handle",
372
+ "data-edge": edge,
373
+ "aria-label": edge === "start" ? row.startLabel : row.endLabel,
374
+ onPointerDown: (event) => {
375
+ const width = track.current?.getBoundingClientRect().width ?? 0;
376
+ if (!width || event.button !== 0) return;
377
+ event.currentTarget.setPointerCapture(event.pointerId);
378
+ drag.current = {
379
+ id: row.id,
380
+ edge,
381
+ x: event.clientX,
382
+ width,
383
+ direction: getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
384
+ };
385
+ },
386
+ onPointerMove: (event) => {
387
+ if (drag.current)
388
+ setPreview({
389
+ id: drag.current.id,
390
+ edge: drag.current.edge,
391
+ delta: deltaAt(event.clientX)
392
+ });
393
+ },
394
+ onPointerUp: complete,
395
+ onPointerCancel: cancel,
396
+ onLostPointerCapture: cancel,
397
+ onKeyDown: (event) => {
398
+ if (event.key === "Escape") {
399
+ cancel();
400
+ return;
401
+ }
402
+ const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
403
+ if (!step) return;
404
+ event.preventDefault();
405
+ const direction = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1;
406
+ const delta2 = step * direction;
407
+ if (edge === "start" ? rowStart + delta2 <= rowEnd : rowEnd + delta2 >= rowStart)
408
+ onRangeChange(row.id, edge, delta2);
409
+ },
410
+ children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true" })
255
411
  },
256
- children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true" })
257
- },
258
- edge
259
- ))
412
+ edge
413
+ ))
414
+ ]
260
415
  }
261
416
  )
262
417
  ] })
@@ -274,7 +429,14 @@ const RangeTimeline = React.forwardRef(
274
429
  // Only a NON-default step scopes the unit-width knob, so a theme that narrows
275
430
  // --range-timeline-unit-width globally still owns the default timeline (gh#730).
276
431
  "data-density": density === "default" ? void 0 : density,
277
- "aria-label": label
432
+ "data-busy": busy ? "true" : void 0,
433
+ "aria-busy": busy || void 0,
434
+ "aria-label": label,
435
+ // Only once a width exists does it override the token, so a theme's width stays the default.
436
+ style: currentLabelWidth !== void 0 ? {
437
+ ...props.style,
438
+ "--range-timeline-label-width": `${currentLabelWidth}px`
439
+ } : props.style
278
440
  };
279
441
  if (!sticky) {
280
442
  return /* @__PURE__ */ jsx("section", { ...sectionProps, tabIndex: 0, children: /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-canvas", style: canvasStyle, children: [
@@ -1,12 +1,29 @@
1
1
  declare const DEFAULT_PASSWORD_RULES: readonly ["length", "upper", "lower", "number", "symbol"];
2
2
  export type PasswordRule = (typeof DEFAULT_PASSWORD_RULES)[number];
3
+ /**
4
+ * Every visible and screen-reader string, each optional (gh#1191). Unset entries come from the
5
+ * kit's own ja / en / vi catalogue for the active locale, so most apps pass nothing.
6
+ */
3
7
  export type PasswordStrengthLabels = {
4
- weak: string;
5
- fair: string;
6
- strong: string;
8
+ weak?: string;
9
+ fair?: string;
10
+ strong?: string;
11
+ /** Checklist line per rule. */
12
+ rules?: Partial<Record<PasswordRule, string>>;
13
+ /** Screen-reader prefix before a met rule (e.g. "Passed: "). */
14
+ passed?: string;
15
+ /** Screen-reader prefix before an unmet rule (e.g. "Not yet: "). */
16
+ failed?: string;
17
+ /** The polite live-region sentence, given the strength word. */
18
+ announcement?: (strength: string) => string;
7
19
  };
8
20
  export type PasswordStrengthProps = {
9
21
  value: string;
22
+ /**
23
+ * The length rule's threshold (gh#1193), so the checklist matches the server's policy, e.g. 12 for
24
+ * Laravel `Password::min(12)`. Default 8. It drives the check, the score and the rule text.
25
+ */
26
+ minLength?: number;
10
27
  rules?: PasswordRule[];
11
28
  showChecklist?: boolean;
12
29
  labels?: PasswordStrengthLabels;
@@ -15,6 +32,6 @@ export type PasswordStrengthReturn = {
15
32
  score: 0 | 1 | 2 | 3 | 4;
16
33
  checks: Record<PasswordRule, boolean>;
17
34
  };
18
- export declare function usePasswordStrength(value: string, rules?: PasswordRule[]): PasswordStrengthReturn;
19
- export declare function PasswordStrength({ value, rules, showChecklist, labels, }: PasswordStrengthProps): import("react").JSX.Element;
35
+ export declare function usePasswordStrength(value: string, rules?: PasswordRule[], minLength?: number): PasswordStrengthReturn;
36
+ export declare function PasswordStrength({ value, minLength, rules, showChecklist, labels, }: PasswordStrengthProps): import("react").JSX.Element;
20
37
  export {};
@@ -1,16 +1,12 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Check, X } from "lucide-react";
4
+ import { useTranslation } from "../../i18n/use-translation.js";
4
5
  const DEFAULT_PASSWORD_RULES = ["length", "upper", "lower", "number", "symbol"];
5
- const DEFAULT_LABELS = {
6
- weak: "Weak",
7
- fair: "Fair",
8
- strong: "Strong"
9
- };
10
- function usePasswordStrength(value, rules = [...DEFAULT_PASSWORD_RULES]) {
6
+ function usePasswordStrength(value, rules = [...DEFAULT_PASSWORD_RULES], minLength = 8) {
11
7
  const uniqueRules = [...new Set(rules)];
12
8
  const checks = {
13
- length: value.length >= 8,
9
+ length: value.length >= minLength,
14
10
  upper: /[A-Z]/.test(value),
15
11
  lower: /[a-z]/.test(value),
16
12
  number: /\d/.test(value),
@@ -25,28 +21,34 @@ function scoreTone(score) {
25
21
  if (score <= 3) return "warning";
26
22
  return "success";
27
23
  }
28
- function scoreLabel(score, labels) {
29
- if (score <= 1) return labels.weak;
30
- if (score <= 3) return labels.fair;
31
- return labels.strong;
24
+ function scoreKey(score) {
25
+ if (score <= 1) return "weak";
26
+ if (score <= 3) return "fair";
27
+ return "strong";
32
28
  }
33
29
  function PasswordStrength({
34
30
  value,
31
+ minLength = 8,
35
32
  rules = [...DEFAULT_PASSWORD_RULES],
36
33
  showChecklist = true,
37
- labels = DEFAULT_LABELS
34
+ labels = {}
38
35
  }) {
36
+ const { t } = useTranslation();
39
37
  const normalizedRules = [...new Set(rules)];
40
- const { score, checks } = usePasswordStrength(value, normalizedRules);
38
+ const { score, checks } = usePasswordStrength(value, normalizedRules, minLength);
41
39
  const tone = scoreTone(score);
42
40
  const segments = Array.from({ length: 5 });
41
+ const key = scoreKey(score);
42
+ const strength = labels[key] ?? t(`dataEntry.passwordStrength.${key}`);
43
+ const ruleLabel = (rule) => labels.rules?.[rule] ?? t(`dataEntry.passwordStrength.rules.${rule}`, { n: String(minLength) });
44
+ const announcement = labels.announcement ? labels.announcement(strength) : t("dataEntry.passwordStrength.announcement", { strength });
43
45
  return /* @__PURE__ */ jsxs("div", { className: "ui-password-strength", children: [
44
46
  /* @__PURE__ */ jsx(
45
47
  "div",
46
48
  {
47
49
  className: "ui-password-strength-track",
48
50
  role: "img",
49
- "aria-label": `Password strength ${score}/4`,
51
+ "aria-label": t("dataEntry.passwordStrength.meter", { score: String(score) }),
50
52
  children: segments.map((_, index) => {
51
53
  const filled = index < score + 1;
52
54
  return /* @__PURE__ */ jsx(
@@ -63,7 +65,7 @@ function PasswordStrength({
63
65
  }
64
66
  ),
65
67
  /* @__PURE__ */ jsxs("div", { className: "ui-password-strength-meta", children: [
66
- /* @__PURE__ */ jsx("span", { className: "ui-password-strength-label", children: scoreLabel(score, labels) }),
68
+ /* @__PURE__ */ jsx("span", { className: "ui-password-strength-label", children: strength }),
67
69
  /* @__PURE__ */ jsxs("span", { className: "ui-password-strength-score", "aria-hidden": "true", children: [
68
70
  score,
69
71
  "/4"
@@ -76,34 +78,15 @@ function PasswordStrength({
76
78
  "data-state": checks[rule] ? "passed" : "failed",
77
79
  children: [
78
80
  checks[rule] ? /* @__PURE__ */ jsx(Check, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(X, { "aria-hidden": "true" }),
79
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: checks[rule] ? "Passed: " : "Failed: " }),
80
- /* @__PURE__ */ jsx("span", { children: labelForRule(rule) })
81
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: checks[rule] ? labels.passed ?? t("dataEntry.passwordStrength.passed") : labels.failed ?? t("dataEntry.passwordStrength.failed") }),
82
+ /* @__PURE__ */ jsx("span", { children: ruleLabel(rule) })
81
83
  ]
82
84
  },
83
85
  rule
84
86
  )) }) : null,
85
- /* @__PURE__ */ jsxs("span", { className: "sr-only", "aria-live": "polite", children: [
86
- scoreLabel(score, labels),
87
- " password strength"
88
- ] })
87
+ /* @__PURE__ */ jsx("span", { className: "sr-only", "aria-live": "polite", children: announcement })
89
88
  ] });
90
89
  }
91
- function labelForRule(rule) {
92
- switch (rule) {
93
- case "length":
94
- return "8+ characters";
95
- case "upper":
96
- return "Contains uppercase letter";
97
- case "lower":
98
- return "Contains lowercase letter";
99
- case "number":
100
- return "Contains number";
101
- case "symbol":
102
- return "Contains symbol";
103
- default:
104
- return rule;
105
- }
106
- }
107
90
  export {
108
91
  PasswordStrength,
109
92
  usePasswordStrength
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "31.29.0",
3
+ "version": "31.30.1",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -240,6 +240,22 @@
240
240
  "moved": "{label}: position {position} of {count}.",
241
241
  "dropped": "Dropped {label} at position {position} of {count}.",
242
242
  "cancelled": "Move cancelled. {label} is back at position {position} of {count}."
243
+ },
244
+ "passwordStrength": {
245
+ "weak": "Weak",
246
+ "fair": "Fair",
247
+ "strong": "Strong",
248
+ "meter": "Password strength {score} of 4",
249
+ "passed": "Passed: ",
250
+ "failed": "Not yet: ",
251
+ "announcement": "Password strength: {strength}",
252
+ "rules": {
253
+ "length": "{n}+ characters",
254
+ "upper": "Contains uppercase letter",
255
+ "lower": "Contains lowercase letter",
256
+ "number": "Contains number",
257
+ "symbol": "Contains symbol"
258
+ }
243
259
  }
244
260
  },
245
261
  "feedback": {
@@ -749,7 +765,13 @@
749
765
  "childRows": "Child rows",
750
766
  "outsideAfter": "After the visible range",
751
767
  "outsideBefore": "Before the visible range",
752
- "schedule": "Schedule"
768
+ "schedule": "Schedule",
769
+ "noDates": "No dates",
770
+ "resizeLabel": "Resize the label column",
771
+ "overdue": "Overdue",
772
+ "overrun": "Over the plan",
773
+ "early": "Ahead of the plan",
774
+ "lateBy": "+{n}d"
753
775
  },
754
776
  "chat": {
755
777
  "bubble": {
@@ -236,6 +236,22 @@
236
236
  "moved": "{label}: {count} 件中 {position} 番目。",
237
237
  "dropped": "{label} を {count} 件中 {position} 番目に置きました。",
238
238
  "cancelled": "移動を取り消しました。{label} は {count} 件中 {position} 番目に戻りました。"
239
+ },
240
+ "passwordStrength": {
241
+ "weak": "弱い",
242
+ "fair": "普通",
243
+ "strong": "強い",
244
+ "meter": "パスワードの強度 4 段階中 {score}",
245
+ "passed": "達成: ",
246
+ "failed": "未達成: ",
247
+ "announcement": "パスワードの強度: {strength}",
248
+ "rules": {
249
+ "length": "{n} 文字以上",
250
+ "upper": "大文字を含む",
251
+ "lower": "小文字を含む",
252
+ "number": "数字を含む",
253
+ "symbol": "記号を含む"
254
+ }
239
255
  }
240
256
  },
241
257
  "feedback": {
@@ -724,7 +740,13 @@
724
740
  "childRows": "子の行",
725
741
  "outsideAfter": "表示範囲より後",
726
742
  "outsideBefore": "表示範囲より前",
727
- "schedule": "スケジュール"
743
+ "schedule": "スケジュール",
744
+ "noDates": "日付なし",
745
+ "resizeLabel": "ラベル列の幅を変更",
746
+ "overdue": "期限超過",
747
+ "overrun": "予定超過",
748
+ "early": "予定より早く完了",
749
+ "lateBy": "+{n}日"
728
750
  },
729
751
  "chat": {
730
752
  "bubble": {
@@ -237,6 +237,22 @@
237
237
  "moved": "{label}: vị trí {position}/{count}.",
238
238
  "dropped": "Đã thả {label} ở vị trí {position}/{count}.",
239
239
  "cancelled": "Đã huỷ di chuyển. {label} trở lại vị trí {position}/{count}."
240
+ },
241
+ "passwordStrength": {
242
+ "weak": "Yếu",
243
+ "fair": "Trung bình",
244
+ "strong": "Mạnh",
245
+ "meter": "Độ mạnh mật khẩu {score} trên 4",
246
+ "passed": "Đạt: ",
247
+ "failed": "Chưa đạt: ",
248
+ "announcement": "Độ mạnh mật khẩu: {strength}",
249
+ "rules": {
250
+ "length": "Tối thiểu {n} ký tự",
251
+ "upper": "Có chữ in hoa",
252
+ "lower": "Có chữ thường",
253
+ "number": "Có chữ số",
254
+ "symbol": "Có ký hiệu"
255
+ }
240
256
  }
241
257
  },
242
258
  "feedback": {
@@ -725,7 +741,13 @@
725
741
  "childRows": "Hàng con",
726
742
  "outsideAfter": "Sau khoảng đang xem",
727
743
  "outsideBefore": "Trước khoảng đang xem",
728
- "schedule": "Lịch công việc"
744
+ "schedule": "Lịch công việc",
745
+ "noDates": "Chưa có ngày",
746
+ "resizeLabel": "Đổi độ rộng cột nhãn",
747
+ "overdue": "Quá hạn",
748
+ "overrun": "Vượt kế hoạch",
749
+ "early": "Sớm hơn kế hoạch",
750
+ "lateBy": "+{n} ngày"
729
751
  },
730
752
  "chat": {
731
753
  "bubble": {
@@ -71,6 +71,15 @@ import type { TreeFieldNamesProp, TreeOptionProp } from "./data-entry.prop.js";
71
71
  export type LegendItemProp = {
72
72
  /** The tone this key explains — the SAME tone the marks it stands for are drawn in. */
73
73
  tone: ToneProp;
74
+ color?: never;
75
+ label: LabelProp;
76
+ } | {
77
+ /**
78
+ * A hex colour from data (the same contract as `Badge color` / `RangeTimelineRow color`),
79
+ * for keys whose colours are user-chosen, e.g. a project's status colours (gh#1189).
80
+ */
81
+ color: string;
82
+ tone?: never;
74
83
  label: LabelProp;
75
84
  };
76
85
  /** @see Legend — the key for a colour-coded surface: a breakdown bar, a chart, a status column. */