@widgetic/design-system 0.5.14 → 0.5.16

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 (155) hide show
  1. package/README.md +167 -0
  2. package/dist/components/CopyableHeader.svelte +25 -11
  3. package/dist/components/CopyableHeader.svelte.d.ts +4 -0
  4. package/dist/components/EmptyState.svelte +1 -1
  5. package/dist/components/PasswordInput.svelte +2 -2
  6. package/dist/components/WScrollArea.svelte +2 -2
  7. package/dist/components/ic/browser/BrowserInputController.svelte +2 -2
  8. package/dist/components/index.d.ts +1 -0
  9. package/dist/components/modals/ActionModal.svelte +7 -2
  10. package/dist/components/modals/ActionModal.svelte.d.ts +2 -0
  11. package/dist/components/modals/BaseModal.svelte +4 -2
  12. package/dist/components/modals/BaseModal.svelte.d.ts +1 -0
  13. package/dist/components/modals/NewItemDialog.svelte +48 -30
  14. package/dist/components/modals/NewItemDialog.svelte.d.ts +3 -0
  15. package/dist/components/modals/Portal.svelte +9 -1
  16. package/dist/components/modals/README.md +34 -1
  17. package/dist/components/modals/RenameDialog.svelte +29 -9
  18. package/dist/components/modals/RenameDialog.svelte.d.ts +3 -0
  19. package/dist/components/modals/messages.d.ts +39 -0
  20. package/dist/components/modals/messages.js +10 -0
  21. package/dist/components/navigation/Breadcrumbs.svelte +55 -50
  22. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +4 -0
  23. package/dist/components/navigation/types.d.ts +8 -0
  24. package/dist/components/pdf/PDFViewer.svelte +17 -8
  25. package/dist/components/pdf/PDFViewer.svelte.d.ts +5 -0
  26. package/dist/components/search-input/SearchInput.svelte +3 -3
  27. package/dist/components/shad-editor/colorpicker.svelte +4 -1
  28. package/dist/components/shad-editor/custom/Extentions/ImageExtention.d.ts +20 -1
  29. package/dist/components/shad-editor/custom/Extentions/ImageExtention.js +4 -1
  30. package/dist/components/shad-editor/custom/code-extended.svelte +4 -2
  31. package/dist/components/shad-editor/custom/image-extended-component.svelte +54 -32
  32. package/dist/components/shad-editor/editor.css +5 -5
  33. package/dist/components/shad-editor/i18n/context.d.ts +6 -0
  34. package/dist/components/shad-editor/i18n/context.js +11 -0
  35. package/dist/components/shad-editor/i18n/index.d.ts +8 -0
  36. package/dist/components/shad-editor/i18n/index.js +11 -0
  37. package/dist/components/shad-editor/i18n/messages.d.ts +109 -0
  38. package/dist/components/shad-editor/i18n/messages.js +116 -0
  39. package/dist/components/shad-editor/icons/block-quote.svelte +3 -1
  40. package/dist/components/shad-editor/icons/bold.svelte +3 -1
  41. package/dist/components/shad-editor/icons/buttle-list.svelte +3 -1
  42. package/dist/components/shad-editor/icons/code.svelte +3 -1
  43. package/dist/components/shad-editor/icons/highlighter.svelte +5 -3
  44. package/dist/components/shad-editor/icons/image.svelte +7 -5
  45. package/dist/components/shad-editor/icons/italic.svelte +3 -1
  46. package/dist/components/shad-editor/icons/link.svelte +8 -6
  47. package/dist/components/shad-editor/icons/ordered-list.svelte +3 -1
  48. package/dist/components/shad-editor/icons/quickcolor.svelte +24 -22
  49. package/dist/components/shad-editor/icons/redo.svelte +3 -1
  50. package/dist/components/shad-editor/icons/search-replace.svelte +16 -14
  51. package/dist/components/shad-editor/icons/strikethrough.svelte +3 -1
  52. package/dist/components/shad-editor/icons/subscript.svelte +3 -1
  53. package/dist/components/shad-editor/icons/superscript.svelte +3 -1
  54. package/dist/components/shad-editor/icons/table.svelte +21 -19
  55. package/dist/components/shad-editor/icons/task-list.svelte +3 -1
  56. package/dist/components/shad-editor/icons/text.svelte +13 -11
  57. package/dist/components/shad-editor/icons/textalign.svelte +11 -9
  58. package/dist/components/shad-editor/icons/textcolor.svelte +5 -3
  59. package/dist/components/shad-editor/icons/underline.svelte +3 -1
  60. package/dist/components/shad-editor/icons/undo.svelte +3 -1
  61. package/dist/components/shad-editor/index.d.ts +1 -0
  62. package/dist/components/shad-editor/index.js +2 -0
  63. package/dist/components/shad-editor/shad-editor.svelte +32 -3
  64. package/dist/components/shad-editor/shad-editor.svelte.d.ts +12 -0
  65. package/dist/components/textDirection.d.ts +91 -0
  66. package/dist/components/textDirection.js +99 -0
  67. package/dist/components/toast/ToasterModal.svelte +49 -14
  68. package/dist/components/toast/ToasterModal.svelte.d.ts +9 -2
  69. package/dist/components/toast/toasterRoots.d.ts +23 -0
  70. package/dist/components/toast/toasterRoots.js +36 -0
  71. package/dist/components/ui/alert/index.d.ts +2 -2
  72. package/dist/components/ui/alert/index.js +1 -1
  73. package/dist/components/ui/breadcrumb/breadcrumb-separator.svelte +1 -1
  74. package/dist/components/ui/calendar/calendar-next-button.svelte +21 -4
  75. package/dist/components/ui/calendar/calendar-next-button.svelte.d.ts +9 -2
  76. package/dist/components/ui/calendar/calendar-prev-button.svelte +21 -4
  77. package/dist/components/ui/calendar/calendar-prev-button.svelte.d.ts +9 -2
  78. package/dist/components/ui/calendar/calendar.svelte +18 -6
  79. package/dist/components/ui/calendar/calendar.svelte.d.ts +6 -1
  80. package/dist/components/ui/command/command-input.svelte +1 -1
  81. package/dist/components/ui/command/command-shortcut.svelte +1 -1
  82. package/dist/components/ui/dialog/dialog-content.svelte +5 -2
  83. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +1 -0
  84. package/dist/components/ui/dialog/dialog-header.svelte +1 -1
  85. package/dist/components/ui/drawer/drawer-header.svelte +1 -1
  86. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +2 -2
  87. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +6 -0
  88. package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
  89. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +1 -1
  90. package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +1 -1
  91. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +2 -2
  92. package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
  93. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +7 -1
  94. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +2 -2
  95. package/dist/components/ui/hover-card/hover-card-content.svelte +6 -0
  96. package/dist/components/ui/input-otp/input-otp-slot.svelte +1 -1
  97. package/dist/components/ui/popover/popover-content.svelte +6 -0
  98. package/dist/components/ui/progress/progress.svelte +2 -2
  99. package/dist/components/ui/range-calendar/range-calendar-cell.svelte +1 -1
  100. package/dist/components/ui/range-calendar/range-calendar-next-button.svelte +21 -4
  101. package/dist/components/ui/range-calendar/range-calendar-next-button.svelte.d.ts +9 -2
  102. package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte +21 -4
  103. package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte.d.ts +9 -2
  104. package/dist/components/ui/range-calendar/range-calendar.svelte +18 -6
  105. package/dist/components/ui/range-calendar/range-calendar.svelte.d.ts +7 -2
  106. package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +1 -1
  107. package/dist/components/ui/scroll-area/scroll-area.svelte +6 -1
  108. package/dist/components/ui/select/select-content.svelte +6 -0
  109. package/dist/components/ui/select/select-group-heading.svelte +1 -1
  110. package/dist/components/ui/select/select-item.svelte +2 -2
  111. package/dist/components/ui/slider/slider.svelte +6 -0
  112. package/dist/components/ui/switch/switch.svelte +1 -1
  113. package/dist/components/ui/table/table-cell.svelte +1 -1
  114. package/dist/components/ui/table/table-head.svelte +1 -1
  115. package/dist/components/ui/tooltip/tooltip-content.svelte +6 -0
  116. package/dist/i18n/audit/catalogUsage.d.ts +36 -0
  117. package/dist/i18n/audit/catalogUsage.js +58 -0
  118. package/dist/i18n/audit/designSystemWords.d.ts +47 -0
  119. package/dist/i18n/audit/designSystemWords.js +137 -0
  120. package/dist/i18n/audit/hardCodedText.d.ts +40 -0
  121. package/dist/i18n/audit/hardCodedText.js +27 -0
  122. package/dist/i18n/audit/index.d.ts +19 -0
  123. package/dist/i18n/audit/index.js +19 -0
  124. package/dist/i18n/audit/messageCalls.d.ts +34 -0
  125. package/dist/i18n/audit/messageCalls.js +89 -0
  126. package/dist/i18n/audit/syntaxTree.d.ts +33 -0
  127. package/dist/i18n/audit/syntaxTree.js +90 -0
  128. package/dist/i18n/audit/visibleText.d.ts +57 -0
  129. package/dist/i18n/audit/visibleText.js +252 -0
  130. package/dist/i18n/createI18n.d.ts +63 -0
  131. package/dist/i18n/createI18n.js +72 -0
  132. package/dist/i18n/direction.d.ts +27 -0
  133. package/dist/i18n/direction.js +89 -0
  134. package/dist/i18n/index.d.ts +17 -0
  135. package/dist/i18n/index.js +16 -0
  136. package/dist/i18n/localeDate.d.ts +34 -0
  137. package/dist/i18n/localeDate.js +104 -0
  138. package/dist/i18n/splitMessages.d.ts +21 -0
  139. package/dist/i18n/splitMessages.js +33 -0
  140. package/dist/i18n/translator.d.ts +61 -0
  141. package/dist/i18n/translator.js +337 -0
  142. package/dist/rtl/audit/auditPhysicalPlacement.d.ts +36 -0
  143. package/dist/rtl/audit/auditPhysicalPlacement.js +58 -0
  144. package/dist/rtl/audit/index.d.ts +14 -0
  145. package/dist/rtl/audit/index.js +14 -0
  146. package/dist/rtl/audit/physicalPlacement.d.ts +64 -0
  147. package/dist/rtl/audit/physicalPlacement.js +444 -0
  148. package/dist/shad-editor/index.d.ts +1 -1
  149. package/dist/shad-editor/index.js +1 -1
  150. package/dist/styles/global.css +4 -4
  151. package/dist/utils/fillMessage.d.ts +8 -0
  152. package/dist/utils/fillMessage.js +10 -0
  153. package/dist/utils/itemName.d.ts +17 -0
  154. package/dist/utils/itemName.js +41 -0
  155. package/package.json +19 -2
