@nadicodeai/ui 12.1.165 → 12.1.166
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/AGENTS.md +8 -9
- package/README.md +2 -17
- package/dist/components/accordion.d.ts.map +1 -1
- package/dist/components/accordion.js +2 -1
- package/dist/components/account-menu.d.ts.map +1 -1
- package/dist/components/account-menu.js +5 -3
- package/dist/components/activity-calendar.d.ts.map +1 -1
- package/dist/components/activity-calendar.js +3 -2
- package/dist/components/agent-card.js +1 -1
- package/dist/components/agent-orb.d.ts.map +1 -1
- package/dist/components/agent-orb.js +1 -0
- package/dist/components/agent-profile.d.ts.map +1 -1
- package/dist/components/agent-profile.js +7 -4
- package/dist/components/alert-dialog.d.ts +1 -1
- package/dist/components/alert-dialog.d.ts.map +1 -1
- package/dist/components/alert-dialog.js +3 -3
- package/dist/components/app-mark.js +1 -1
- package/dist/components/avatar.js +8 -8
- package/dist/components/badge.d.ts +11 -16
- package/dist/components/badge.d.ts.map +1 -1
- package/dist/components/badge.js +7 -43
- package/dist/components/breadcrumb.d.ts.map +1 -1
- package/dist/components/breadcrumb.js +3 -2
- package/dist/components/button.d.ts +5 -21
- package/dist/components/button.d.ts.map +1 -1
- package/dist/components/button.js +6 -76
- package/dist/components/calendar.d.ts +1 -1
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +3 -3
- package/dist/components/card-row.js +1 -1
- package/dist/components/card.d.ts +4 -1
- package/dist/components/card.d.ts.map +1 -1
- package/dist/components/card.js +3 -2
- package/dist/components/catalog-drawer.d.ts.map +1 -1
- package/dist/components/catalog-drawer.js +3 -2
- package/dist/components/chart-axis.d.ts.map +1 -1
- package/dist/components/chart-axis.js +0 -2
- package/dist/components/chart-bar-horizontal.d.ts.map +1 -1
- package/dist/components/chart-bar-horizontal.js +16 -14
- package/dist/components/chart-card.d.ts.map +1 -1
- package/dist/components/chart-card.js +0 -1
- package/dist/components/chart.d.ts.map +1 -1
- package/dist/components/chart.js +18 -32
- package/dist/components/checkbox.d.ts +3 -2
- package/dist/components/checkbox.d.ts.map +1 -1
- package/dist/components/checkbox.js +4 -3
- package/dist/components/choicebox.d.ts +5 -5
- package/dist/components/choicebox.d.ts.map +1 -1
- package/dist/components/choicebox.js +6 -5
- package/dist/components/clearable-input.d.ts.map +1 -1
- package/dist/components/clearable-input.js +2 -2
- package/dist/components/code-block.d.ts.map +1 -1
- package/dist/components/code-block.js +4 -2
- package/dist/components/collapsible.js +1 -1
- package/dist/components/collection-filters.d.ts.map +1 -1
- package/dist/components/collection-filters.js +1 -0
- package/dist/components/collection-page.js +1 -1
- package/dist/components/collection-sort.d.ts.map +1 -1
- package/dist/components/collection-sort.js +1 -4
- package/dist/components/collection-toolbar.js +1 -1
- package/dist/components/combobox.js +4 -4
- package/dist/components/command.d.ts.map +1 -1
- package/dist/components/command.js +1 -1
- package/dist/components/confirm-dialog.js +2 -2
- package/dist/components/context-menu.d.ts +1 -1
- package/dist/components/context-menu.d.ts.map +1 -1
- package/dist/components/copy-button.js +1 -1
- package/dist/components/data-table.d.ts +11 -3
- package/dist/components/data-table.d.ts.map +1 -1
- package/dist/components/data-table.js +12 -4
- package/dist/components/dialog.d.ts +1 -1
- package/dist/components/dialog.d.ts.map +1 -1
- package/dist/components/dialog.js +2 -2
- package/dist/components/drawer.d.ts +4 -4
- package/dist/components/drawer.d.ts.map +1 -1
- package/dist/components/drawer.js +14 -15
- package/dist/components/dropdown-menu.d.ts +1 -1
- package/dist/components/dropdown-menu.d.ts.map +1 -1
- package/dist/components/dropdown-menu.js +9 -5
- package/dist/components/empty-art.js +1 -1
- package/dist/components/empty-state.js +2 -2
- package/dist/components/entry-scene.d.ts.map +1 -1
- package/dist/components/entry-scene.js +2 -3
- package/dist/components/field.js +3 -3
- package/dist/components/file-diff.d.ts.map +1 -1
- package/dist/components/file-diff.js +5 -2
- package/dist/components/footer.d.ts +1 -1
- package/dist/components/footer.d.ts.map +1 -1
- package/dist/components/footer.js +3 -3
- package/dist/components/form-dialog.d.ts.map +1 -1
- package/dist/components/form-dialog.js +2 -1
- package/dist/components/form-drawer.d.ts +7 -9
- package/dist/components/form-drawer.d.ts.map +1 -1
- package/dist/components/form-drawer.js +5 -5
- package/dist/components/form.d.ts +26 -0
- package/dist/components/form.d.ts.map +1 -0
- package/dist/components/form.js +29 -0
- package/dist/components/gauge.js +2 -2
- package/dist/components/heatmap/calendar-heatmap.d.ts.map +1 -1
- package/dist/components/heatmap/calendar-heatmap.js +15 -13
- package/dist/components/hive-scene.d.ts.map +1 -1
- package/dist/components/hive-scene.js +3 -1
- package/dist/components/hover-card.d.ts +1 -1
- package/dist/components/hover-card.d.ts.map +1 -1
- package/dist/components/hover-card.js +1 -1
- package/dist/components/input-group.d.ts +5 -6
- package/dist/components/input-group.d.ts.map +1 -1
- package/dist/components/input-group.js +8 -11
- package/dist/components/input-otp.d.ts.map +1 -1
- package/dist/components/input-otp.js +2 -2
- package/dist/components/item.d.ts +1 -1
- package/dist/components/item.js +2 -2
- package/dist/components/kbd.d.ts +6 -9
- package/dist/components/kbd.d.ts.map +1 -1
- package/dist/components/kbd.js +7 -3
- package/dist/components/message-notice.js +1 -1
- package/dist/components/middle-truncate.d.ts.map +1 -1
- package/dist/components/middle-truncate.js +1 -0
- package/dist/components/model-identity.d.ts +4 -4
- package/dist/components/model-identity.d.ts.map +1 -1
- package/dist/components/model-identity.js +2 -2
- package/dist/components/model-mark.d.ts.map +1 -1
- package/dist/components/model-mark.js +4 -2
- package/dist/components/multi-select.js +2 -2
- package/dist/components/native-select.d.ts.map +1 -1
- package/dist/components/native-select.js +2 -6
- package/dist/components/notice-banner.js +1 -1
- package/dist/components/object-page.js +2 -2
- package/dist/components/object-workspace.d.ts.map +1 -1
- package/dist/components/object-workspace.js +1 -3
- package/dist/components/overview-page.d.ts +1 -1
- package/dist/components/overview-page.d.ts.map +1 -1
- package/dist/components/overview-page.js +2 -1
- package/dist/components/page-actions.d.ts.map +1 -1
- package/dist/components/page-actions.js +9 -4
- package/dist/components/pairing-code.js +1 -1
- package/dist/components/pending-label.d.ts.map +1 -1
- package/dist/components/pending-label.js +0 -1
- package/dist/components/popover.d.ts +1 -1
- package/dist/components/popover.d.ts.map +1 -1
- package/dist/components/portal-sidebar-frame.d.ts.map +1 -1
- package/dist/components/portal-sidebar-frame.js +1 -1
- package/dist/components/product-message.d.ts.map +1 -1
- package/dist/components/product-message.js +1 -0
- package/dist/components/progress-checklist.d.ts.map +1 -1
- package/dist/components/progress-checklist.js +2 -2
- package/dist/components/progress.d.ts.map +1 -1
- package/dist/components/progress.js +5 -4
- package/dist/components/radio-group.js +2 -2
- package/dist/components/reading-column.js +1 -1
- package/dist/components/record-identity.d.ts.map +1 -1
- package/dist/components/record-identity.js +2 -5
- package/dist/components/record-list.d.ts.map +1 -1
- package/dist/components/record-list.js +4 -3
- package/dist/components/record-sheet.js +1 -1
- package/dist/components/record-time.d.ts.map +1 -1
- package/dist/components/record-time.js +2 -1
- package/dist/components/resizable.d.ts.map +1 -1
- package/dist/components/resizable.js +1 -1
- package/dist/components/row-edit-form.d.ts +2 -1
- package/dist/components/row-edit-form.d.ts.map +1 -1
- package/dist/components/row-edit-form.js +2 -1
- package/dist/components/run-timeline.js +1 -1
- package/dist/components/scope-switcher.js +1 -1
- package/dist/components/segmented-control.js +1 -1
- package/dist/components/select.js +1 -1
- package/dist/components/settings-facts.js +1 -1
- package/dist/components/settings-section.js +1 -1
- package/dist/components/sheet.d.ts +1 -1
- package/dist/components/sheet.d.ts.map +1 -1
- package/dist/components/sheet.js +1 -1
- package/dist/components/sidebar.d.ts +2 -2
- package/dist/components/sidebar.d.ts.map +1 -1
- package/dist/components/sidebar.js +16 -18
- package/dist/components/site-menu.d.ts +28 -0
- package/dist/components/site-menu.d.ts.map +1 -0
- package/dist/components/site-menu.js +23 -0
- package/dist/components/slider.d.ts.map +1 -1
- package/dist/components/slider.js +2 -1
- package/dist/components/snippet.d.ts +2 -0
- package/dist/components/snippet.d.ts.map +1 -1
- package/dist/components/snippet.js +13 -6
- package/dist/components/sonner.d.ts.map +1 -1
- package/dist/components/sonner.js +3 -4
- package/dist/components/status-badge.d.ts +1 -1
- package/dist/components/status-badge.d.ts.map +1 -1
- package/dist/components/switch.js +1 -1
- package/dist/components/table-card.js +1 -1
- package/dist/components/table.js +4 -4
- package/dist/components/tabs-nav.d.ts +52 -0
- package/dist/components/tabs-nav.d.ts.map +1 -0
- package/dist/components/tabs-nav.js +95 -0
- package/dist/components/tabs.d.ts +2 -56
- package/dist/components/tabs.d.ts.map +1 -1
- package/dist/components/tabs.js +2 -91
- package/dist/components/text-link.d.ts +1 -2
- package/dist/components/text-link.d.ts.map +1 -1
- package/dist/components/text-link.js +1 -2
- package/dist/components/textarea.d.ts +5 -2
- package/dist/components/textarea.d.ts.map +1 -1
- package/dist/components/textarea.js +12 -4
- package/dist/components/toast.d.ts +2 -0
- package/dist/components/toast.d.ts.map +1 -1
- package/dist/components/toast.js +4 -0
- package/dist/components/toggle-group.d.ts +3 -1
- package/dist/components/toggle-group.d.ts.map +1 -1
- package/dist/components/toggle-group.js +3 -6
- package/dist/components/toggle.js +1 -1
- package/dist/components/tooltip.d.ts +12 -12
- package/dist/components/tooltip.d.ts.map +1 -1
- package/dist/components/tooltip.js +9 -9
- package/dist/components/trend-badge.js +2 -2
- package/dist/components/update-card.js +1 -1
- package/dist/components/usage-meter.d.ts +1 -1
- package/dist/components/usage-meter.d.ts.map +1 -1
- package/dist/components/usage-meter.js +9 -7
- package/dist/components/waiting-line.js +1 -1
- package/dist/index.d.ts +3 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -7
- package/dist/internal/agent-orb-motion.d.ts +4 -1
- package/dist/internal/agent-orb-motion.d.ts.map +1 -1
- package/dist/internal/agent-orb-motion.js +9 -7
- package/dist/internal/agent-orb.d.ts +4 -1
- package/dist/internal/agent-orb.d.ts.map +1 -1
- package/dist/internal/agent-orb.js +7 -1
- package/dist/internal/assistant-ui/attachment.d.ts.map +1 -1
- package/dist/internal/assistant-ui/attachment.js +15 -15
- package/dist/internal/assistant-ui/follow-up-suggestions.js +1 -1
- package/dist/internal/assistant-ui/markdown-text.js +26 -25
- package/dist/internal/assistant-ui/reasoning.d.ts.map +1 -1
- package/dist/internal/assistant-ui/reasoning.js +13 -16
- package/dist/internal/assistant-ui/thread.d.ts.map +1 -1
- package/dist/internal/assistant-ui/thread.js +22 -16
- package/dist/internal/assistant-ui/tool-fallback.d.ts.map +1 -1
- package/dist/internal/assistant-ui/tool-fallback.js +29 -21
- package/dist/internal/assistant-ui/tool-group.d.ts.map +1 -1
- package/dist/internal/assistant-ui/tool-group.js +6 -7
- package/dist/internal/assistant-ui/tooltip-icon-button.js +1 -1
- package/dist/internal/badge-frame.d.ts +15 -0
- package/dist/internal/badge-frame.d.ts.map +1 -0
- package/dist/internal/badge-frame.js +44 -0
- package/dist/internal/button-classes.d.ts +20 -0
- package/dist/internal/button-classes.d.ts.map +1 -0
- package/dist/internal/button-classes.js +74 -0
- package/dist/internal/catalog-list.d.ts.map +1 -1
- package/dist/internal/catalog-list.js +8 -4
- package/dist/internal/collection-rail.js +3 -3
- package/dist/internal/form-drawer-frame.d.ts +10 -0
- package/dist/internal/form-drawer-frame.d.ts.map +1 -0
- package/dist/internal/form-drawer-frame.js +9 -0
- package/dist/internal/form-submit.d.ts +2 -1
- package/dist/internal/form-submit.d.ts.map +1 -1
- package/dist/internal/settings-save.d.ts.map +1 -1
- package/dist/internal/settings-save.js +4 -2
- package/dist/internal/status-mark.d.ts.map +1 -1
- package/dist/internal/status-mark.js +4 -1
- package/dist/internal/tab-row.d.ts +10 -0
- package/dist/internal/tab-row.d.ts.map +1 -0
- package/dist/internal/tab-row.js +2 -0
- package/dist/internal/table-compact-line.d.ts +1 -1
- package/dist/internal/table-compact-line.d.ts.map +1 -1
- package/dist/internal/table-compact-line.js +1 -1
- package/dist/internal/tooltip-content-frame.d.ts +14 -0
- package/dist/internal/tooltip-content-frame.d.ts.map +1 -0
- package/dist/internal/tooltip-content-frame.js +11 -0
- package/dist/lib/css-variables.d.ts +8 -0
- package/dist/lib/css-variables.d.ts.map +1 -0
- package/dist/lib/css-variables.js +4 -0
- package/dist/lib/merge-tokens.generated.js +1 -1
- package/dist/lib/use-copy-to-clipboard.d.ts.map +1 -1
- package/dist/lib/use-copy-to-clipboard.js +1 -2
- package/dist/showcase/samples.js +1 -1
- package/dist/showcase/specimens/choices.d.ts.map +1 -1
- package/dist/showcase/specimens/choices.js +3 -2
- package/dist/showcase/specimens/compositions.d.ts.map +1 -1
- package/dist/showcase/specimens/compositions.js +7 -5
- package/dist/showcase/specimens/data.d.ts.map +1 -1
- package/dist/showcase/specimens/data.js +1 -1
- package/dist/showcase/specimens/feedback.d.ts.map +1 -1
- package/dist/showcase/specimens/feedback.js +1 -2
- package/dist/showcase/specimens/index.d.ts +2 -0
- package/dist/showcase/specimens/index.d.ts.map +1 -1
- package/dist/showcase/specimens/inputs.d.ts +1 -0
- package/dist/showcase/specimens/inputs.d.ts.map +1 -1
- package/dist/showcase/specimens/inputs.js +5 -2
- package/dist/showcase/specimens/menus.d.ts +1 -0
- package/dist/showcase/specimens/menus.d.ts.map +1 -1
- package/dist/showcase/specimens/menus.js +7 -2
- package/dist/showcase/specimens/navigation.d.ts.map +1 -1
- package/dist/showcase/specimens/navigation.js +6 -5
- package/dist/showcase/specimens/status.d.ts.map +1 -1
- package/dist/showcase/specimens/status.js +4 -4
- package/dist/showcase/usage.generated.d.ts +1 -1
- package/dist/showcase/usage.generated.d.ts.map +1 -1
- package/dist/showcase/usage.generated.js +38 -16
- package/dist/standalone/index.js +1 -1
- package/dist/standalone/orb-page.js +10 -8
- package/dist/styles/components.css +1 -1
- package/package.json +32 -33
- package/dist/eslint/index.js +0 -224
- package/dist/eslint/rules/badge-is-static-label.js +0 -93
- package/dist/eslint/rules/button-icon-slot.js +0 -102
- package/dist/eslint/rules/button-link-for-navigation.js +0 -146
- package/dist/eslint/rules/button-no-aria-label-with-text.js +0 -75
- package/dist/eslint/rules/button-type-roles-only-in-buttons.js +0 -83
- package/dist/eslint/rules/choice-control-has-a-name.js +0 -98
- package/dist/eslint/rules/choice-group-in-fieldset.js +0 -86
- package/dist/eslint/rules/command-shortcut-single-owner.js +0 -118
- package/dist/eslint/rules/confirm-dialog-words.js +0 -63
- package/dist/eslint/rules/data-table-one-unsized-column.js +0 -96
- package/dist/eslint/rules/destructive-action-placement.js +0 -69
- package/dist/eslint/rules/disabled-choice-says-why.js +0 -71
- package/dist/eslint/rules/disabled-control-appearance.js +0 -75
- package/dist/eslint/rules/ellipsis-opens-a-dialog.js +0 -110
- package/dist/eslint/rules/empty-state-owner.js +0 -145
- package/dist/eslint/rules/input-group-owners.js +0 -155
- package/dist/eslint/rules/jsx-class-utility.js +0 -200
- package/dist/eslint/rules/kbd-children-no-modifier-text.js +0 -128
- package/dist/eslint/rules/literal-text.js +0 -41
- package/dist/eslint/rules/menu-owner.js +0 -75
- package/dist/eslint/rules/middle-truncate-placement.js +0 -69
- package/dist/eslint/rules/no-app-feature-component.js +0 -100
- package/dist/eslint/rules/no-bare-button-label.js +0 -55
- package/dist/eslint/rules/no-choice-control-wrapper.js +0 -97
- package/dist/eslint/rules/no-comment.js +0 -77
- package/dist/eslint/rules/no-control-in-read-only-surface.js +0 -106
- package/dist/eslint/rules/no-decorative-icon.js +0 -164
- package/dist/eslint/rules/no-deprecated-zod-api.js +0 -215
- package/dist/eslint/rules/no-global-patch.js +0 -115
- package/dist/eslint/rules/no-hand-built-busy.js +0 -104
- package/dist/eslint/rules/no-hand-built-theme-picker.js +0 -144
- package/dist/eslint/rules/no-hand-built-tile.js +0 -41
- package/dist/eslint/rules/no-hand-formatted-date.js +0 -87
- package/dist/eslint/rules/no-hand-stacked-avatars.js +0 -67
- package/dist/eslint/rules/no-nested-collapse.js +0 -60
- package/dist/eslint/rules/no-package-component-redefinition.js +0 -175
- package/dist/eslint/rules/no-search-in-form.js +0 -47
- package/dist/eslint/rules/no-shadcn-appearance-override.js +0 -305
- package/dist/eslint/rules/no-shell-prompt-in-snippet.js +0 -74
- package/dist/eslint/rules/no-stock-error-words.js +0 -56
- package/dist/eslint/rules/no-subject-derived-expectation.js +0 -183
- package/dist/eslint/rules/no-tooltip-around-labelled-field.js +0 -72
- package/dist/eslint/rules/no-weight-class-on-strong-in-type-role.js +0 -57
- package/dist/eslint/rules/no-write-in-subscription.js +0 -140
- package/dist/eslint/rules/one-submenu-level.js +0 -43
- package/dist/eslint/rules/one-toast-per-flow.js +0 -91
- package/dist/eslint/rules/option-count.js +0 -167
- package/dist/eslint/rules/page-command-link-not-render.js +0 -43
- package/dist/eslint/rules/page-composition.js +0 -232
- package/dist/eslint/rules/progress-needs-name.js +0 -57
- package/dist/eslint/rules/rendered-import.js +0 -121
- package/dist/eslint/rules/server-action-discipline.js +0 -233
- package/dist/eslint/rules/settings-limit-in-footer.js +0 -94
- package/dist/eslint/rules/skeleton-in-region.js +0 -80
- package/dist/eslint/rules/sortable-heading-uses-sort.js +0 -79
- package/dist/eslint/rules/table-cell-inset-owner.js +0 -107
- package/dist/eslint/rules/tabs-selection-is-controlled.js +0 -47
- package/dist/eslint/rules/test-files-from-one-definition.js +0 -67
- package/dist/eslint/rules/tooltip-only-arrow.js +0 -38
- package/dist/eslint/rules/tooltip-trigger-accessible-name.js +0 -114
- package/dist/eslint/rules/workspace-declares-imports.js +0 -163
- package/dist/eslint/strict-design-system.js +0 -250
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Standard tool checked first: a `no-restricted-syntax` selector can match the
|
|
3
|
-
* string `"mod+k"`, but not a handler that reads `key` and `metaKey` as two
|
|
4
|
-
* separate checks (`(e.metaKey || e.ctrlKey) && e.key === "k"`), because the
|
|
5
|
-
* two reads only mean a shortcut together, in the same function; and it cannot
|
|
6
|
-
* allow the files that own Find.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const SHORTCUT_STRING = /^(?:mod|meta|cmd|ctrl)\+k$/i;
|
|
10
|
-
const MODIFIER_PROPERTIES = new Set(["metaKey", "ctrlKey"]);
|
|
11
|
-
const CASE_METHODS = new Set(["toLowerCase", "toUpperCase"]);
|
|
12
|
-
const KEY_VALUES = { key: new Set(["k", "K"]), code: new Set(["KeyK"]) };
|
|
13
|
-
|
|
14
|
-
const inFiles = (filename, files) => {
|
|
15
|
-
const path = filename.replaceAll("\\", "/");
|
|
16
|
-
return files.some((file) => path === file || path.endsWith(`/${file}`));
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const unwrapChain = (node) => (node.type === "ChainExpression" ? node.expression : node);
|
|
20
|
-
|
|
21
|
-
const propertyName = (member) =>
|
|
22
|
-
member.type === "MemberExpression" && !member.computed ? member.property.name : null;
|
|
23
|
-
|
|
24
|
-
function keyRead(node) {
|
|
25
|
-
const read = unwrapChain(node);
|
|
26
|
-
if (read.type === "CallExpression") {
|
|
27
|
-
const callee = unwrapChain(read.callee);
|
|
28
|
-
if (propertyName(callee) && CASE_METHODS.has(propertyName(callee))) {
|
|
29
|
-
return keyRead(callee.object);
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
const name = propertyName(read);
|
|
34
|
-
return name && name in KEY_VALUES ? name : null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const isLetterK = (read, literal) =>
|
|
38
|
-
literal.type === "Literal" &&
|
|
39
|
-
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- ESTree types Literal.value as string | number | boolean | RegExp | bigint | null; only typeof picks the string.
|
|
40
|
-
typeof literal.value === "string" &&
|
|
41
|
-
KEY_VALUES[read].has(literal.value);
|
|
42
|
-
|
|
43
|
-
const COMPARISONS = new Set(["===", "==", "!==", "!="]);
|
|
44
|
-
|
|
45
|
-
export const commandShortcutSingleOwner = {
|
|
46
|
-
meta: {
|
|
47
|
-
type: "problem",
|
|
48
|
-
docs: {
|
|
49
|
-
description: "Only the files that own Find bind the command-and-K shortcut.",
|
|
50
|
-
},
|
|
51
|
-
schema: [
|
|
52
|
-
{
|
|
53
|
-
type: "object",
|
|
54
|
-
properties: {
|
|
55
|
-
owners: { type: "array", items: { type: "string" }, uniqueItems: true },
|
|
56
|
-
},
|
|
57
|
-
additionalProperties: false,
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
defaultOptions: [{ owners: [] }],
|
|
61
|
-
messages: {
|
|
62
|
-
"find-shortcut": "⌘K and Ctrl+K belong to Find. Bind an in-page field to another key.",
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
create(context) {
|
|
66
|
-
if (inFiles(context.filename, context.options[0].owners)) return {};
|
|
67
|
-
|
|
68
|
-
const frames = [{ letterKs: [], readsModifier: false }];
|
|
69
|
-
const frame = () => frames.at(-1);
|
|
70
|
-
|
|
71
|
-
const enter = () => frames.push({ letterKs: [], readsModifier: false });
|
|
72
|
-
const leave = () => {
|
|
73
|
-
const { letterKs, readsModifier } = frames.pop();
|
|
74
|
-
if (!readsModifier) return;
|
|
75
|
-
for (const node of letterKs) context.report({ node, messageId: "find-shortcut" });
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const checkString = (node, value) => {
|
|
79
|
-
if (SHORTCUT_STRING.test(value.trim())) context.report({ node, messageId: "find-shortcut" });
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const noteLetterK = (node, read, other) => {
|
|
83
|
-
const name = keyRead(read);
|
|
84
|
-
if (name && isLetterK(name, other)) frame().letterKs.push(node);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
FunctionDeclaration: enter,
|
|
89
|
-
FunctionExpression: enter,
|
|
90
|
-
ArrowFunctionExpression: enter,
|
|
91
|
-
"FunctionDeclaration:exit": leave,
|
|
92
|
-
"FunctionExpression:exit": leave,
|
|
93
|
-
"ArrowFunctionExpression:exit": leave,
|
|
94
|
-
"Program:exit"() {
|
|
95
|
-
leave();
|
|
96
|
-
},
|
|
97
|
-
MemberExpression(node) {
|
|
98
|
-
if (MODIFIER_PROPERTIES.has(propertyName(node))) frame().readsModifier = true;
|
|
99
|
-
},
|
|
100
|
-
BinaryExpression(node) {
|
|
101
|
-
if (!COMPARISONS.has(node.operator)) return;
|
|
102
|
-
noteLetterK(node, node.left, node.right);
|
|
103
|
-
noteLetterK(node, node.right, node.left);
|
|
104
|
-
},
|
|
105
|
-
SwitchCase(node) {
|
|
106
|
-
if (node.test) noteLetterK(node, node.parent.discriminant, node.test);
|
|
107
|
-
},
|
|
108
|
-
Literal(node) {
|
|
109
|
-
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- ESTree types Literal.value as string | number | boolean | RegExp | bigint | null; only typeof picks the string.
|
|
110
|
-
if (typeof node.value === "string") checkString(node, node.value);
|
|
111
|
-
},
|
|
112
|
-
TemplateElement(node) {
|
|
113
|
-
if (node.parent.type === "TemplateLiteral" && node.parent.expressions.length === 0)
|
|
114
|
-
checkString(node, node.value.cooked ?? "");
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
},
|
|
118
|
-
};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Standard tool checked first: `no-restricted-syntax` reads one attribute at a
|
|
3
|
-
* time, and this check compares two of them on the same element (the title's
|
|
4
|
-
* verb with the confirm label's), and must know the element is the package's
|
|
5
|
-
* `ConfirmDialog` or the Portal's wrapper of it, not a local component.
|
|
6
|
-
*/
|
|
7
|
-
import { stringAttribute } from "./literal-text.js";
|
|
8
|
-
import { renderedImports, writtenName } from "./rendered-import.js";
|
|
9
|
-
|
|
10
|
-
const DIALOGS = new Set(["ConfirmDialog", "ConfirmedActionForm"]);
|
|
11
|
-
const QUESTION_OPENING = /^(?:are you sure|do you (?:really )?want|would you like|should)\b/i;
|
|
12
|
-
|
|
13
|
-
const words = (text) => text.trim().split(/\s+/).filter(Boolean);
|
|
14
|
-
|
|
15
|
-
export const confirmDialogWords = {
|
|
16
|
-
meta: {
|
|
17
|
-
type: "problem",
|
|
18
|
-
docs: {
|
|
19
|
-
description:
|
|
20
|
-
"A ConfirmDialog's title is a statement, and its confirm label repeats the title's verb with the noun.",
|
|
21
|
-
},
|
|
22
|
-
schema: [],
|
|
23
|
-
messages: {
|
|
24
|
-
"title-is-a-question":
|
|
25
|
-
'The title of a confirmation is a statement that names the record, "Remove pec@acme.it", never a question.',
|
|
26
|
-
"confirm-label-bare":
|
|
27
|
-
'The confirm label repeats the verb and the noun, "Remove mailbox"; "{{label}}" alone does not say what is confirmed.',
|
|
28
|
-
"confirm-verb-differs":
|
|
29
|
-
'The confirm label repeats the title\'s verb: the title says "{{title}}", so the label starts with "{{verb}}", not "{{label}}".',
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
create(context) {
|
|
33
|
-
const importOf = renderedImports(context);
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
JSXOpeningElement(node) {
|
|
37
|
-
if (!importOf(node.name) || !DIALOGS.has(writtenName(node.name))) return;
|
|
38
|
-
const titleText = stringAttribute(node, "title");
|
|
39
|
-
const labelText = stringAttribute(node, "confirmLabel");
|
|
40
|
-
if (
|
|
41
|
-
titleText !== null &&
|
|
42
|
-
(titleText.trim().endsWith("?") || QUESTION_OPENING.test(titleText.trim()))
|
|
43
|
-
) {
|
|
44
|
-
context.report({ node, messageId: "title-is-a-question" });
|
|
45
|
-
}
|
|
46
|
-
if (labelText === null) return;
|
|
47
|
-
const labelWords = words(labelText);
|
|
48
|
-
if (labelWords.length < 2) {
|
|
49
|
-
context.report({ node, messageId: "confirm-label-bare", data: { label: labelText } });
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const verb = titleText === null ? undefined : words(titleText)[0];
|
|
53
|
-
if (verb !== undefined && verb.toLowerCase() !== labelWords[0].toLowerCase()) {
|
|
54
|
-
context.report({
|
|
55
|
-
node,
|
|
56
|
-
messageId: "confirm-verb-differs",
|
|
57
|
-
data: { title: titleText, verb, label: labelText },
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
},
|
|
63
|
-
};
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Standard tool checked first: a `no-restricted-syntax` selector matches one
|
|
3
|
-
* node, but this reads a whole `columns` array (how many of its objects lack a
|
|
4
|
-
* `size`, whether one sets `meta.wraps`, whether a spread or a call makes the
|
|
5
|
-
* list unknowable) and follows a variable to its array, which no selector can.
|
|
6
|
-
*/
|
|
7
|
-
import { renderedImports } from "./rendered-import.js";
|
|
8
|
-
|
|
9
|
-
const COMMANDS_COLUMN_ID = "actions";
|
|
10
|
-
|
|
11
|
-
const WRAPPERS = new Set(["TSAsExpression", "TSSatisfiesExpression", "TSNonNullExpression"]);
|
|
12
|
-
|
|
13
|
-
function unwrap(node) {
|
|
14
|
-
let current = node;
|
|
15
|
-
while (WRAPPERS.has(current.type)) current = current.expression;
|
|
16
|
-
return current;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const keyName = (property) => {
|
|
20
|
-
if (property.computed) return null;
|
|
21
|
-
return property.key.type === "Identifier" ? property.key.name : property.key.value;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const propertyOf = (column, name) =>
|
|
25
|
-
column.properties.find((property) => property.type === "Property" && keyName(property) === name);
|
|
26
|
-
|
|
27
|
-
function setsWraps(column) {
|
|
28
|
-
const meta = propertyOf(column, "meta")?.value;
|
|
29
|
-
if (meta?.type !== "ObjectExpression") return false;
|
|
30
|
-
const wraps = propertyOf(meta, "wraps")?.value;
|
|
31
|
-
return wraps !== undefined && !(wraps.type === "Literal" && wraps.value === false);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const isCommandsColumn = (column) => propertyOf(column, "id")?.value.value === COMMANDS_COLUMN_ID;
|
|
35
|
-
|
|
36
|
-
const isKnownColumn = (element) =>
|
|
37
|
-
element?.type === "ObjectExpression" &&
|
|
38
|
-
element.properties.every((property) => property.type === "Property");
|
|
39
|
-
|
|
40
|
-
function arrayOf(context, expression) {
|
|
41
|
-
const node = unwrap(expression);
|
|
42
|
-
if (node.type === "ArrayExpression") return node;
|
|
43
|
-
if (node.type !== "Identifier") return null;
|
|
44
|
-
for (let scope = context.sourceCode.getScope(node); scope; scope = scope.upper) {
|
|
45
|
-
const variable = scope.set.get(node.name);
|
|
46
|
-
if (!variable) continue;
|
|
47
|
-
const [definition] = variable.defs;
|
|
48
|
-
const initialisedOnce =
|
|
49
|
-
variable.defs.length === 1 && variable.references.filter((use) => use.isWrite()).length === 1;
|
|
50
|
-
if (!initialisedOnce || definition.node.type !== "VariableDeclarator") return null;
|
|
51
|
-
const init = definition.node.init ? unwrap(definition.node.init) : null;
|
|
52
|
-
return init?.type === "ArrayExpression" ? init : null;
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function breaksTheRule(array) {
|
|
58
|
-
if (!array.elements.every(isKnownColumn)) return false;
|
|
59
|
-
if (array.elements.some(setsWraps)) return false;
|
|
60
|
-
const unsized = array.elements.filter((column) => !propertyOf(column, "size"));
|
|
61
|
-
if (unsized.length > 1) return true;
|
|
62
|
-
const content = array.elements.filter((column) => !isCommandsColumn(column));
|
|
63
|
-
return content.length > 0 && unsized.length === 0;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export const dataTableOneUnsizedColumn = {
|
|
67
|
-
meta: {
|
|
68
|
-
type: "problem",
|
|
69
|
-
docs: {
|
|
70
|
-
description:
|
|
71
|
-
"A DataTable's columns all have a size in pixels except the one that takes the spare width.",
|
|
72
|
-
},
|
|
73
|
-
schema: [],
|
|
74
|
-
messages: {
|
|
75
|
-
"one-unsized-column":
|
|
76
|
-
"Give every column a `size` in pixels except the one that takes the spare width. Columns without a size share what is left, so two of them make the widths depend on the data.",
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
create(context) {
|
|
80
|
-
const importOf = renderedImports(context);
|
|
81
|
-
return {
|
|
82
|
-
JSXOpeningElement(node) {
|
|
83
|
-
if (importOf(node.name)?.name !== "DataTable") return;
|
|
84
|
-
const attribute = node.attributes.find(
|
|
85
|
-
(candidate) => candidate.type === "JSXAttribute" && candidate.name.name === "columns",
|
|
86
|
-
);
|
|
87
|
-
const value = attribute?.value;
|
|
88
|
-
if (value?.type !== "JSXExpressionContainer") return;
|
|
89
|
-
const array = arrayOf(context, value.expression);
|
|
90
|
-
if (array && breaksTheRule(array)) {
|
|
91
|
-
context.report({ node: value.expression, messageId: "one-unsized-column" });
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
},
|
|
96
|
-
};
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Standard tool checked first: a `no-restricted-syntax` descendant selector can
|
|
3
|
-
* match a `Button` below `SheetContent`, but not that the button is the design
|
|
4
|
-
* system's, nor follow a renamed import, nor read `variant="destructive"` off
|
|
5
|
-
* the opening element or ignore a match that only sits in a `render` prop.
|
|
6
|
-
*/
|
|
7
|
-
import { renderedImports, staticAttribute } from "./rendered-import.js";
|
|
8
|
-
|
|
9
|
-
const BUTTONS = new Set(["Button", "ButtonLink"]);
|
|
10
|
-
const CONFIRMATIONS = new Set(["ConfirmDialog", "AlertDialog", "ConfirmActionDialog"]);
|
|
11
|
-
const CLOSED_SURFACES = new Set([
|
|
12
|
-
"SheetContent",
|
|
13
|
-
"DrawerContent",
|
|
14
|
-
"FormDrawer",
|
|
15
|
-
"CatalogDrawer",
|
|
16
|
-
"AccordionContent",
|
|
17
|
-
"CollapsibleContent",
|
|
18
|
-
"Disclosure",
|
|
19
|
-
"HoverCardContent",
|
|
20
|
-
]);
|
|
21
|
-
|
|
22
|
-
const FROM_DESIGN_SYSTEM = /^(@nadicodeai\/ui(\/|$)|@\/|\.)/;
|
|
23
|
-
|
|
24
|
-
export const destructiveActionPlacement = {
|
|
25
|
-
meta: {
|
|
26
|
-
type: "problem",
|
|
27
|
-
docs: {
|
|
28
|
-
description:
|
|
29
|
-
"A destructive action is confirmed in a ConfirmDialog opened from the page or a row's menu, never inside a sheet, drawer, hover card or collapsed section.",
|
|
30
|
-
},
|
|
31
|
-
schema: [],
|
|
32
|
-
messages: {
|
|
33
|
-
"destructive-placement":
|
|
34
|
-
"A destructive action is confirmed in a ConfirmDialog opened from the page or the row's menu: only a modal dims the whole page. Do not place it in a sheet, a drawer, a hover card or a section that starts closed.",
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
create(context) {
|
|
38
|
-
const importOf = renderedImports(context);
|
|
39
|
-
const exportOf = (element) => {
|
|
40
|
-
const imported = importOf(element.openingElement.name);
|
|
41
|
-
return imported && FROM_DESIGN_SYSTEM.test(imported.source) ? imported.name : null;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const isDestructive = (element) => {
|
|
45
|
-
const name = exportOf(element);
|
|
46
|
-
if (CONFIRMATIONS.has(name)) return true;
|
|
47
|
-
return (
|
|
48
|
-
BUTTONS.has(name) && staticAttribute(element.openingElement, "variant") === "destructive"
|
|
49
|
-
);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function insideClosedSurface(element) {
|
|
53
|
-
let child = element;
|
|
54
|
-
for (let node = element.parent; node; child = node, node = node.parent) {
|
|
55
|
-
if (node.type !== "JSXElement" || !node.children.includes(child)) continue;
|
|
56
|
-
if (CLOSED_SURFACES.has(exportOf(node))) return true;
|
|
57
|
-
}
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
JSXElement(node) {
|
|
63
|
-
if (isDestructive(node) && insideClosedSurface(node)) {
|
|
64
|
-
context.report({ node: node.openingElement, messageId: "destructive-placement" });
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
},
|
|
69
|
-
};
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Standard tool checked first: a `no-restricted-syntax` selector can match a
|
|
3
|
-
* `disabled` attribute on a `Checkbox`, but not follow a renamed import to the
|
|
4
|
-
* component, nor read the other attributes of the same element (aria-describedby,
|
|
5
|
-
* a spread) or tell a wait (`disabled={pending}`) from a reason.
|
|
6
|
-
*/
|
|
7
|
-
import { PACKAGE_BARREL, partOf, renderedImports } from "./rendered-import.js";
|
|
8
|
-
|
|
9
|
-
const MODULES = [
|
|
10
|
-
["Checkbox", "checkbox"],
|
|
11
|
-
["Switch", "switch"],
|
|
12
|
-
["RadioGroupItem", "radio-group"],
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
const CHOICE = new Map(
|
|
16
|
-
MODULES.map(([part, module]) => [
|
|
17
|
-
part,
|
|
18
|
-
[`@nadicodeai/ui/components/${module}`, PACKAGE_BARREL].map((from) => ({ from, part })),
|
|
19
|
-
]),
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
const WAIT = /^(?:is)?(?:pending|saving|submitting|busy)$/i;
|
|
23
|
-
|
|
24
|
-
const unwrapChain = (node) => (node.type === "ChainExpression" ? node.expression : node);
|
|
25
|
-
|
|
26
|
-
function isWait(attribute) {
|
|
27
|
-
if (attribute.value?.type !== "JSXExpressionContainer") return false;
|
|
28
|
-
const read = unwrapChain(attribute.value.expression);
|
|
29
|
-
if (read.type === "Identifier") return WAIT.test(read.name);
|
|
30
|
-
if (read.type !== "MemberExpression" || read.computed) return false;
|
|
31
|
-
return WAIT.test(read.property.name);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function isEnabledLiteral(attribute) {
|
|
35
|
-
const expression = attribute.value?.expression;
|
|
36
|
-
return expression?.type === "Literal" && expression.value === false;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const attributeNamed = (openingElement, name) =>
|
|
40
|
-
openingElement.attributes.find(
|
|
41
|
-
(attribute) => attribute.type === "JSXAttribute" && attribute.name.name === name,
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
export const disabledChoiceSaysWhy = {
|
|
45
|
-
meta: {
|
|
46
|
-
type: "problem",
|
|
47
|
-
docs: {
|
|
48
|
-
description:
|
|
49
|
-
"A disabled Checkbox, Switch or RadioGroupItem points aria-describedby at the line that names the reason.",
|
|
50
|
-
},
|
|
51
|
-
schema: [],
|
|
52
|
-
messages: {
|
|
53
|
-
"disabled-without-reason":
|
|
54
|
-
"Say why this is disabled: point aria-describedby at the line that names the reason, or use ChoiceboxGroupItem's disabledReason.",
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
create(context) {
|
|
58
|
-
const importOf = renderedImports(context);
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
JSXOpeningElement(node) {
|
|
62
|
-
if (!partOf(importOf(node.name), CHOICE, context.filename)) return;
|
|
63
|
-
const disabled = attributeNamed(node, "disabled");
|
|
64
|
-
if (!disabled || isEnabledLiteral(disabled) || isWait(disabled)) return;
|
|
65
|
-
if (attributeNamed(node, "aria-describedby")) return;
|
|
66
|
-
if (node.attributes.some((attribute) => attribute.type === "JSXSpreadAttribute")) return;
|
|
67
|
-
context.report({ node: disabled, messageId: "disabled-without-reason" });
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
},
|
|
71
|
-
};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Standard tool checked first: a `no-restricted-syntax` selector can match a
|
|
3
|
-
* conditional `variant`, but not that its condition reads a value the same
|
|
4
|
-
* element's `disabled` reads. That needs the two expressions compared.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { walk } from "./rendered-import.js";
|
|
8
|
-
|
|
9
|
-
const APPEARANCE_ATTRIBUTES = ["variant", "triggerVariant", "size"];
|
|
10
|
-
|
|
11
|
-
function valuePathsAndPrefixes(root, sourceCode) {
|
|
12
|
-
const names = new Set();
|
|
13
|
-
const collect = (node) => {
|
|
14
|
-
const pathTail = node.parent?.type === "MemberExpression" && !node.parent.computed;
|
|
15
|
-
if (node.type === "Identifier" && !pathTail) names.add(node.name);
|
|
16
|
-
if (node.type === "MemberExpression" && !node.computed) names.add(sourceCode.getText(node));
|
|
17
|
-
};
|
|
18
|
-
collect(root);
|
|
19
|
-
walk(root, sourceCode.visitorKeys, collect);
|
|
20
|
-
return names;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const attribute = (openingElement, name) =>
|
|
24
|
-
openingElement.attributes.find(
|
|
25
|
-
(candidate) => candidate.type === "JSXAttribute" && candidate.name.name === name,
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
export const disabledControlAppearance = {
|
|
29
|
-
meta: {
|
|
30
|
-
type: "problem",
|
|
31
|
-
docs: {
|
|
32
|
-
description:
|
|
33
|
-
"A control never chooses its variant from the condition that disables it, so a disabled control has one appearance.",
|
|
34
|
-
},
|
|
35
|
-
schema: [
|
|
36
|
-
{
|
|
37
|
-
type: "object",
|
|
38
|
-
properties: {
|
|
39
|
-
controls: { type: "array", items: { type: "string" }, uniqueItems: true },
|
|
40
|
-
},
|
|
41
|
-
additionalProperties: false,
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
defaultOptions: [{ controls: ["Button"] }],
|
|
45
|
-
messages: {
|
|
46
|
-
"variant-follows-disabled":
|
|
47
|
-
"{{control}} turns its {{attribute}} on `{{condition}}`, which also disables it, so its disabled state looks unlike every other disabled {{control}}. Keep one {{attribute}} and let `disabled` draw the state.",
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
create(context) {
|
|
51
|
-
const [{ controls }] = context.options;
|
|
52
|
-
const { sourceCode } = context;
|
|
53
|
-
return {
|
|
54
|
-
JSXOpeningElement(node) {
|
|
55
|
-
if (node.name.type !== "JSXIdentifier" || !controls.includes(node.name.name)) return;
|
|
56
|
-
const disabled = attribute(node, "disabled")?.value;
|
|
57
|
-
if (disabled?.type !== "JSXExpressionContainer") return;
|
|
58
|
-
const disabling = valuePathsAndPrefixes(disabled.expression, sourceCode);
|
|
59
|
-
for (const name of APPEARANCE_ATTRIBUTES) {
|
|
60
|
-
const chosen = attribute(node, name)?.value;
|
|
61
|
-
if (chosen?.type !== "JSXExpressionContainer") continue;
|
|
62
|
-
if (chosen.expression.type !== "ConditionalExpression") continue;
|
|
63
|
-
const { test } = chosen.expression;
|
|
64
|
-
if (![...valuePathsAndPrefixes(test, sourceCode)].some((read) => disabling.has(read)))
|
|
65
|
-
continue;
|
|
66
|
-
context.report({
|
|
67
|
-
node: chosen,
|
|
68
|
-
messageId: "variant-follows-disabled",
|
|
69
|
-
data: { control: node.name.name, attribute: name, condition: sourceCode.getText(test) },
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
},
|
|
75
|
-
};
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Standard tool checked first: `no-restricted-syntax` can match a label that
|
|
3
|
-
* ends in an ellipsis, but not what its command does: whether the handler
|
|
4
|
-
* opens something (a call that opens or passes `true`), runs, or navigates.
|
|
5
|
-
*/
|
|
6
|
-
import { childText, keyName, literalText } from "./literal-text.js";
|
|
7
|
-
import { renderedImports } from "./rendered-import.js";
|
|
8
|
-
|
|
9
|
-
const ENDS_IN_ELLIPSIS = /(?:…|\.\.\.)\s*$/;
|
|
10
|
-
const COMMAND_KEYS = new Set(["onClick", "onSelect", "link", "render", "icon"]);
|
|
11
|
-
const HANDLER_KEYS = new Set(["onClick", "onSelect"]);
|
|
12
|
-
const MENU_ROWS = new Set([
|
|
13
|
-
"DropdownMenuItem",
|
|
14
|
-
"DropdownMenuCommand",
|
|
15
|
-
"ContextMenuItem",
|
|
16
|
-
"MenubarItem",
|
|
17
|
-
]);
|
|
18
|
-
const MENU_LINKS = new Set(["DropdownMenuLink", "ContextMenuLink"]);
|
|
19
|
-
const FROM_DESIGN_SYSTEM = /^(@nadicodeai\/ui(\/|$)|@\/|\.)/;
|
|
20
|
-
const OPENING_CALL = /^(?:open|show)(?:[A-Z]|$)/;
|
|
21
|
-
const FUNCTIONS = new Set(["ArrowFunctionExpression", "FunctionExpression"]);
|
|
22
|
-
|
|
23
|
-
const calleeName = (callee) => {
|
|
24
|
-
if (callee.type === "Identifier") return callee.name;
|
|
25
|
-
if (callee.type === "MemberExpression" && !callee.computed) return callee.property.name;
|
|
26
|
-
return null;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function opensSomething(node, visitorKeys) {
|
|
30
|
-
const calls = [];
|
|
31
|
-
const stack = [node.body];
|
|
32
|
-
while (stack.length > 0) {
|
|
33
|
-
const current = stack.pop();
|
|
34
|
-
if (current.type === "CallExpression") calls.push(current);
|
|
35
|
-
for (const key of visitorKeys[current.type] ?? []) {
|
|
36
|
-
const value = current[key];
|
|
37
|
-
for (const child of Array.isArray(value) ? value : [value]) {
|
|
38
|
-
if (child?.type) stack.push(child);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return calls.some(
|
|
43
|
-
(call) =>
|
|
44
|
-
OPENING_CALL.test(calleeName(call.callee) ?? "") ||
|
|
45
|
-
call.arguments.some((argument) => argument.type === "Literal" && argument.value === true),
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const ellipsisOpensADialog = {
|
|
50
|
-
meta: {
|
|
51
|
-
type: "problem",
|
|
52
|
-
docs: {
|
|
53
|
-
description: "A command's label ends in an ellipsis only when the command opens a dialog.",
|
|
54
|
-
},
|
|
55
|
-
schema: [],
|
|
56
|
-
messages: {
|
|
57
|
-
"ellipsis-without-dialog":
|
|
58
|
-
'An ellipsis says "this asks for more before it acts". "{{label}}" navigates or runs at once, so drop the ellipsis; keep it only on a command that opens a dialog.',
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
create(context) {
|
|
62
|
-
const importOf = renderedImports(context);
|
|
63
|
-
const { visitorKeys } = context.sourceCode;
|
|
64
|
-
|
|
65
|
-
function reportsNoDialog(handler) {
|
|
66
|
-
return (
|
|
67
|
-
handler !== undefined &&
|
|
68
|
-
FUNCTIONS.has(handler.type) &&
|
|
69
|
-
!opensSomething(handler, visitorKeys)
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
ObjectExpression(node) {
|
|
75
|
-
const keys = new Map(node.properties.map((property) => [keyName(property), property]));
|
|
76
|
-
const label = literalText(keys.get("label")?.value);
|
|
77
|
-
if (label === null || !ENDS_IN_ELLIPSIS.test(label)) return;
|
|
78
|
-
if (![...keys.keys()].some((key) => COMMAND_KEYS.has(key))) return;
|
|
79
|
-
if (keys.has("render")) return;
|
|
80
|
-
const handlerProperty = [...keys].find(([key]) => HANDLER_KEYS.has(key))?.[1];
|
|
81
|
-
const navigates = keys.has("link");
|
|
82
|
-
if (navigates || reportsNoDialog(handlerProperty?.value)) {
|
|
83
|
-
context.report({
|
|
84
|
-
node: keys.get("label").value,
|
|
85
|
-
messageId: "ellipsis-without-dialog",
|
|
86
|
-
data: { label },
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
JSXElement(node) {
|
|
91
|
-
const imported = importOf(node.openingElement.name);
|
|
92
|
-
if (!imported || !FROM_DESIGN_SYSTEM.test(imported.source)) return;
|
|
93
|
-
const row = MENU_ROWS.has(imported.name);
|
|
94
|
-
if (!row && !MENU_LINKS.has(imported.name)) return;
|
|
95
|
-
const label = childText(node);
|
|
96
|
-
if (label === null || !ENDS_IN_ELLIPSIS.test(label)) return;
|
|
97
|
-
const handler = node.openingElement.attributes.find(
|
|
98
|
-
(attribute) => attribute.type === "JSXAttribute" && HANDLER_KEYS.has(attribute.name.name),
|
|
99
|
-
)?.value?.expression;
|
|
100
|
-
if (!row || reportsNoDialog(handler)) {
|
|
101
|
-
context.report({
|
|
102
|
-
node: node.openingElement,
|
|
103
|
-
messageId: "ellipsis-without-dialog",
|
|
104
|
-
data: { label },
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
},
|
|
110
|
-
};
|