@widgetic/design-system 0.5.15 → 0.5.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -0
- package/dist/components/CopyableHeader.svelte +25 -11
- package/dist/components/CopyableHeader.svelte.d.ts +4 -0
- package/dist/components/EmptyState.svelte +1 -1
- package/dist/components/PasswordInput.svelte +2 -2
- package/dist/components/WScrollArea.svelte +2 -2
- package/dist/components/ic/browser/BrowserInputController.svelte +2 -2
- package/dist/components/index.d.ts +1 -0
- package/dist/components/modals/ActionModal.svelte +3 -0
- package/dist/components/modals/ActionModal.svelte.d.ts +1 -0
- package/dist/components/modals/BaseModal.svelte +4 -2
- package/dist/components/modals/BaseModal.svelte.d.ts +1 -0
- package/dist/components/modals/NewItemDialog.svelte +48 -30
- package/dist/components/modals/NewItemDialog.svelte.d.ts +3 -0
- package/dist/components/modals/Portal.svelte +9 -1
- package/dist/components/modals/README.md +34 -1
- package/dist/components/modals/RenameDialog.svelte +29 -9
- package/dist/components/modals/RenameDialog.svelte.d.ts +3 -0
- package/dist/components/modals/messages.d.ts +39 -0
- package/dist/components/modals/messages.js +10 -0
- package/dist/components/navigation/Breadcrumbs.svelte +30 -17
- package/dist/components/navigation/Breadcrumbs.svelte.d.ts +4 -0
- package/dist/components/navigation/types.d.ts +8 -0
- package/dist/components/pdf/PDFViewer.svelte +17 -8
- package/dist/components/pdf/PDFViewer.svelte.d.ts +5 -0
- package/dist/components/search-input/SearchInput.svelte +3 -3
- package/dist/components/shad-editor/colorpicker.svelte +4 -1
- package/dist/components/shad-editor/custom/Extentions/ImageExtention.d.ts +20 -1
- package/dist/components/shad-editor/custom/Extentions/ImageExtention.js +4 -1
- package/dist/components/shad-editor/custom/code-extended.svelte +4 -2
- package/dist/components/shad-editor/custom/image-extended-component.svelte +54 -32
- package/dist/components/shad-editor/editor.css +5 -5
- package/dist/components/shad-editor/i18n/context.d.ts +6 -0
- package/dist/components/shad-editor/i18n/context.js +11 -0
- package/dist/components/shad-editor/i18n/index.d.ts +8 -0
- package/dist/components/shad-editor/i18n/index.js +11 -0
- package/dist/components/shad-editor/i18n/messages.d.ts +109 -0
- package/dist/components/shad-editor/i18n/messages.js +116 -0
- package/dist/components/shad-editor/icons/block-quote.svelte +3 -1
- package/dist/components/shad-editor/icons/bold.svelte +3 -1
- package/dist/components/shad-editor/icons/buttle-list.svelte +3 -1
- package/dist/components/shad-editor/icons/code.svelte +3 -1
- package/dist/components/shad-editor/icons/highlighter.svelte +5 -3
- package/dist/components/shad-editor/icons/image.svelte +7 -5
- package/dist/components/shad-editor/icons/italic.svelte +3 -1
- package/dist/components/shad-editor/icons/link.svelte +8 -6
- package/dist/components/shad-editor/icons/ordered-list.svelte +3 -1
- package/dist/components/shad-editor/icons/quickcolor.svelte +24 -22
- package/dist/components/shad-editor/icons/redo.svelte +3 -1
- package/dist/components/shad-editor/icons/search-replace.svelte +16 -14
- package/dist/components/shad-editor/icons/strikethrough.svelte +3 -1
- package/dist/components/shad-editor/icons/subscript.svelte +3 -1
- package/dist/components/shad-editor/icons/superscript.svelte +3 -1
- package/dist/components/shad-editor/icons/table.svelte +21 -19
- package/dist/components/shad-editor/icons/task-list.svelte +3 -1
- package/dist/components/shad-editor/icons/text.svelte +13 -11
- package/dist/components/shad-editor/icons/textalign.svelte +11 -9
- package/dist/components/shad-editor/icons/textcolor.svelte +5 -3
- package/dist/components/shad-editor/icons/underline.svelte +3 -1
- package/dist/components/shad-editor/icons/undo.svelte +3 -1
- package/dist/components/shad-editor/index.d.ts +1 -0
- package/dist/components/shad-editor/index.js +2 -0
- package/dist/components/shad-editor/shad-editor.svelte +32 -3
- package/dist/components/shad-editor/shad-editor.svelte.d.ts +12 -0
- package/dist/components/textDirection.d.ts +91 -0
- package/dist/components/textDirection.js +99 -0
- package/dist/components/toast/ToasterModal.svelte +49 -14
- package/dist/components/toast/ToasterModal.svelte.d.ts +9 -2
- package/dist/components/toast/toasterRoots.d.ts +23 -0
- package/dist/components/toast/toasterRoots.js +36 -0
- package/dist/components/ui/alert/index.d.ts +2 -2
- package/dist/components/ui/alert/index.js +1 -1
- package/dist/components/ui/breadcrumb/breadcrumb-separator.svelte +1 -1
- package/dist/components/ui/calendar/calendar-next-button.svelte +21 -4
- package/dist/components/ui/calendar/calendar-next-button.svelte.d.ts +9 -2
- package/dist/components/ui/calendar/calendar-prev-button.svelte +21 -4
- package/dist/components/ui/calendar/calendar-prev-button.svelte.d.ts +9 -2
- package/dist/components/ui/calendar/calendar.svelte +18 -6
- package/dist/components/ui/calendar/calendar.svelte.d.ts +6 -1
- package/dist/components/ui/command/command-input.svelte +1 -1
- package/dist/components/ui/command/command-shortcut.svelte +1 -1
- package/dist/components/ui/dialog/dialog-content.svelte +5 -2
- package/dist/components/ui/dialog/dialog-content.svelte.d.ts +1 -0
- package/dist/components/ui/dialog/dialog-header.svelte +1 -1
- package/dist/components/ui/drawer/drawer-header.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +2 -2
- package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +6 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +2 -2
- package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +7 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +2 -2
- package/dist/components/ui/hover-card/hover-card-content.svelte +6 -0
- package/dist/components/ui/input-otp/input-otp-slot.svelte +1 -1
- package/dist/components/ui/popover/popover-content.svelte +6 -0
- package/dist/components/ui/progress/progress.svelte +2 -2
- package/dist/components/ui/range-calendar/range-calendar-cell.svelte +1 -1
- package/dist/components/ui/range-calendar/range-calendar-next-button.svelte +21 -4
- package/dist/components/ui/range-calendar/range-calendar-next-button.svelte.d.ts +9 -2
- package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte +21 -4
- package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte.d.ts +9 -2
- package/dist/components/ui/range-calendar/range-calendar.svelte +18 -6
- package/dist/components/ui/range-calendar/range-calendar.svelte.d.ts +7 -2
- package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +1 -1
- package/dist/components/ui/scroll-area/scroll-area.svelte +6 -1
- package/dist/components/ui/select/select-content.svelte +6 -0
- package/dist/components/ui/select/select-group-heading.svelte +1 -1
- package/dist/components/ui/select/select-item.svelte +2 -2
- package/dist/components/ui/slider/slider.svelte +6 -0
- package/dist/components/ui/switch/switch.svelte +1 -1
- package/dist/components/ui/table/table-cell.svelte +1 -1
- package/dist/components/ui/table/table-head.svelte +1 -1
- package/dist/components/ui/tooltip/tooltip-content.svelte +6 -0
- package/dist/i18n/audit/catalogUsage.d.ts +36 -0
- package/dist/i18n/audit/catalogUsage.js +58 -0
- package/dist/i18n/audit/designSystemWords.d.ts +47 -0
- package/dist/i18n/audit/designSystemWords.js +137 -0
- package/dist/i18n/audit/hardCodedText.d.ts +40 -0
- package/dist/i18n/audit/hardCodedText.js +27 -0
- package/dist/i18n/audit/index.d.ts +19 -0
- package/dist/i18n/audit/index.js +19 -0
- package/dist/i18n/audit/messageCalls.d.ts +34 -0
- package/dist/i18n/audit/messageCalls.js +89 -0
- package/dist/i18n/audit/syntaxTree.d.ts +33 -0
- package/dist/i18n/audit/syntaxTree.js +90 -0
- package/dist/i18n/audit/visibleText.d.ts +57 -0
- package/dist/i18n/audit/visibleText.js +252 -0
- package/dist/i18n/createI18n.d.ts +63 -0
- package/dist/i18n/createI18n.js +72 -0
- package/dist/i18n/direction.d.ts +27 -0
- package/dist/i18n/direction.js +89 -0
- package/dist/i18n/index.d.ts +17 -0
- package/dist/i18n/index.js +16 -0
- package/dist/i18n/localeDate.d.ts +34 -0
- package/dist/i18n/localeDate.js +104 -0
- package/dist/i18n/splitMessages.d.ts +21 -0
- package/dist/i18n/splitMessages.js +33 -0
- package/dist/i18n/translator.d.ts +61 -0
- package/dist/i18n/translator.js +337 -0
- package/dist/rtl/audit/auditPhysicalPlacement.d.ts +36 -0
- package/dist/rtl/audit/auditPhysicalPlacement.js +58 -0
- package/dist/rtl/audit/index.d.ts +14 -0
- package/dist/rtl/audit/index.js +14 -0
- package/dist/rtl/audit/physicalPlacement.d.ts +64 -0
- package/dist/rtl/audit/physicalPlacement.js +444 -0
- package/dist/shad-editor/index.d.ts +1 -1
- package/dist/shad-editor/index.js +1 -1
- package/dist/styles/global.css +4 -4
- package/dist/utils/fillMessage.d.ts +8 -0
- package/dist/utils/fillMessage.js +10 -0
- package/dist/utils/itemName.d.ts +17 -0
- package/dist/utils/itemName.js +41 -0
- package/package.json +18 -1
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds the places where a screen is put in place with a physical side ("left", "right") instead of a logical one
|
|
3
|
+
* ("start", "end"), because a physical side does not follow the direction of the text: on a right-to-left screen a
|
|
4
|
+
* `ml-2` still opens a gap at the left, where the logical `ms-2` opens it at the right.
|
|
5
|
+
*
|
|
6
|
+
* It reads source text only (a class in a template, a style in a `<style>` block or a style attribute), so it runs in a
|
|
7
|
+
* test and a page never loads it. Nothing here changes what a visitor sees.
|
|
8
|
+
*
|
|
9
|
+
* A box put in place from the left or the right edge (`left: 8px`, `style:right={x}`, `element.style.left = ...`) is found
|
|
10
|
+
* in a stylesheet, in a `<style>` block, in a style attribute and in an assignment to `style.left` or `style.right`; the
|
|
11
|
+
* code around it (`rect.left`, `{ left: 3 }` in a script) is not a style and is left alone. A box that is stretched from
|
|
12
|
+
* both edges (`left: 0; right: 0`), centred (`left: 50%`) or given back to the browser (`left: auto`) is the same in both
|
|
13
|
+
* directions and is never reported.
|
|
14
|
+
*
|
|
15
|
+
* A shorthand that names the four sides by their place (`padding: 0 8px 0 4px`, `inset: 0 2rem 0 0`, `border-radius: 4px 0 0 4px`)
|
|
16
|
+
* is found when the length at the left differs from the one at the right (`padding: 4px 8px` and `border-radius: 4px` are the
|
|
17
|
+
* same on both sides and are left alone).
|
|
18
|
+
*
|
|
19
|
+
* Placement that is physical on purpose stays, when the line says why:
|
|
20
|
+
*
|
|
21
|
+
* <!-- rtl-ok: the timeline is a left-to-right island -->
|
|
22
|
+
* <div class="left-0 right-0">
|
|
23
|
+
*
|
|
24
|
+
* The note `rtl-ok` on the line itself or on the line above lets that line through. A few patterns are never reported
|
|
25
|
+
* because they cannot be wrong: centring (`left-1/2` with `-translate-x-1/2`), a class that already names its direction
|
|
26
|
+
* (`rtl:ml-2`, `ltr:ml-2`), and a class that follows the side a pop-up opens on (`data-[side=left]:...`).
|
|
27
|
+
*/
|
|
28
|
+
/** The note that lets a physical placement through; it goes on the line itself or on the line above, with the reason. */
|
|
29
|
+
export const ALLOWED_PLACEMENT_NOTE = 'rtl-ok';
|
|
30
|
+
const LOGICAL_MARGIN_AND_PADDING = { ml: 'ms', mr: 'me', pl: 'ps', pr: 'pe' };
|
|
31
|
+
const LOGICAL_CORNER = { l: 's', r: 'e', tl: 'ss', tr: 'se', bl: 'es', br: 'ee' };
|
|
32
|
+
const CLASS_RULES = [
|
|
33
|
+
{
|
|
34
|
+
pattern: /^(?:scroll-)?(?:ml|mr|pl|pr)-/,
|
|
35
|
+
instead: (utility) => utility.replace(/^(scroll-)?(ml|mr|pl|pr)-/, (_whole, scroll, side) => `${scroll ?? ''}${LOGICAL_MARGIN_AND_PADDING[side]}-`)
|
|
36
|
+
},
|
|
37
|
+
{ pattern: /^(?:left|right)-/, instead: (utility) => utility.replace(/^left-/, 'start-').replace(/^right-/, 'end-') },
|
|
38
|
+
{ pattern: /^text-(?:left|right)$/, instead: (utility) => (utility === 'text-left' ? 'text-start' : 'text-end') },
|
|
39
|
+
{
|
|
40
|
+
pattern: /^rounded-(?:tl|tr|bl|br|l|r)(?:-|$)/,
|
|
41
|
+
instead: (utility) => utility.replace(/^rounded-(tl|tr|bl|br|l|r)/, (_whole, side) => `rounded-${LOGICAL_CORNER[side]}`)
|
|
42
|
+
},
|
|
43
|
+
{ pattern: /^border-(?:l|r)(?:-|$)/, instead: (utility) => utility.replace(/^border-l/, 'border-s').replace(/^border-r/, 'border-e') },
|
|
44
|
+
{ pattern: /^(?:float|clear)-(?:left|right)$/, instead: (utility) => utility.replace('-left', '-start').replace('-right', '-end') }
|
|
45
|
+
];
|
|
46
|
+
/** Moving by a distance to one side only means something in a left-to-right screen. */
|
|
47
|
+
const SHIFT_PATTERN = /^translate-x-/;
|
|
48
|
+
/** Centring is the same in both directions: the pair `left-1/2` and `-translate-x-1/2`, or the same with `50%`. */
|
|
49
|
+
const CENTRING_PATTERN = /^(?:(?:left|right)-(?:1\/2|\[50%\])|translate-x-(?:1\/2|\[-?50%\])|slide-(?:in-from|out-to)-(?:left|right)-1\/2)$/;
|
|
50
|
+
/** An animation that comes from a side is physical; it belongs under the side a pop-up opens on. */
|
|
51
|
+
const SIDE_ANIMATION_PATTERN = /^slide-(?:in-from|out-to)-(?:left|right)(?:-|$)/;
|
|
52
|
+
/** The variant that follows the side a pop-up opens on: what is written under it is meant to be physical. */
|
|
53
|
+
const POPUP_SIDE_VARIANT_PATTERN = /^data-\[side=(?:left|right)\]$/;
|
|
54
|
+
/**
|
|
55
|
+
* The words a stylesheet or a style attribute uses for a side, and what to write instead. A property is the whole word:
|
|
56
|
+
* `margin-left` is one, `grid-margin-left` (the name of a class) and `--card-padding-right` (a custom property) are not.
|
|
57
|
+
*/
|
|
58
|
+
const NOT_PART_OF_A_LONGER_NAME = '(?<![\\w$-])';
|
|
59
|
+
const STYLE_RULES = [
|
|
60
|
+
{ pattern: new RegExp(`${NOT_PART_OF_A_LONGER_NAME}(?:margin|padding)-(?:left|right)\\b`, 'g'), instead: (text) => text.replace('-left', '-inline-start').replace('-right', '-inline-end') },
|
|
61
|
+
{ pattern: new RegExp(`${NOT_PART_OF_A_LONGER_NAME}border-(?:left|right)(?:-(?:width|color|style))?\\b`, 'g'), instead: (text) => text.replace('-left', '-inline-start').replace('-right', '-inline-end') },
|
|
62
|
+
{
|
|
63
|
+
pattern: new RegExp(`${NOT_PART_OF_A_LONGER_NAME}border-(?:top|bottom)-(?:left|right)-radius\\b`, 'g'),
|
|
64
|
+
instead: (text) => text.replace(/-(top|bottom)-(left|right)-/, (_whole, block, side) => `-${block === 'top' ? 'start' : 'end'}-${side === 'left' ? 'start' : 'end'}-`)
|
|
65
|
+
},
|
|
66
|
+
{ pattern: new RegExp(`${NOT_PART_OF_A_LONGER_NAME}(?:margin|padding)(?:Left|Right)\\b`, 'g'), instead: (text) => text.replace('Left', 'InlineStart').replace('Right', 'InlineEnd') },
|
|
67
|
+
{ pattern: new RegExp(`${NOT_PART_OF_A_LONGER_NAME}text-align\\s*:\\s*(?:left|right)\\b`, 'g'), instead: (text) => text.replace(/(left|right)$/, (side) => (side === 'left' ? 'start' : 'end')) },
|
|
68
|
+
{ pattern: new RegExp(`${NOT_PART_OF_A_LONGER_NAME}(?:float|clear)\\s*:\\s*(?:left|right)\\b`, 'g'), instead: (text) => text.replace(/(left|right)$/, (side) => (side === 'left' ? 'inline-start' : 'inline-end')) }
|
|
69
|
+
];
|
|
70
|
+
/**
|
|
71
|
+
* The place a box is put at from a physical edge. These are found only where the words are a style: in a stylesheet, in a
|
|
72
|
+
* `<style>` block, in a style attribute (`style="left: 8px"`, `style:right={x}`, also when it runs over several lines), in an
|
|
73
|
+
* assignment to `style.left` and in a string of a script that is a list of declarations with a length
|
|
74
|
+
* (`` `top: ${top}px; right: ${right}px;` ``).
|
|
75
|
+
*/
|
|
76
|
+
const OFFSET_DECLARATION = new RegExp(`${NOT_PART_OF_A_LONGER_NAME}(left|right)\\s*:\\s*`, 'g');
|
|
77
|
+
const OFFSET_DIRECTIVE = new RegExp(`${NOT_PART_OF_A_LONGER_NAME}style:(left|right)\\b`, 'g');
|
|
78
|
+
const OFFSET_ASSIGNMENT = new RegExp(`${NOT_PART_OF_A_LONGER_NAME}style\\.(left|right)\\s*=(?!=)`, 'g');
|
|
79
|
+
const STYLE_ATTRIBUTE = /(?<![\w$-])style=["'{`]/;
|
|
80
|
+
/**
|
|
81
|
+
* The side a pop-up opens on, named by a place on the screen (`side="left"`, `side={'right'}`). It does not turn around by
|
|
82
|
+
* itself: a pop-up that opens beside a control at the end of the line must open on the left in a right to left screen.
|
|
83
|
+
*/
|
|
84
|
+
const POPUP_SIDE_ATTRIBUTE = new RegExp(`${NOT_PART_OF_A_LONGER_NAME}side=(?:"(left|right)"|'(left|right)'|\\{\\s*["'\`](left|right)["'\`]\\s*\\})`, 'g');
|
|
85
|
+
/**
|
|
86
|
+
* A shorthand that names the sides by their place (`padding: 0 8px 0 4px` is top, right, bottom, left) puts one length at the
|
|
87
|
+
* left and another at the right, and the left stays the left on a right to left screen. It is found where the words are a
|
|
88
|
+
* style, like the offsets above.
|
|
89
|
+
*/
|
|
90
|
+
const POSITIONAL_SHORTHAND = new RegExp(`${NOT_PART_OF_A_LONGER_NAME}(margin|padding|inset|scroll-margin|scroll-padding|border-width|border-color|border-style|border-radius)\\s*:\\s*`, 'g');
|
|
91
|
+
/** What a `left` or a `right` may be set to without moving anything to one side: the browser decides, or it is the middle. */
|
|
92
|
+
const SAME_IN_BOTH_DIRECTIONS = /^(?:auto|50%|inherit|initial|unset|revert)\s*(?:!important)?$/;
|
|
93
|
+
const ZERO_OFFSET = /^0(?:px|rem|em|%)?\s*(?:!important)?$/;
|
|
94
|
+
/** The part of the advice that is the same for every offset: a number measured from the left edge may be meant. */
|
|
95
|
+
const OFFSET_ADVICE = 'a distance from the start of the line is written `inset-inline-start` / `inset-inline-end`; one that was measured from the left edge of the screen, or that an element does not follow, stays with the note `rtl-ok` and the reason';
|
|
96
|
+
/**
|
|
97
|
+
* A comment of the kinds that may be written anywhere in the source.
|
|
98
|
+
*
|
|
99
|
+
* - A block comment starts where a comment can start: at the beginning of a line, after a space or after a mark of the code
|
|
100
|
+
* (`;`, `,`, a bracket, `:`, `>`). A `/` and `*` that follow a letter or a star are part of a word, a path or a mime type
|
|
101
|
+
* (`accept="image/*"`, the glob `src/**` and `/*.ts`) and are not the start of anything.
|
|
102
|
+
* - A line comment (two slashes) runs to the end of the line, and starts after a space or a mark of the code, so that the
|
|
103
|
+
* two slashes of an address (`https://`, `src="//cdn"`) are left alone.
|
|
104
|
+
*/
|
|
105
|
+
const BLOCK_COMMENT = /(?<=^|[\s;,{}()[\]:>])\/\*[\s\S]*?\*\//g;
|
|
106
|
+
const LINE_COMMENT = /(^|[\s;,{}()])\/\/[^\n]*/gm;
|
|
107
|
+
/** A comment that only a template can hold: in a script `<!--` is text (`'<!--'`, `/<!--/`) and must not hide what follows it. */
|
|
108
|
+
const TEMPLATE_COMMENT = /<!--[\s\S]*?-->/g;
|
|
109
|
+
/** A block of a Svelte or HTML file that is not template: its tags, and what it holds up to the closing tag. */
|
|
110
|
+
const SCRIPT_OR_STYLE_BLOCK = /<(script|style)\b[^>]*>[\s\S]*?<\/\1\s*>/gi;
|
|
111
|
+
const blankText = (text) => text.replace(/[^\n]/g, ' ');
|
|
112
|
+
const blankBlockComments = (text) => text.replace(BLOCK_COMMENT, blankText);
|
|
113
|
+
const blankLineComments = (text) => text.replace(LINE_COMMENT, (whole, before) => `${before}${blankText(whole.slice(before.length))}`);
|
|
114
|
+
/**
|
|
115
|
+
* The source with every comment blanked out (a space for each letter, new lines kept), so the lines still count the same
|
|
116
|
+
* and the words in a comment are never taken for a class.
|
|
117
|
+
*
|
|
118
|
+
* What counts as a comment depends on where it is written, because the same characters are text somewhere else: in a
|
|
119
|
+
* template an HTML comment, a block comment and a line comment are comments; in a `<script>` block a block comment and a
|
|
120
|
+
* line comment are, and `<!--` is not (a regular expression or a string may hold it, and it must not hide the lines up to
|
|
121
|
+
* the next `-->`); in a `<style>` block and in a stylesheet only a block comment is. A script file has block and line
|
|
122
|
+
* comments.
|
|
123
|
+
*/
|
|
124
|
+
export function blankComments(source, language = 'markup') {
|
|
125
|
+
if (language === 'stylesheet')
|
|
126
|
+
return blankBlockComments(source);
|
|
127
|
+
if (language === 'script')
|
|
128
|
+
return blankLineComments(blankBlockComments(source));
|
|
129
|
+
const blankTemplate = (text) => blankLineComments(blankBlockComments(text.replace(TEMPLATE_COMMENT, blankText)));
|
|
130
|
+
let result = '';
|
|
131
|
+
let position = 0;
|
|
132
|
+
for (const block of source.matchAll(SCRIPT_OR_STYLE_BLOCK)) {
|
|
133
|
+
const start = block.index ?? 0;
|
|
134
|
+
const openingTag = block[0].match(/^<(?:script|style)\b[^>]*>/i)?.[0] ?? '';
|
|
135
|
+
const closingTag = block[0].match(/<\/(?:script|style)\s*>$/i)?.[0] ?? '';
|
|
136
|
+
const inside = block[0].slice(openingTag.length, block[0].length - closingTag.length);
|
|
137
|
+
const blankedInside = block[1].toLowerCase() === 'style' ? blankBlockComments(inside) : blankLineComments(blankBlockComments(inside));
|
|
138
|
+
result += blankTemplate(source.slice(position, start)) + openingTag + blankedInside + closingTag;
|
|
139
|
+
position = start + block[0].length;
|
|
140
|
+
}
|
|
141
|
+
return result + blankTemplate(source.slice(position));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Splits a word of the source into variants and utility. A colon splits only outside `[...]` and `(...)`, and a word
|
|
145
|
+
* ends where a class cannot continue (`=`, `{`, `}`, `<`, `>`, `,`, `;`) so that `class:pr-10={open}` reads as `pr-10`.
|
|
146
|
+
*/
|
|
147
|
+
function readClassWord(rawWord) {
|
|
148
|
+
const word = rawWord.startsWith('class:') ? rawWord.slice('class:'.length) : rawWord;
|
|
149
|
+
const pieces = [];
|
|
150
|
+
let depth = 0;
|
|
151
|
+
let current = '';
|
|
152
|
+
for (const character of word) {
|
|
153
|
+
if (character === '[' || character === '(')
|
|
154
|
+
depth += 1;
|
|
155
|
+
else if (character === ']' || character === ')')
|
|
156
|
+
depth = Math.max(0, depth - 1);
|
|
157
|
+
if (depth === 0 && character === ':') {
|
|
158
|
+
pieces.push(current);
|
|
159
|
+
current = '';
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (depth === 0 && '={}<>,;'.includes(character))
|
|
163
|
+
break;
|
|
164
|
+
current += character;
|
|
165
|
+
}
|
|
166
|
+
pieces.push(current);
|
|
167
|
+
const lastPiece = pieces[pieces.length - 1];
|
|
168
|
+
const leading = lastPiece.match(/^[!-]*/)?.[0] ?? '';
|
|
169
|
+
const afterLeading = lastPiece.slice(leading.length);
|
|
170
|
+
const trailing = afterLeading.match(/!+$/)?.[0] ?? '';
|
|
171
|
+
const utility = afterLeading.slice(0, afterLeading.length - trailing.length);
|
|
172
|
+
if (!utility)
|
|
173
|
+
return null;
|
|
174
|
+
return { variants: pieces.slice(0, -1), leading, trailing, utility, written: pieces.join(':') };
|
|
175
|
+
}
|
|
176
|
+
/** Whether a line carries the note that lets a physical placement through, on itself or on the line above. */
|
|
177
|
+
function isAllowedByNote(rawLines, lineIndex) {
|
|
178
|
+
return rawLines[lineIndex].includes(ALLOWED_PLACEMENT_NOTE) || (lineIndex > 0 && rawLines[lineIndex - 1].includes(ALLOWED_PLACEMENT_NOTE));
|
|
179
|
+
}
|
|
180
|
+
/** Every physical placement in `source`, in the order the source has them; each with its line and what to write instead. */
|
|
181
|
+
export function findPhysicalPlacement(source, { language = 'markup' } = {}) {
|
|
182
|
+
const rawLines = source.split('\n');
|
|
183
|
+
const codeLines = blankComments(source, language).split('\n');
|
|
184
|
+
const found = [];
|
|
185
|
+
const lineIsInStylesheet = linesInStylesheet(codeLines, language);
|
|
186
|
+
const lineIsInStyleAttribute = linesInStyleAttribute(codeLines.join('\n'));
|
|
187
|
+
const offsets = [];
|
|
188
|
+
codeLines.forEach((codeLine, lineIndex) => {
|
|
189
|
+
if (isAllowedByNote(rawLines, lineIndex))
|
|
190
|
+
return;
|
|
191
|
+
const line = lineIndex + 1;
|
|
192
|
+
const classWords = codeLine
|
|
193
|
+
.split(/[\s"'`]+/)
|
|
194
|
+
.map(readClassWord)
|
|
195
|
+
.filter((word) => word !== null);
|
|
196
|
+
// A shift to one side is let through when the same line has its mirror, written with the `rtl:` variant.
|
|
197
|
+
const lineHasMirroredClass = classWords.some((word) => word.variants.includes('rtl'));
|
|
198
|
+
for (const { variants, leading, trailing, utility, written } of classWords) {
|
|
199
|
+
if (variants.includes('rtl') || variants.includes('ltr'))
|
|
200
|
+
continue;
|
|
201
|
+
if (variants.some((variant) => POPUP_SIDE_VARIANT_PATTERN.test(variant)))
|
|
202
|
+
continue;
|
|
203
|
+
if (CENTRING_PATTERN.test(utility))
|
|
204
|
+
continue;
|
|
205
|
+
if (SHIFT_PATTERN.test(utility)) {
|
|
206
|
+
if (!lineHasMirroredClass)
|
|
207
|
+
found.push({ line, text: written, kind: 'class', suggestion: `\`${written}\` moves to one physical side: pair it with an \`rtl:\` class that moves the other way` });
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (SIDE_ANIMATION_PATTERN.test(utility)) {
|
|
211
|
+
found.push({ line, text: written, kind: 'class', suggestion: `\`${written}\` comes from one physical side: write it under \`data-[side=...]:\` so that it follows the side the pop-up opens on` });
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const rule = CLASS_RULES.find((candidate) => candidate.pattern.test(utility));
|
|
215
|
+
if (rule) {
|
|
216
|
+
const replacement = [...variants, `${leading}${rule.instead(utility)}${trailing}`].join(':');
|
|
217
|
+
found.push({ line, text: written, kind: 'class', suggestion: `write \`${replacement}\` instead of \`${written}\`` });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (const rule of STYLE_RULES) {
|
|
221
|
+
for (const match of codeLine.matchAll(rule.pattern)) {
|
|
222
|
+
found.push({ line, text: match[0], kind: 'style', suggestion: `write \`${rule.instead(match[0])}\` instead of \`${match[0]}\`` });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
for (const match of codeLine.matchAll(POPUP_SIDE_ATTRIBUTE)) {
|
|
226
|
+
const side = match[1] ?? match[2] ?? match[3];
|
|
227
|
+
found.push({
|
|
228
|
+
line,
|
|
229
|
+
text: `side="${side}"`,
|
|
230
|
+
kind: 'attribute',
|
|
231
|
+
suggestion: `\`side="${side}"\` opens the pop-up on the ${side} of the screen: write \`side={${side === 'left' ? 'sideAtStart' : 'sideAtEnd'}($textDirection)}\` for the side at the ${side === 'left' ? 'start' : 'end'} of the line, or say why it must stay with the note \`rtl-ok\``
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
for (const match of codeLine.matchAll(OFFSET_ASSIGNMENT)) {
|
|
235
|
+
found.push({ line, text: `style.${match[1]}`, kind: 'style', suggestion: `\`style.${match[1]}\` puts the box at the ${match[1]} edge: ${OFFSET_ADVICE}` });
|
|
236
|
+
}
|
|
237
|
+
for (const match of codeLine.matchAll(OFFSET_DIRECTIVE)) {
|
|
238
|
+
found.push({ line, text: `style:${match[1]}`, kind: 'style', suggestion: `\`style:${match[1]}\` puts the box at the ${match[1]} edge: ${OFFSET_ADVICE}` });
|
|
239
|
+
}
|
|
240
|
+
const lineIsStyle = lineIsInStylesheet[lineIndex] || lineIsInStyleAttribute[lineIndex] || STYLE_ATTRIBUTE.test(codeLine);
|
|
241
|
+
for (const match of codeLine.matchAll(OFFSET_DECLARATION)) {
|
|
242
|
+
const side = match[1];
|
|
243
|
+
const value = readDeclarationValue(codeLine.slice((match.index ?? 0) + match[0].length));
|
|
244
|
+
if (!lineIsStyle && !isDeclarationInStyleString(codeLine, match.index ?? 0, value))
|
|
245
|
+
continue;
|
|
246
|
+
if (SAME_IN_BOTH_DIRECTIONS.test(value))
|
|
247
|
+
continue;
|
|
248
|
+
offsets.push({ line, side, written: `${side}: ${value}`.trim(), zero: ZERO_OFFSET.test(value) });
|
|
249
|
+
}
|
|
250
|
+
for (const match of codeLine.matchAll(POSITIONAL_SHORTHAND)) {
|
|
251
|
+
const property = match[1];
|
|
252
|
+
const value = readDeclarationValue(codeLine.slice((match.index ?? 0) + match[0].length));
|
|
253
|
+
if (!lineIsStyle && !isDeclarationInStyleString(codeLine, match.index ?? 0, value))
|
|
254
|
+
continue;
|
|
255
|
+
const advice = adviseOnPositionalShorthand(property, value);
|
|
256
|
+
if (advice)
|
|
257
|
+
found.push({ line, text: `${property}: ${value}`.trim(), kind: 'style', suggestion: advice });
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
// A box stretched from both edges (`left: 0` and `right: 0` in the same rule or the same attribute) is the same in both directions.
|
|
261
|
+
for (const offset of offsets) {
|
|
262
|
+
const hasZeroMirror = offset.zero && offsets.some((other) => other.zero && other.side !== offset.side && Math.abs(other.line - offset.line) <= STRETCH_REACH_IN_LINES);
|
|
263
|
+
if (hasZeroMirror)
|
|
264
|
+
continue;
|
|
265
|
+
found.push({ line: offset.line, text: offset.written, kind: 'style', suggestion: `\`${offset.side}\` puts the box at the ${offset.side} edge: ${OFFSET_ADVICE}` });
|
|
266
|
+
}
|
|
267
|
+
return found.sort((first, second) => first.line - second.line);
|
|
268
|
+
}
|
|
269
|
+
/** A length: a number or a `{...}` / `${...}` of a template, followed by a unit that measures a distance. */
|
|
270
|
+
const LENGTH_VALUE = /(?:-?\d*\.?\d+|\$?\{[^}]*\})\s*(?:px|rem|em|%|vw|vh|ch)(?![\w-])/;
|
|
271
|
+
/** What a string holds before a declaration when it is a list of declarations: nothing, or complete ones. */
|
|
272
|
+
const DECLARATIONS_BEFORE = /^(?:\s*[\w-]+\s*:\s*[^;]*;)*\s*$/;
|
|
273
|
+
/**
|
|
274
|
+
* Whether the `left:` or `right:` at `index` is written inside a string of a script that is a list of declarations (a style
|
|
275
|
+
* built as text and handed to an element), as opposed to an object (`{ left: 3 }`), a label or a sentence. The string must
|
|
276
|
+
* hold only complete declarations before it, and the value must be a length.
|
|
277
|
+
*/
|
|
278
|
+
function isDeclarationInStyleString(codeLine, index, value) {
|
|
279
|
+
if (!LENGTH_VALUE.test(value))
|
|
280
|
+
return false;
|
|
281
|
+
let openQuote = null;
|
|
282
|
+
let openAt = -1;
|
|
283
|
+
for (let position = 0; position < index; position += 1) {
|
|
284
|
+
const character = codeLine[position];
|
|
285
|
+
if (openQuote === null) {
|
|
286
|
+
if (character === '"' || character === "'" || character === '`') {
|
|
287
|
+
openQuote = character;
|
|
288
|
+
openAt = position;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else if (character === '\\') {
|
|
292
|
+
position += 1;
|
|
293
|
+
}
|
|
294
|
+
else if (character === openQuote) {
|
|
295
|
+
openQuote = null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (openQuote === null)
|
|
299
|
+
return false;
|
|
300
|
+
return DECLARATIONS_BEFORE.test(codeLine.slice(openAt + 1, index));
|
|
301
|
+
}
|
|
302
|
+
/** How many lines apart `left: 0` and `right: 0` may be and still be one rule. */
|
|
303
|
+
const STRETCH_REACH_IN_LINES = 3;
|
|
304
|
+
/**
|
|
305
|
+
* The value of a declaration, from the text that follows its colon: up to the `;`, the end of the rule (a `}` that closes
|
|
306
|
+
* nothing opened in the value) or the quote that ends a style attribute. A `{...}` of a template (`{mouseX + 20}px`) is part
|
|
307
|
+
* of the value.
|
|
308
|
+
*/
|
|
309
|
+
function readDeclarationValue(rest) {
|
|
310
|
+
let depth = 0;
|
|
311
|
+
let value = '';
|
|
312
|
+
for (const character of rest) {
|
|
313
|
+
if (character === '{')
|
|
314
|
+
depth += 1;
|
|
315
|
+
else if (character === '}') {
|
|
316
|
+
if (depth === 0)
|
|
317
|
+
break;
|
|
318
|
+
depth -= 1;
|
|
319
|
+
}
|
|
320
|
+
else if (depth === 0 && (character === ';' || character === '"' || character === "'" || character === '`'))
|
|
321
|
+
break;
|
|
322
|
+
value += character;
|
|
323
|
+
}
|
|
324
|
+
return value.trim();
|
|
325
|
+
}
|
|
326
|
+
/** The words of a shorthand value, split where there is a space outside a `(...)` or a `{...}`; `!important` is not one of them. */
|
|
327
|
+
function splitShorthandValue(value) {
|
|
328
|
+
const words = [];
|
|
329
|
+
let depth = 0;
|
|
330
|
+
let current = '';
|
|
331
|
+
for (const character of value.replace(/!important\s*$/i, '').trim()) {
|
|
332
|
+
if (character === '(' || character === '{')
|
|
333
|
+
depth += 1;
|
|
334
|
+
else if (character === ')' || character === '}')
|
|
335
|
+
depth = Math.max(0, depth - 1);
|
|
336
|
+
if (depth === 0 && /\s/.test(character)) {
|
|
337
|
+
if (current)
|
|
338
|
+
words.push(current);
|
|
339
|
+
current = '';
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
current += character;
|
|
343
|
+
}
|
|
344
|
+
if (current)
|
|
345
|
+
words.push(current);
|
|
346
|
+
return words;
|
|
347
|
+
}
|
|
348
|
+
/** The property that sets the sides at the start and the end of the line, for each shorthand that names the four sides. */
|
|
349
|
+
const LOGICAL_INLINE_SHORTHAND = {
|
|
350
|
+
margin: 'margin-inline',
|
|
351
|
+
padding: 'padding-inline',
|
|
352
|
+
inset: 'inset-inline',
|
|
353
|
+
'scroll-margin': 'scroll-margin-inline',
|
|
354
|
+
'scroll-padding': 'scroll-padding-inline',
|
|
355
|
+
'border-width': 'border-inline-width',
|
|
356
|
+
'border-color': 'border-inline-color',
|
|
357
|
+
'border-style': 'border-inline-style'
|
|
358
|
+
};
|
|
359
|
+
/** Two lengths are the same when they are written the same, a zero with or without its unit counting as one. */
|
|
360
|
+
function sameLength(first, second) {
|
|
361
|
+
const plain = (length) => (/^0(?:px|rem|em|%)?$/.test(length) ? '0' : length);
|
|
362
|
+
return plain(first) === plain(second);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* What to write instead of a shorthand that puts a different length at the left than at the right, or nothing when the two
|
|
366
|
+
* sides are the same. A box has four sides (`top right bottom left`, with the missing ones copied) and four corners
|
|
367
|
+
* (`top-left top-right bottom-right bottom-left`); only the pairs that sit across the line from each other matter.
|
|
368
|
+
*/
|
|
369
|
+
function adviseOnPositionalShorthand(property, value) {
|
|
370
|
+
if (property === 'border-radius') {
|
|
371
|
+
// A slash separates the horizontal radii from the vertical ones; each half is read as a list of corners.
|
|
372
|
+
const halves = value.split('/').map((half) => splitShorthandValue(half));
|
|
373
|
+
const differs = halves.some((corners) => {
|
|
374
|
+
if (corners.length === 2)
|
|
375
|
+
return !sameLength(corners[0], corners[1]);
|
|
376
|
+
if (corners.length === 3)
|
|
377
|
+
return !sameLength(corners[0], corners[1]) || !sameLength(corners[1], corners[2]);
|
|
378
|
+
if (corners.length === 4)
|
|
379
|
+
return !sameLength(corners[0], corners[1]) || !sameLength(corners[3], corners[2]);
|
|
380
|
+
return false;
|
|
381
|
+
});
|
|
382
|
+
return differs
|
|
383
|
+
? `\`border-radius: ${value}\` rounds the left corners differently from the right ones: write \`border-start-start-radius\`, \`border-start-end-radius\`, \`border-end-end-radius\` and \`border-end-start-radius\` for the corners at the start and the end of the line`
|
|
384
|
+
: null;
|
|
385
|
+
}
|
|
386
|
+
const sides = splitShorthandValue(value);
|
|
387
|
+
if (sides.length !== 4 || sameLength(sides[1], sides[3]))
|
|
388
|
+
return null;
|
|
389
|
+
const logical = LOGICAL_INLINE_SHORTHAND[property] ?? `${property}-inline`;
|
|
390
|
+
return `\`${property}: ${value}\` names the sides by their place: the second length is the right and the fourth the left, so on a right to left screen they are on the wrong sides: write \`${logical}: ${sides[3]} ${sides[1]}\` (start, then end) and the top and bottom lengths on their own`;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* For each line, whether it is inside the value of a style attribute that began on the line before it or on this one
|
|
394
|
+
* (`style="` ... `"`, or `style={` ... `}`, over several lines). The end of the value is the quote that closes it outside
|
|
395
|
+
* any `{...}` of an expression, so a quote of an expression (`{open ? 'a' : 'b'}`) does not end it.
|
|
396
|
+
*/
|
|
397
|
+
function linesInStyleAttribute(source) {
|
|
398
|
+
const lineCount = source.split('\n').length;
|
|
399
|
+
const inside = new Array(lineCount).fill(false);
|
|
400
|
+
const lineOfIndex = (index) => source.slice(0, index).split('\n').length - 1;
|
|
401
|
+
for (const opening of source.matchAll(new RegExp(STYLE_ATTRIBUTE.source, 'g'))) {
|
|
402
|
+
const valueStart = (opening.index ?? 0) + opening[0].length - 1;
|
|
403
|
+
const opener = source[valueStart];
|
|
404
|
+
const closer = opener === '{' ? '}' : opener;
|
|
405
|
+
let depth = 0;
|
|
406
|
+
let end = -1;
|
|
407
|
+
for (let position = valueStart + 1; position < source.length; position += 1) {
|
|
408
|
+
const character = source[position];
|
|
409
|
+
if (character === '{')
|
|
410
|
+
depth += 1;
|
|
411
|
+
else if (character === '}' && depth > 0)
|
|
412
|
+
depth -= 1;
|
|
413
|
+
else if (character === closer && depth === 0) {
|
|
414
|
+
end = position;
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (end === -1)
|
|
419
|
+
continue;
|
|
420
|
+
for (let line = lineOfIndex(valueStart); line <= lineOfIndex(end); line += 1)
|
|
421
|
+
inside[line] = true;
|
|
422
|
+
}
|
|
423
|
+
return inside;
|
|
424
|
+
}
|
|
425
|
+
/** For each line, whether it is a line of a stylesheet: in a `.css` file every line is, in a markup file those of a `<style>` block. */
|
|
426
|
+
function linesInStylesheet(codeLines, language) {
|
|
427
|
+
if (language === 'stylesheet')
|
|
428
|
+
return codeLines.map(() => true);
|
|
429
|
+
if (language === 'script')
|
|
430
|
+
return codeLines.map(() => false);
|
|
431
|
+
let insideBlock = false;
|
|
432
|
+
return codeLines.map((codeLine) => {
|
|
433
|
+
if (/<style\b/.test(codeLine))
|
|
434
|
+
insideBlock = true;
|
|
435
|
+
const insideThisLine = insideBlock;
|
|
436
|
+
if (/<\/style\s*>/.test(codeLine))
|
|
437
|
+
insideBlock = false;
|
|
438
|
+
return insideThisLine;
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
/** A placement as one line of a report: where it is, what it is and what to do. */
|
|
442
|
+
export function describePhysicalPlacement(file, placement) {
|
|
443
|
+
return `${file}:${placement.line} ${placement.text} - ${placement.suggestion}`;
|
|
444
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { ShadEditor, type TipTapEditor, type TipTapEditorLevel } from '../components/shad-editor';
|
|
1
|
+
export { ShadEditor, SHAD_EDITOR_MESSAGES, checkShadEditorMessages, type ShadEditorMessageKey, type TipTapEditor, type TipTapEditorLevel } from '../components/shad-editor';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { ShadEditor } from '../components/shad-editor';
|
|
1
|
+
export { ShadEditor, SHAD_EDITOR_MESSAGES, checkShadEditorMessages } from '../components/shad-editor';
|
package/dist/styles/global.css
CHANGED
|
@@ -291,7 +291,7 @@ go 3 levels up in development mode, 2 levels up in production mode*/
|
|
|
291
291
|
width: 100%;
|
|
292
292
|
position: absolute;
|
|
293
293
|
top: 0;
|
|
294
|
-
|
|
294
|
+
inset-inline-start: 0;
|
|
295
295
|
pointer-events: none;
|
|
296
296
|
}
|
|
297
297
|
|
|
@@ -634,14 +634,14 @@ go 3 levels up in development mode, 2 levels up in production mode*/
|
|
|
634
634
|
.modal-close {
|
|
635
635
|
@apply fixed z-50;
|
|
636
636
|
top: 16px;
|
|
637
|
-
|
|
637
|
+
inset-inline-end: 16px;
|
|
638
638
|
width: 40px;
|
|
639
639
|
height: 40px;
|
|
640
640
|
}
|
|
641
641
|
|
|
642
642
|
/* Modal Button Container */
|
|
643
643
|
.modal-actions {
|
|
644
|
-
@apply flex justify-end gap-4 absolute bottom-6
|
|
644
|
+
@apply flex justify-end gap-4 absolute bottom-6 end-6;
|
|
645
645
|
}
|
|
646
646
|
|
|
647
647
|
/* Modal Button Styles */
|
|
@@ -790,7 +790,7 @@ go 3 levels up in development mode, 2 levels up in production mode*/
|
|
|
790
790
|
}
|
|
791
791
|
|
|
792
792
|
.dropdown-menu-item {
|
|
793
|
-
@apply !flex !items-center !gap-2 !w-full !text-
|
|
793
|
+
@apply !flex !items-center !gap-2 !w-full !text-start !h6 !text-[hsl(var(--grey7))] !whitespace-nowrap;
|
|
794
794
|
padding: 8px 16px;
|
|
795
795
|
}
|
|
796
796
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Puts values into a message: `{max}` becomes the value called `max`. A name that has no value stays as it was
|
|
3
|
+
* written, so a message with a typo shows the typo rather than "undefined".
|
|
4
|
+
*
|
|
5
|
+
* Used by the components whose words a page can replace (see `messages` of RenameDialog and NewItemDialog), so a
|
|
6
|
+
* translated sentence can put a number or a name wherever its grammar needs it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function fillMessage(template: string, values: Record<string, string | number>): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Puts values into a message: `{max}` becomes the value called `max`. A name that has no value stays as it was
|
|
3
|
+
* written, so a message with a typo shows the typo rather than "undefined".
|
|
4
|
+
*
|
|
5
|
+
* Used by the components whose words a page can replace (see `messages` of RenameDialog and NewItemDialog), so a
|
|
6
|
+
* translated sentence can put a number or a name wherever its grammar needs it.
|
|
7
|
+
*/
|
|
8
|
+
export function fillMessage(template, values) {
|
|
9
|
+
return template.replace(/\{(\w+)\}/g, (placeholder, name) => Object.prototype.hasOwnProperty.call(values, name) ? String(values[name]) : placeholder);
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a name given to a new item (a folder, a project...) may be made of.
|
|
3
|
+
*
|
|
4
|
+
* Letters, numbers and combining marks of ANY script, and white space. A German "ü", a Japanese or Arabic name, a
|
|
5
|
+
* Cyrillic "Фото 2024" are fine; punctuation and symbols are not. Combining marks are part of the rule because many
|
|
6
|
+
* scripts write one letter with a base character and a mark ("é" can be "e" followed by a combining accent, and
|
|
7
|
+
* Hindi and Thai vowels are marks). The two invisible joiners (U+200C zero width non-joiner, U+200D zero width
|
|
8
|
+
* joiner) are allowed too: Persian, Hindi and several other scripts need them to spell a word correctly.
|
|
9
|
+
*/
|
|
10
|
+
/** What is wrong with a name. `null` means nothing. */
|
|
11
|
+
export type ItemNameProblem = 'required' | 'invalidCharacters';
|
|
12
|
+
/** True when the name has a letter or a number and only allowed characters (white space around it is ignored). */
|
|
13
|
+
export declare function isValidItemName(name: string): boolean;
|
|
14
|
+
/** What is wrong with a name, or `null` when it is fine. */
|
|
15
|
+
export declare function findItemNameProblem(name: string): ItemNameProblem | null;
|
|
16
|
+
/** The text without the characters a name may not have. Used while the visitor types or pastes. */
|
|
17
|
+
export declare function keepAllowedNameCharacters(text: string): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a name given to a new item (a folder, a project...) may be made of.
|
|
3
|
+
*
|
|
4
|
+
* Letters, numbers and combining marks of ANY script, and white space. A German "ü", a Japanese or Arabic name, a
|
|
5
|
+
* Cyrillic "Фото 2024" are fine; punctuation and symbols are not. Combining marks are part of the rule because many
|
|
6
|
+
* scripts write one letter with a base character and a mark ("é" can be "e" followed by a combining accent, and
|
|
7
|
+
* Hindi and Thai vowels are marks). The two invisible joiners (U+200C zero width non-joiner, U+200D zero width
|
|
8
|
+
* joiner) are allowed too: Persian, Hindi and several other scripts need them to spell a word correctly.
|
|
9
|
+
*/
|
|
10
|
+
const ALLOWED_NAME_PATTERN = /^[\p{L}\p{N}\p{M}\s\u200C\u200D]+$/u;
|
|
11
|
+
const NOT_ALLOWED_CHARACTERS_PATTERN = /[^\p{L}\p{N}\p{M}\s\u200C\u200D]/gu;
|
|
12
|
+
// A name that is only marks, joiners and white space looks empty
|
|
13
|
+
const LETTER_OR_NUMBER_PATTERN = /[\p{L}\p{N}]/u;
|
|
14
|
+
/** True when the name has a letter or a number and only allowed characters (white space around it is ignored). */
|
|
15
|
+
export function isValidItemName(name) {
|
|
16
|
+
if (!name || typeof name !== 'string')
|
|
17
|
+
return false;
|
|
18
|
+
const trimmed = name.trim();
|
|
19
|
+
if (trimmed.length === 0)
|
|
20
|
+
return false;
|
|
21
|
+
return ALLOWED_NAME_PATTERN.test(trimmed) && LETTER_OR_NUMBER_PATTERN.test(trimmed);
|
|
22
|
+
}
|
|
23
|
+
/** What is wrong with a name, or `null` when it is fine. */
|
|
24
|
+
export function findItemNameProblem(name) {
|
|
25
|
+
if (!name || typeof name !== 'string')
|
|
26
|
+
return 'required';
|
|
27
|
+
const trimmed = name.trim();
|
|
28
|
+
if (trimmed.length === 0)
|
|
29
|
+
return 'required';
|
|
30
|
+
// A character the dialog does not accept is the clearer thing to tell the visitor about
|
|
31
|
+
if (!ALLOWED_NAME_PATTERN.test(trimmed))
|
|
32
|
+
return 'invalidCharacters';
|
|
33
|
+
// Only marks and joiners: the name looks empty
|
|
34
|
+
if (!LETTER_OR_NUMBER_PATTERN.test(trimmed))
|
|
35
|
+
return 'required';
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/** The text without the characters a name may not have. Used while the visitor types or pastes. */
|
|
39
|
+
export function keepAllowedNameCharacters(text) {
|
|
40
|
+
return text.replace(NOT_ALLOWED_CHARACTERS_PATTERN, '');
|
|
41
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@widgetic/design-system",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"svelte": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"check:watch": "NODE_OPTIONS='--max-old-space-size=8192' svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
40
40
|
"check:strict": "tsc --noEmit && npm run check",
|
|
41
41
|
"check:deps": "check-runtime-deps",
|
|
42
|
+
"test": "node --test \"tests/**/*.test.ts\"",
|
|
42
43
|
"test:deps": "check-runtime-deps-fixtures",
|
|
43
44
|
"pnpm-link": "zsh -c 'mkdir -p ~/.pnpm-global/bin && export PATH=\"$PATH:$HOME/.pnpm-global/bin\" && pnpm install -g pnpm && pnpm config set global-bin-dir ~/.pnpm-global/bin && echo \"export PATH=\\\"$PATH:$HOME/.pnpm-global/bin\\\"\" >> ~/.zshrc && source ~/.zshrc && pnpm link --global'",
|
|
44
45
|
"link-packs": "zsh -c 'mkdir -p ~/.pnpm-global/bin && export PATH=\"$PATH:$HOME/.pnpm-global/bin\" && pnpm install -g pnpm && pnpm config set global-bin-dir ~/.pnpm-global/bin && echo \"export PATH=\\\"$PATH:$HOME/.pnpm-global/bin\\\"\" >> ~/.zshrc && source ~/.zshrc && pnpm link --global @widgetic/api-sdk @widgetic/design-system'",
|
|
@@ -85,6 +86,19 @@
|
|
|
85
86
|
"svelte": "./dist/icons/*.js",
|
|
86
87
|
"default": "./dist/icons/*.js"
|
|
87
88
|
},
|
|
89
|
+
"./i18n": {
|
|
90
|
+
"types": "./dist/i18n/index.d.ts",
|
|
91
|
+
"svelte": "./dist/i18n/index.js",
|
|
92
|
+
"default": "./dist/i18n/index.js"
|
|
93
|
+
},
|
|
94
|
+
"./i18n/audit": {
|
|
95
|
+
"types": "./dist/i18n/audit/index.d.ts",
|
|
96
|
+
"default": "./dist/i18n/audit/index.js"
|
|
97
|
+
},
|
|
98
|
+
"./rtl/audit": {
|
|
99
|
+
"types": "./dist/rtl/audit/index.d.ts",
|
|
100
|
+
"default": "./dist/rtl/audit/index.js"
|
|
101
|
+
},
|
|
88
102
|
"./stores": {
|
|
89
103
|
"types": "./dist/stores/index.d.ts",
|
|
90
104
|
"svelte": "./dist/stores/index.js",
|
|
@@ -295,6 +309,9 @@
|
|
|
295
309
|
"optional": true
|
|
296
310
|
}
|
|
297
311
|
},
|
|
312
|
+
"overrides": {
|
|
313
|
+
"shell-quote": "^1.12.0"
|
|
314
|
+
},
|
|
298
315
|
"volta": {
|
|
299
316
|
"node": "22.14.0",
|
|
300
317
|
"npm": "11.1.0"
|