@@ -1,5 +1,12 @@
1
1
  import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui";
2
- export type RangeCalendarPrevButtonProps = RangeCalendarPrimitive.PrevButtonProps;
3
- declare const RangeCalendarPrevButton: import("svelte").Component<RangeCalendarPrimitive.PrevButtonProps, {}, "ref">;
2
+ export type RangeCalendarPrevButtonProps = RangeCalendarPrimitive.PrevButtonProps & {
3
+ /**
4
+ * The name a screen reader gives the button, in the page's language. The library names the button "Previous" and
5
+ * keeps that name whatever props it is given, so this one is put on the button after the library's own. English
6
+ * ("Previous") when it is not given.
7
+ */
8
+ label?: string;
9
+ };
10
+ declare const RangeCalendarPrevButton: import("svelte").Component<RangeCalendarPrevButtonProps, {}, "ref">;
4
11
  type RangeCalendarPrevButton = ReturnType<typeof RangeCalendarPrevButton>;
5
12
  export default RangeCalendarPrevButton;
@@ -1,11 +1,17 @@
1
1
  <script lang="ts" module>
2
2
  import { RangeCalendar as RangeCalendarPrimitive, type WithoutChildrenOrChild } from "bits-ui";
3
- export type RangeCalendarProps = WithoutChildrenOrChild<RangeCalendarPrimitive.RootProps>;
3
+ export type RangeCalendarProps = WithoutChildrenOrChild<RangeCalendarPrimitive.RootProps> & {
4
+ /** The name a screen reader gives the button to the previous month, in the page's language. English when not given. */
5
+ previousLabel?: string;
6
+ /** The name a screen reader gives the button to the next month, in the page's language. English when not given. */
7
+ nextLabel?: string;
8
+ };
4
9
  </script>
5
10
 
6
11
  <script lang="ts">
7
12
  import * as RangeCalendar from "./index.js";
8
13
  import { cn } from "../../../utils.js";
14
+ import { describeCalendarWeekdayHeading } from "../../../i18n/localeDate.js";
9
15
 
10
16
  let {
11
17
  ref = $bindable(null),
@@ -13,6 +19,9 @@
13
19
  placeholder = $bindable(),
14
20
  weekdayFormat = "short",
15
21
  class: className,
22
+ previousLabel,
23
+ nextLabel,
24
+ locale: calendarLocale = 'en',
16
25
  ...restProps
17
26
  }: RangeCalendarProps = $props();
18
27
  </script>
@@ -22,23 +31,26 @@
22
31
  bind:value
23
32
  bind:placeholder
24
33
  {weekdayFormat}
34
+ locale={calendarLocale}
25
35
  class={cn("p-3", className)}
26
36
  {...restProps}
27
37
  >
28
38
  {#snippet children({ months, weekdays })}
29
39
  <RangeCalendar.Header>
30
- <RangeCalendar.PrevButton />
40
+ <RangeCalendar.PrevButton label={previousLabel} />
31
41
  <RangeCalendar.Heading />
32
- <RangeCalendar.NextButton />
42
+ <RangeCalendar.NextButton label={nextLabel} />
33
43
  </RangeCalendar.Header>
34
44
  <RangeCalendar.Months>
35
45
  {#each months as month}
36
46
  <RangeCalendar.Grid>
37
47
  <RangeCalendar.GridHead>
38
48
  <RangeCalendar.GridRow class="flex">
39
- {#each weekdays as weekday}
40
- <RangeCalendar.HeadCell>
41
- {weekday.slice(0, 2)}
49
+ {#each weekdays as weekday, weekdayIndex}
50
+ {@const weekdayHeading = describeCalendarWeekdayHeading(String(calendarLocale), weekdayIndex)}
51
+ <!-- A one-letter name (a language written right to left) carries the whole name for a screen reader. -->
52
+ <RangeCalendar.HeadCell aria-label={weekdayHeading.fullName}>
53
+ {weekdayHeading.label || weekday}
42
54
  </RangeCalendar.HeadCell>
43
55
  {/each}
44
56
  </RangeCalendar.GridRow>
@@ -1,6 +1,11 @@
1
1
  import { RangeCalendar as RangeCalendarPrimitive, type WithoutChildrenOrChild } from "bits-ui";
2
- export type RangeCalendarProps = WithoutChildrenOrChild<RangeCalendarPrimitive.RootProps>;
2
+ export type RangeCalendarProps = WithoutChildrenOrChild<RangeCalendarPrimitive.RootProps> & {
3
+ /** The name a screen reader gives the button to the previous month, in the page's language. English when not given. */
4
+ previousLabel?: string;
5
+ /** The name a screen reader gives the button to the next month, in the page's language. English when not given. */
6
+ nextLabel?: string;
7
+ };
3
8
  import * as RangeCalendar from "./index.js";
4
- declare const RangeCalendar: import("svelte").Component<Omit<Omit<RangeCalendarPrimitive.RootProps, "child">, "children">, {}, "placeholder" | "ref" | "value">;
9
+ declare const RangeCalendar: import("svelte").Component<RangeCalendarProps, {}, "placeholder" | "ref" | "value">;
5
10
  type RangeCalendar = ReturnType<typeof RangeCalendar>;
6
11
  export default RangeCalendar;
@@ -20,7 +20,7 @@
20
20
  {orientation}
21
21
  class={cn(
22
22
  "flex touch-none select-none transition-colors",
23
- orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-px",
23
+ orientation === "vertical" && "h-full w-2.5 border-s border-s-transparent p-px",
24
24
  orientation === "horizontal" && "h-2.5 w-full border-t border-t-transparent p-px",
25
25
  className
26
26
  )}
@@ -2,6 +2,11 @@
2
2
  import { ScrollArea as ScrollAreaPrimitive, type WithoutChild } from "bits-ui";
3
3
  import { Scrollbar } from "./index.js";
4
4
  import { cn } from "../../../utils.js";
5
+ import { useTextDirection } from "../../textDirection.js";
6
+
7
+ // The library writes dir="ltr" on its own element unless it is told the direction, and that turns everything
8
+ // inside the scroll area left to right even on a right to left screen.
9
+ const textDirection = useTextDirection();
5
10
 
6
11
  let {
7
12
  ref = $bindable(null),
@@ -18,7 +23,7 @@
18
23
  } = $props();
19
24
  </script>
20
25
 
21
- <ScrollAreaPrimitive.Root bind:ref {...restProps} class={cn("relative overflow-hidden", className)}>
26
+ <ScrollAreaPrimitive.Root bind:ref dir={$textDirection} {...restProps} class={cn("relative overflow-hidden", className)}>
22
27
  <ScrollAreaPrimitive.Viewport class="h-full w-full rounded-[inherit]">
23
28
  {@render children?.()}
24
29
  </ScrollAreaPrimitive.Viewport>
@@ -3,6 +3,11 @@
3
3
  import SelectScrollUpButton from "./select-scroll-up-button.svelte";
4
4
  import SelectScrollDownButton from "./select-scroll-down-button.svelte";
5
5
  import { cn } from "../../../utils.js";
6
+ import { useTextDirection } from "../../textDirection.js";
7
+
8
+ // The library writes dir="ltr" on the floating element unless it is told the direction: left to right text and
9
+ // alignment inside a menu of a right to left screen.
10
+ const textDirection = useTextDirection();
6
11
 
7
12
  let {
8
13
  ref = $bindable(null),
@@ -20,6 +25,7 @@
20
25
  <SelectPrimitive.Content
21
26
  bind:ref
22
27
  {sideOffset}
28
+ dir={$textDirection}
23
29
  class={cn(
24
30
  "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-popover text-popover-foreground relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border shadow-md data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
25
31
  className
@@ -15,6 +15,6 @@
15
15
 
16
16
  <SelectPrimitive.GroupHeading
17
17
  bind:ref
18
- class={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
18
+ class={cn("py-1.5 ps-8 pe-2 text-sm font-semibold", className)}
19
19
  {...restProps}
20
20
  />
@@ -21,13 +21,13 @@
21
21
  bind:ref
22
22
  {value}
23
23
  class={cn(
24
- "data-highlighted:bg-accent data-highlighted:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden data-disabled:pointer-events-none data-disabled:opacity-50",
24
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-hidden data-disabled:pointer-events-none data-disabled:opacity-50",
25
25
  className
26
26
  )}
27
27
  {...restProps}
28
28
  >
29
29
  {#snippet children({ selected, highlighted })}
30
- <span class="absolute left-2 flex size-3.5 items-center justify-center">
30
+ <span class="absolute start-2 flex size-3.5 items-center justify-center">
31
31
  {#if selected}
32
32
  <Check class="size-4" />
33
33
  {/if}
@@ -5,6 +5,11 @@
5
5
 
6
6
  <script lang="ts">
7
7
  import { cn } from "../../../utils.js";
8
+ import { useTextDirection } from "../../textDirection.js";
9
+
10
+ // The slider of the library is left to right unless it is told: on a right to left screen the smallest value is at the
11
+ // right end, like the rest of the screen, and the arrow keys follow.
12
+ const textDirection = useTextDirection();
8
13
 
9
14
  let {
10
15
  ref = $bindable(null),
@@ -22,6 +27,7 @@ get along, so we shut typescript up by casting `value` to `never`.
22
27
  bind:value={value as never}
23
28
  bind:ref
24
29
  class={cn("relative flex w-full touch-none select-none items-center", className)}
30
+ dir={$textDirection}
25
31
  {...restProps}
26
32
  >
27
33
  {#snippet children({ thumbs })}
@@ -25,7 +25,7 @@
25
25
  >
26
26
  <SwitchPrimitive.Thumb
27
27
  class={cn(
28
- "bg-background pointer-events-none block size-5 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
28
+ "bg-background pointer-events-none block size-5 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0 rtl:data-[state=checked]:-translate-x-5"
29
29
  )}
30
30
  />
31
31
  </SwitchPrimitive.Root>
@@ -18,7 +18,7 @@
18
18
 
19
19
  <td
20
20
  bind:this={ref}
21
- class={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
21
+ class={cn("p-4 align-middle [&:has([role=checkbox])]:pe-0", className)}
22
22
  {...restProps}
23
23
  >
24
24
  {@render children?.()}
@@ -19,7 +19,7 @@
19
19
  <th
20
20
  bind:this={ref}
21
21
  class={cn(
22
- "text-muted-foreground h-12 px-4 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0",
22
+ "text-muted-foreground h-12 px-4 text-start align-middle font-medium [&:has([role=checkbox])]:pe-0",
23
23
  className
24
24
  )}
25
25
  {...restProps}
@@ -5,6 +5,11 @@
5
5
 
6
6
  <script lang="ts">
7
7
  import { cn } from "../../../utils.js";
8
+ import { useTextDirection } from "../../textDirection.js";
9
+
10
+ // The library writes dir="ltr" on the floating element unless it is told the direction: left to right text and
11
+ // alignment inside a menu of a right to left screen.
12
+ const textDirection = useTextDirection();
8
13
 
9
14
  let {
10
15
  ref = $bindable(null),
@@ -17,6 +22,7 @@
17
22
  <TooltipPrimitive.Content
18
23
  bind:ref
19
24
  {sideOffset}
25
+ dir={$textDirection}
20
26
  class={cn(
21
27
  "bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md border px-3 py-1.5 text-sm shadow-md",
22
28
  className
@@ -0,0 +1,36 @@
1
+ import { type MessageProblem } from '../translator.ts';
2
+ import { type MessageCall, type MessageCallOptions } from './messageCalls.ts';
3
+ /**
4
+ * The catalog and the code have to agree: every message the code asks for exists, is given the values it asks for, and
5
+ * every message of the catalog is asked for somewhere (so nobody translates a sentence that never appears).
6
+ */
7
+ /** Lower-case dotted names, each with a word after the dot: `upload.cancel`, `toast.renameFailed`. */
8
+ export declare const DEFAULT_KEY_SHAPE: RegExp;
9
+ export interface CatalogUsageOptions extends MessageCallOptions {
10
+ /** The folder of the package's source. */
11
+ folder: string;
12
+ /** The English messages of the package. */
13
+ catalog: Readonly<Record<string, string>>;
14
+ /** Files to leave out (full paths): the catalog's own file holds every key as text. */
15
+ exclude?: readonly string[];
16
+ /** The shape every key has. */
17
+ keyShape?: RegExp;
18
+ }
19
+ export interface CatalogUsageReport {
20
+ /** The files that were read. */
21
+ fileCount: number;
22
+ /** Every place that asks for a message. */
23
+ calls: MessageCall[];
24
+ /** One line for each call that asks for a message the catalog does not have. */
25
+ unknownKeys: string[];
26
+ /** One line for each call that gives a message the wrong values (a missing one, or one it does not ask for). */
27
+ wrongValues: string[];
28
+ /** Messages of the catalog that nothing asks for. */
29
+ unusedKeys: string[];
30
+ /** Problems in the English messages themselves: a message that does not parse, or asks for a value it was not given. */
31
+ messageProblems: MessageProblem[];
32
+ /** Keys that do not have the shape. */
33
+ oddKeys: string[];
34
+ }
35
+ /** Reads the package and compares the messages the code asks for with the catalog. */
36
+ export declare function auditCatalogUsage(options: CatalogUsageOptions): CatalogUsageReport;
@@ -0,0 +1,58 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { findMessageProblems, valueNamesOfTemplate } from "../translator.js";
4
+ import { findMessageCalls } from "./messageCalls.js";
5
+ import { listSourceFiles } from "./syntaxTree.js";
6
+ /**
7
+ * The catalog and the code have to agree: every message the code asks for exists, is given the values it asks for, and
8
+ * every message of the catalog is asked for somewhere (so nobody translates a sentence that never appears).
9
+ */
10
+ /** Lower-case dotted names, each with a word after the dot: `upload.cancel`, `toast.renameFailed`. */
11
+ export const DEFAULT_KEY_SHAPE = /^[a-z][A-Za-z0-9]*(\.[A-Za-z][A-Za-z0-9]*)+$/;
12
+ function describeCall(folder, call) {
13
+ return `${path.relative(folder, call.file).split(path.sep).join('/')}:${call.line} ${call.via} '${call.key}${call.keyIsPrefix ? '${…}' : ''}'`;
14
+ }
15
+ /** Reads the package and compares the messages the code asks for with the catalog. */
16
+ export function auditCatalogUsage(options) {
17
+ const { folder, catalog } = options;
18
+ const excluded = new Set(options.exclude ?? []);
19
+ const catalogKeys = Object.keys(catalog);
20
+ const sourceFiles = listSourceFiles(folder).filter((file) => !excluded.has(file));
21
+ const sources = new Map(sourceFiles.map((file) => [file, fs.readFileSync(file, 'utf8')]));
22
+ const calls = sourceFiles.flatMap((file) => findMessageCalls(file, sources.get(file), options));
23
+ const has = (key) => Object.prototype.hasOwnProperty.call(catalog, key);
24
+ const unknownKeys = calls
25
+ .filter((call) => (call.keyIsPrefix ? !catalogKeys.some((key) => key.startsWith(call.key)) : !has(call.key)))
26
+ .map((call) => describeCall(folder, call));
27
+ const wrongValues = [];
28
+ for (const call of calls) {
29
+ if (call.keyIsPrefix || call.valueNames === null || !has(call.key))
30
+ continue;
31
+ const asked = valueNamesOfTemplate(catalog[call.key]);
32
+ const missing = asked.filter((name) => !call.valueNames?.includes(name));
33
+ const extra = call.valueNames.filter((name) => !asked.includes(name));
34
+ if (missing.length > 0 || extra.length > 0) {
35
+ wrongValues.push(`${describeCall(folder, call)}: ${missing.length > 0 ? `missing ${missing.join(', ')}` : ''}${missing.length > 0 && extra.length > 0 ? '; ' : ''}${extra.length > 0 ? `not asked for ${extra.join(', ')}` : ''}`);
36
+ }
37
+ }
38
+ const allSource = [...sources.values()].join('\n');
39
+ const prefixes = calls.filter((call) => call.keyIsPrefix).map((call) => call.key);
40
+ const unusedKeys = catalogKeys.filter((key) => {
41
+ if (prefixes.some((prefix) => key.startsWith(prefix)))
42
+ return false;
43
+ if (calls.some((call) => !call.keyIsPrefix && call.key === key))
44
+ return false;
45
+ // A key kept in a list of keys is used where the list is read.
46
+ return !(allSource.includes(`'${key}'`) || allSource.includes(`"${key}"`));
47
+ });
48
+ const keyShape = options.keyShape ?? DEFAULT_KEY_SHAPE;
49
+ return {
50
+ fileCount: sourceFiles.length,
51
+ calls,
52
+ unknownKeys,
53
+ wrongValues,
54
+ unusedKeys,
55
+ messageProblems: findMessageProblems(catalog, catalog),
56
+ oddKeys: catalogKeys.filter((key) => !keyShape.test(key))
57
+ };
58
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Some components of the design system carry English of their own (the close button of a window, "Back" in the path above
3
+ * the files, "Loading PDF..."). The design system lets a page replace each of those words with a prop, but a component that
4
+ * is used without the prop quietly shows English. This reads the syntax tree of each file of a package and reports a
5
+ * component that shows words and is used without all of them.
6
+ */
7
+ /** Where the design system's components come from, unless a package says otherwise. Its icons, stores and the like show no words. */
8
+ export declare const DEFAULT_COMPONENTS_MODULES: readonly string[];
9
+ export interface DesignSystemWordsOptions {
10
+ /** The folder of the package's source. */
11
+ folder: string;
12
+ /**
13
+ * The props that carry the words of each component of the design system the package uses, as the design system
14
+ * documents them. A component is listed with every prop that holds a word a visitor can see or hear.
15
+ */
16
+ componentsWithWords: Readonly<Record<string, readonly string[]>>;
17
+ /** The components and functions the package uses that show no words of their own: the words come in as content. */
18
+ withoutWords: readonly string[];
19
+ /** The module names the design system's components are imported from. */
20
+ componentsModules?: readonly string[];
21
+ /** Files to leave out (paths, as `folder` joined with the name). */
22
+ exclude?: readonly string[];
23
+ }
24
+ export interface DesignSystemUse {
25
+ file: string;
26
+ line: number;
27
+ /** The name the design system exports. */
28
+ component: string;
29
+ /** The names of the attributes written on it; `null` when a spread (`{...props}`) hides them. */
30
+ attributes: string[] | null;
31
+ }
32
+ export interface DesignSystemWordsReport {
33
+ /** Every use of a design-system component in a Svelte template. */
34
+ uses: DesignSystemUse[];
35
+ /** The design-system components that a `.ts` file of the package passes on: the name the file exports → the name in the design system. */
36
+ passedOn: Map<string, string>;
37
+ /** Everything the package imports from the design system's components module. */
38
+ importedNames: Set<string>;
39
+ /** One line for each use of a component that shows words, without all of its words. */
40
+ missing: string[];
41
+ /** Names the package imports that are in neither list: a new component has to be put in one of them. */
42
+ unknown: string[];
43
+ /** Names in a list that the package no longer imports. */
44
+ stale: string[];
45
+ }
46
+ /** Reads every file of a package and compares what it uses of the design system with what each component says it shows. */
47
+ export declare function auditDesignSystemWords(options: DesignSystemWordsOptions): DesignSystemWordsReport;
@@ -0,0 +1,137 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { lineOf, listSourceFiles, parseFile, walkTree } from "./syntaxTree.js";
4
+ /**
5
+ * Some components of the design system carry English of their own (the close button of a window, "Back" in the path above
6
+ * the files, "Loading PDF..."). The design system lets a page replace each of those words with a prop, but a component that
7
+ * is used without the prop quietly shows English. This reads the syntax tree of each file of a package and reports a
8
+ * component that shows words and is used without all of them.
9
+ */
10
+ /** Where the design system's components come from, unless a package says otherwise. Its icons, stores and the like show no words. */
11
+ export const DEFAULT_COMPONENTS_MODULES = ['@widgetic/design-system/components'];
12
+ /** The name a side of an export specifier gives: an identifier's `name`, or the text of `export { x as 'a b' }`. */
13
+ function nameOfSpecifier(node) {
14
+ if (!node)
15
+ return undefined;
16
+ if (node.type === 'Identifier')
17
+ return node.name;
18
+ return typeof node.value === 'string' ? node.value : undefined;
19
+ }
20
+ /**
21
+ * Local name → exported name, for what a file imports from the design system's components (types are left out). Also what
22
+ * it imports from a file of the package itself that passes a component of the design system on (see
23
+ * {@link namesPassedOnFromTheDesignSystem}): a barrel like `components/modals/index.ts`, which would otherwise hide the
24
+ * component from the audit.
25
+ */
26
+ function importsOf(program, modules, passedOn) {
27
+ const imports = new Map();
28
+ for (const statement of program?.body ?? []) {
29
+ if (statement.type !== 'ImportDeclaration' || statement.importKind === 'type')
30
+ continue;
31
+ const source = String(statement.source?.value);
32
+ const fromDesignSystem = modules.has(source);
33
+ const fromThePackage = source.startsWith('$lib') || source.startsWith('.');
34
+ if (!fromDesignSystem && !fromThePackage)
35
+ continue;
36
+ for (const specifier of statement.specifiers) {
37
+ if (specifier.importKind === 'type')
38
+ continue;
39
+ if (specifier.type === 'ImportSpecifier') {
40
+ const exported = specifier.imported.name ?? specifier.imported.value;
41
+ if (fromDesignSystem)
42
+ imports.set(specifier.local.name, exported);
43
+ else if (passedOn.has(exported))
44
+ imports.set(specifier.local.name, passedOn.get(exported));
45
+ }
46
+ else if (fromDesignSystem) {
47
+ // `import * as X` and `import X`: the audit cannot tell which component `<X.Y>` is, so it says so
48
+ imports.set(specifier.local.name, `${specifier.local.name}.*`);
49
+ }
50
+ }
51
+ }
52
+ return imports;
53
+ }
54
+ /**
55
+ * The design system's components that a `.ts` file of the package passes on (`import { RenameDialog } from
56
+ * '@widgetic/design-system/components'; export { RenameDialog };`, or `export { RenameDialog } from '...'`), as the name the
57
+ * file exports → the name the design system gives it. A file that then imports the name from that file is using the
58
+ * design system's component all the same.
59
+ */
60
+ function namesPassedOnFromTheDesignSystem(files, modules) {
61
+ const passedOn = new Map();
62
+ for (const { file, source } of files) {
63
+ if (!file.endsWith('.ts'))
64
+ continue;
65
+ const program = parseFile(file, source).tree.module?.content;
66
+ const imports = importsOf(program, modules, new Map());
67
+ for (const statement of program?.body ?? []) {
68
+ if (statement.type !== 'ExportNamedDeclaration')
69
+ continue;
70
+ // `exportKind` is the TypeScript parser's addition to the tree; the types of the tree do not know it.
71
+ if (statement.exportKind === 'type')
72
+ continue;
73
+ for (const specifier of statement.specifiers ?? []) {
74
+ const local = nameOfSpecifier(specifier.local);
75
+ const exported = nameOfSpecifier(specifier.exported);
76
+ if (local === undefined || exported === undefined)
77
+ continue;
78
+ if (modules.has(String(statement.source?.value)))
79
+ passedOn.set(exported, local);
80
+ else if (statement.source === null && imports.has(local))
81
+ passedOn.set(exported, imports.get(local));
82
+ }
83
+ }
84
+ }
85
+ return passedOn;
86
+ }
87
+ function readFile(file, source, modules, passedOn) {
88
+ const { tree, text } = parseFile(file, source);
89
+ const imports = new Map([...importsOf(tree.instance?.content, modules, passedOn), ...importsOf(tree.module?.content, modules, passedOn)]);
90
+ const uses = [];
91
+ for (const { node } of walkTree(tree.fragment)) {
92
+ if (node.type !== 'Component')
93
+ continue;
94
+ const exported = imports.get(String(node.name).split('.')[0]);
95
+ if (exported === undefined)
96
+ continue;
97
+ const attributes = node.attributes ?? [];
98
+ const hasSpread = attributes.some((attribute) => attribute.type === 'SpreadAttribute');
99
+ uses.push({
100
+ file,
101
+ line: lineOf(text, node.start),
102
+ component: exported,
103
+ attributes: hasSpread ? null : attributes.filter((attribute) => typeof attribute.name === 'string').map((attribute) => attribute.name)
104
+ });
105
+ }
106
+ return { uses, imported: [...imports.values()] };
107
+ }
108
+ /** Reads every file of a package and compares what it uses of the design system with what each component says it shows. */
109
+ export function auditDesignSystemWords(options) {
110
+ const modules = new Set(options.componentsModules ?? DEFAULT_COMPONENTS_MODULES);
111
+ const excluded = new Set(options.exclude ?? []);
112
+ const sourceFiles = listSourceFiles(options.folder)
113
+ .filter((file) => !excluded.has(file))
114
+ .map((file) => ({ file, source: fs.readFileSync(file, 'utf8') }));
115
+ const passedOn = namesPassedOnFromTheDesignSystem(sourceFiles, modules);
116
+ const found = sourceFiles.map(({ file, source }) => readFile(file, source, modules, passedOn));
117
+ const uses = found.flatMap((entry) => entry.uses);
118
+ const importedNames = new Set(found.flatMap((entry) => entry.imported));
119
+ const missing = [];
120
+ for (const use of uses) {
121
+ const words = Object.prototype.hasOwnProperty.call(options.componentsWithWords, use.component) ? options.componentsWithWords[use.component] : undefined;
122
+ if (!words)
123
+ continue;
124
+ const place = `${path.relative(options.folder, use.file).split(path.sep).join('/')}:${use.line} <${use.component}>`;
125
+ if (use.attributes === null) {
126
+ missing.push(`${place}: has a {...spread}; write the props out so the audit can see them`);
127
+ continue;
128
+ }
129
+ const absent = words.filter((word) => !use.attributes?.includes(word));
130
+ if (absent.length > 0)
131
+ missing.push(`${place}: no ${absent.join(', ')}`);
132
+ }
133
+ const listed = new Set([...Object.keys(options.componentsWithWords), ...options.withoutWords]);
134
+ const unknown = [...importedNames].filter((name) => !listed.has(name));
135
+ const stale = [...listed].filter((name) => !importedNames.has(name));
136
+ return { uses, passedOn, importedNames, missing, unknown, stale };
137
+ }
@@ -0,0 +1,40 @@
1
+ import { type VisibleText, type VisibleTextOptions } from './visibleText.ts';
2
+ /**
3
+ * Every word a visitor can read must come from the message catalog, or a page cannot translate it. This reads the syntax
4
+ * tree of each file of a package and reports the English that is written into a file, except for the texts the package
5
+ * allows (with the reason for each).
6
+ */
7
+ /** A text that is allowed to be written into a file. Keep the list short: a word here is a word no page can translate. */
8
+ export interface AllowedText {
9
+ /** The path of the file inside the package's source folder, with `/` between the parts. */
10
+ file: string;
11
+ /** The text as the scan reads it (spaces folded, `{}` where a value goes). */
12
+ text: string;
13
+ /** Why no page needs to translate it: a type specimen, a CSS unit, English for the host page's code. */
14
+ reason: string;
15
+ }
16
+ export interface HardCodedTextOptions extends VisibleTextOptions {
17
+ /** The folder of the package's source. */
18
+ folder: string;
19
+ /** `svelte` (the default) reads the components; `source` reads the TypeScript files too, for a package that keeps sentences there. */
20
+ files?: 'svelte' | 'source';
21
+ /** Files to leave out (full paths): the catalog's own file holds every sentence by design. */
22
+ exclude?: readonly string[];
23
+ allowed?: readonly AllowedText[];
24
+ }
25
+ export interface HardCodedTextReport {
26
+ /** Every file that was read, as a path relative to the folder with `/` between the parts. */
27
+ files: string[];
28
+ /** Everything a visitor can read in those files. */
29
+ found: VisibleText[];
30
+ /** One line for each text that is written into a file and is not allowed. */
31
+ loose: string[];
32
+ /** Allowed texts that are gone from their file (take them off the list). */
33
+ unusedAllowances: AllowedText[];
34
+ /** Allowed texts that give no reason. */
35
+ allowancesWithoutReason: AllowedText[];
36
+ }
37
+ /** One line that says where a text is and what it says. */
38
+ export declare function describeVisibleText(folder: string, found: VisibleText): string;
39
+ /** Reads the package and reports the English that is written into its files. */
40
+ export declare function auditHardCodedText(options: HardCodedTextOptions): HardCodedTextReport;
@@ -0,0 +1,27 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { listSourceFiles, listSvelteFiles } from "./syntaxTree.js";
4
+ import { findVisibleText } from "./visibleText.js";
5
+ function relativePath(folder, file) {
6
+ return path.relative(folder, file).split(path.sep).join('/');
7
+ }
8
+ /** One line that says where a text is and what it says. */
9
+ export function describeVisibleText(folder, found) {
10
+ const place = found.where ? ` ${found.where}` : '';
11
+ return `${relativePath(folder, found.file)}:${found.line} [${found.kind}${place}] "${found.text.length > 90 ? `${found.text.slice(0, 90)}…` : found.text}"`;
12
+ }
13
+ /** Reads the package and reports the English that is written into its files. */
14
+ export function auditHardCodedText(options) {
15
+ const excluded = new Set(options.exclude ?? []);
16
+ const allowed = options.allowed ?? [];
17
+ const listed = options.files === 'source' ? listSourceFiles(options.folder) : listSvelteFiles(options.folder);
18
+ const files = listed.filter((file) => !excluded.has(file));
19
+ const found = [];
20
+ for (const file of files)
21
+ found.push(...findVisibleText(file, fs.readFileSync(file, 'utf8'), options));
22
+ const isAllowed = (entry) => allowed.some((candidate) => candidate.file === relativePath(options.folder, entry.file) && candidate.text === entry.text);
23
+ const loose = found.filter((entry) => !isAllowed(entry)).map((entry) => describeVisibleText(options.folder, entry));
24
+ const unusedAllowances = allowed.filter((candidate) => !found.some((entry) => relativePath(options.folder, entry.file) === candidate.file && entry.text === candidate.text));
25
+ const allowancesWithoutReason = allowed.filter((candidate) => candidate.reason.trim().length < 12);
26
+ return { files: files.map((file) => relativePath(options.folder, file)), found, loose, unusedAllowances, allowancesWithoutReason };
27
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Test tooling for the words of a Widgetic package: three audits a package runs in its own tests, so that no English is
3
+ * written into a screen, every message the code asks for is in the catalog, and every design-system component that shows
4
+ * words is given them.
5
+ *
6
+ * import { auditHardCodedText, auditCatalogUsage, auditDesignSystemWords } from '@widgetic/design-system/i18n/audit';
7
+ *
8
+ * const report = auditHardCodedText({ folder: SOURCE_FOLDER, allowed: ALLOWED });
9
+ * expect(report.loose).toEqual([]);
10
+ *
11
+ * It reads the source of a package with Svelte's own parser, so it runs in Node (a test, a build step) and a page never
12
+ * loads it. Nothing here changes what a visitor sees.
13
+ */
14
+ export { auditCatalogUsage, DEFAULT_KEY_SHAPE, type CatalogUsageOptions, type CatalogUsageReport } from './catalogUsage.ts';
15
+ export { auditDesignSystemWords, DEFAULT_COMPONENTS_MODULES, type DesignSystemUse, type DesignSystemWordsOptions, type DesignSystemWordsReport } from './designSystemWords.ts';
16
+ export { auditHardCodedText, describeVisibleText, type AllowedText, type HardCodedTextOptions, type HardCodedTextReport } from './hardCodedText.ts';
17
+ export { DEFAULT_LOOKUPS, findMessageCalls, type MessageCall, type MessageCallOptions, type MessageLookup } from './messageCalls.ts';
18
+ export { listSourceFiles, listSvelteFiles } from './syntaxTree.ts';
19
+ export { DEFAULT_MESSAGE_LOOKUPS, DEFAULT_TEXT_ATTRIBUTES, DEFAULT_TEXT_PROPERTIES, DEFAULT_TEXT_VARIABLE_PATTERN, findVisibleText, type VisibleText, type VisibleTextOptions } from './visibleText.ts';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Test tooling for the words of a Widgetic package: three audits a package runs in its own tests, so that no English is
3
+ * written into a screen, every message the code asks for is in the catalog, and every design-system component that shows
4
+ * words is given them.
5
+ *
6
+ * import { auditHardCodedText, auditCatalogUsage, auditDesignSystemWords } from '@widgetic/design-system/i18n/audit';
7
+ *
8
+ * const report = auditHardCodedText({ folder: SOURCE_FOLDER, allowed: ALLOWED });
9
+ * expect(report.loose).toEqual([]);
10
+ *
11
+ * It reads the source of a package with Svelte's own parser, so it runs in Node (a test, a build step) and a page never
12
+ * loads it. Nothing here changes what a visitor sees.
13
+ */
14
+ export { auditCatalogUsage, DEFAULT_KEY_SHAPE } from "./catalogUsage.js";
15
+ export { auditDesignSystemWords, DEFAULT_COMPONENTS_MODULES } from "./designSystemWords.js";
16
+ export { auditHardCodedText, describeVisibleText } from "./hardCodedText.js";
17
+ export { DEFAULT_LOOKUPS, findMessageCalls } from "./messageCalls.js";
18
+ export { listSourceFiles, listSvelteFiles } from "./syntaxTree.js";
19
+ export { DEFAULT_MESSAGE_LOOKUPS, DEFAULT_TEXT_ATTRIBUTES, DEFAULT_TEXT_PROPERTIES, DEFAULT_TEXT_VARIABLE_PATTERN, findVisibleText } from "./visibleText.js";