efficient-web-builder 1.0.0 → 2.0.0
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/CHANGELOG.md +30 -0
- package/README.md +2 -1
- package/dist/components/AddBlockRow.d.ts +1 -7
- package/dist/components/AddBlockRow.js +1 -1
- package/dist/components/AddChildRow.d.ts +1 -6
- package/dist/components/AddChildRow.js +1 -1
- package/dist/components/AddSidePanel.d.ts +2 -39
- package/dist/components/AddSidePanel.js +1 -1
- package/dist/components/BlockGapField.js +1 -1
- package/dist/components/BlogPostSource.js +1 -1
- package/dist/components/CardShadowField.js +1 -1
- package/dist/components/ChatSettingsPanel.js +1 -1
- package/dist/components/CornersField.d.ts +0 -1
- package/dist/components/CornersField.js +1 -1
- package/dist/components/DuplicateToPageMenu.js +1 -1
- package/dist/components/EditorScreenMount.d.ts +1 -0
- package/dist/components/EditorScreenMount.js +1 -0
- package/dist/components/EditorStoreProvider.d.ts +1 -0
- package/dist/components/EditorStoreProvider.js +1 -0
- package/dist/components/HeroImageField.js +1 -1
- package/dist/components/HeroSection.d.ts +2 -6
- package/dist/components/HeroSection.js +4 -4
- package/dist/components/HoverEffectField.js +1 -1
- package/dist/components/ImageFocusRow.js +1 -1
- package/dist/components/KeepHint.d.ts +5 -0
- package/dist/components/KeepHint.js +1 -0
- package/dist/components/LocationsGroupedField.js +1 -1
- package/dist/components/MediaSidePanel.d.ts +1 -6
- package/dist/components/MediaSidePanel.js +1 -1
- package/dist/components/MediaUploadField.js +1 -1
- package/dist/components/MultiSelectField.d.ts +4 -1
- package/dist/components/MultiSelectField.js +1 -1
- package/dist/components/NodeContextMenu.d.ts +2 -96
- package/dist/components/NodeContextMenu.js +1 -1
- package/dist/components/PageEditGuardNotices.d.ts +1 -1
- package/dist/components/PageEditGuardNotices.js +1 -1
- package/dist/components/PanelToggleIcon.d.ts +1 -3
- package/dist/components/PanelToggleIcon.js +1 -1
- package/dist/components/PartSettingsPanel.d.ts +5 -0
- package/dist/components/PartSettingsPanel.js +1 -1
- package/dist/components/PhotographyField.js +1 -1
- package/dist/components/PreviewDeviceContext.d.ts +2 -4
- package/dist/components/PreviewDeviceContext.js +1 -1
- package/dist/components/PreviewPartsContext.d.ts +1 -3
- package/dist/components/PreviewPartsContext.js +1 -1
- package/dist/components/SearchSelectField.d.ts +3 -1
- package/dist/components/SearchSelectField.js +1 -1
- package/dist/components/SectionEditor.d.ts +2 -5
- package/dist/components/SectionEditor.js +1 -1
- package/dist/components/SectionEditorShell.d.ts +1 -1
- package/dist/components/SectionEditorShell.js +1 -1
- package/dist/components/SectionThemePicker.d.ts +3 -1
- package/dist/components/SectionThemePicker.js +1 -1
- package/dist/components/ShadowColourField.js +1 -1
- package/dist/components/SidePanelHead.d.ts +1 -23
- package/dist/components/SidePanelHead.js +1 -1
- package/dist/components/SideSpaceField.d.ts +0 -1
- package/dist/components/SideSpaceField.js +1 -1
- package/dist/components/SiteEditorProvider.d.ts +3 -10
- package/dist/components/SiteEditorProvider.js +1 -1
- package/dist/components/SiteWidthField.d.ts +0 -1
- package/dist/components/SiteWidthField.js +1 -1
- package/dist/components/SlotChildActionsContext.d.ts +2 -13
- package/dist/components/SlotChildActionsContext.js +1 -1
- package/dist/components/SocialLinksPanel.js +1 -1
- package/dist/components/SourceStatus.js +1 -1
- package/dist/components/StandaloneSectionEditor.d.ts +1 -0
- package/dist/components/StandaloneSectionEditor.js +1 -1
- package/dist/components/StorefrontDocumentContext.d.ts +2 -67
- package/dist/components/StorefrontDocumentContext.js +1 -1
- package/dist/components/StorefrontEditor.d.ts +1 -0
- package/dist/components/StorefrontEditor.js +1 -1
- package/dist/components/StorefrontEditorChrome.d.ts +1 -1
- package/dist/components/StorefrontEditorChrome.js +1 -1
- package/dist/components/StorefrontLinkField.d.ts +9 -0
- package/dist/components/StorefrontLinkField.js +1 -1
- package/dist/components/ThemeField.js +1 -1
- package/dist/components/TickerSettingsPanel.js +1 -1
- package/dist/components/UiIcon.d.ts +2 -10
- package/dist/components/UiIcon.js +1 -1
- package/dist/components/WebsiteScreenParts.d.ts +2 -0
- package/dist/components/WebsiteScreenParts.js +1 -0
- package/dist/components/WidthFollowField.js +1 -1
- package/dist/components/panel-widths.d.ts +2 -12
- package/dist/components/panel-widths.js +1 -1
- package/dist/components/storefront-editor.css +125 -986
- package/dist/components/useFooterFocus.d.ts +2 -0
- package/dist/components/useFooterFocus.js +1 -0
- package/dist/components/useLocationsTree.d.ts +6 -0
- package/dist/components/useLocationsTree.js +1 -0
- package/dist/components/usePartBridge.d.ts +1 -9
- package/dist/components/usePartBridge.js +1 -1
- package/dist/components/usePinnedChromeSeed.d.ts +2 -0
- package/dist/components/usePinnedChromeSeed.js +1 -0
- package/dist/components/useSocialSettings.js +1 -1
- package/dist/components/useStorefrontBlogPosts.js +1 -1
- package/dist/lib/client-only.d.ts +1 -0
- package/dist/lib/client-only.js +1 -0
- package/dist/lib/content/view-tiers.d.ts +2 -6
- package/dist/lib/content/view-tiers.js +1 -1
- package/dist/lib/editor-admin-bridge.d.ts +2 -29
- package/dist/lib/editor-admin-bridge.js +1 -1
- package/dist/lib/editor-backend-error.d.ts +2 -0
- package/dist/lib/editor-backend-error.js +1 -0
- package/dist/lib/editor-blocks.d.ts +2 -2
- package/dist/lib/editor-chrome.d.ts +2 -52
- package/dist/lib/editor-chrome.js +1 -1
- package/dist/lib/editor-document-adapter.d.ts +3 -51
- package/dist/lib/editor-document-adapter.js +1 -1
- package/dist/lib/editor-document-addressing.d.ts +2 -0
- package/dist/lib/editor-document-addressing.js +1 -0
- package/dist/lib/editor-document-contract.d.ts +1 -0
- package/dist/lib/editor-document-contract.js +0 -0
- package/dist/lib/editor-document-hooks.d.ts +1 -15
- package/dist/lib/editor-document-switch.d.ts +2 -0
- package/dist/lib/editor-document-switch.js +1 -0
- package/dist/lib/editor-failure.d.ts +1 -4
- package/dist/lib/editor-failure.js +1 -1
- package/dist/lib/editor-host-inbound.d.ts +1 -1
- package/dist/lib/editor-host-inbound.js +1 -1
- package/dist/lib/editor-host.d.ts +2 -135
- package/dist/lib/editor-host.js +1 -1
- package/dist/lib/editor-key-scope.d.ts +1 -3
- package/dist/lib/editor-key-scope.js +1 -1
- package/dist/lib/editor-load-error.d.ts +1 -4
- package/dist/lib/editor-load-error.js +1 -1
- package/dist/lib/editor-media-panel.d.ts +2 -23
- package/dist/lib/editor-media-panel.js +1 -1
- package/dist/lib/editor-media-picker.d.ts +2 -12
- package/dist/lib/editor-media-picker.js +1 -1
- package/dist/lib/editor-page-lock.d.ts +1 -1
- package/dist/lib/editor-page-lock.js +1 -1
- package/dist/lib/editor-screen.d.ts +2 -0
- package/dist/lib/editor-screen.js +1 -0
- package/dist/lib/editor-section-templates.d.ts +1 -2
- package/dist/lib/editor-section-templates.js +1 -1
- package/dist/lib/editor-session-context.d.ts +1 -7
- package/dist/lib/editor-session-context.js +1 -1
- package/dist/lib/editor-session.d.ts +2 -45
- package/dist/lib/editor-session.js +1 -1
- package/dist/lib/editor-site-slug.js +1 -1
- package/dist/lib/editor-theme.d.ts +1 -1
- package/dist/lib/editor-theme.js +1 -1
- package/dist/lib/hero-arrows.d.ts +1 -1
- package/dist/lib/links/link-filters.d.ts +1 -0
- package/dist/lib/links/link-filters.js +0 -0
- package/dist/lib/links/link-picker-setup.d.ts +29 -0
- package/dist/lib/links/link-picker-setup.js +1 -0
- package/dist/lib/puck/ColourControl.d.ts +4 -1
- package/dist/lib/puck/ColourControl.js +1 -1
- package/dist/lib/puck/add-palette.d.ts +3 -9
- package/dist/lib/puck/add-palette.js +1 -1
- package/dist/lib/puck/add-target-types.d.ts +1 -0
- package/dist/lib/puck/add-target-types.js +0 -0
- package/dist/lib/puck/add-target.d.ts +2 -9
- package/dist/lib/puck/block-choice.d.ts +2 -0
- package/dist/lib/puck/block-choice.js +1 -0
- package/dist/lib/puck/block-offer.js +1 -1
- package/dist/lib/puck/block-types-in.d.ts +1 -1
- package/dist/lib/puck/block-types-in.js +1 -1
- package/dist/lib/puck/blocks/general/index.d.ts +2 -2
- package/dist/lib/puck/content-tree.d.ts +2 -43
- package/dist/lib/puck/content-tree.js +1 -1
- package/dist/lib/puck/controls.d.ts +11 -22
- package/dist/lib/puck/controls.js +1 -1
- package/dist/lib/puck/device-fields.d.ts +2 -24
- package/dist/lib/puck/device-fields.js +1 -1
- package/dist/lib/puck/field-buckets.d.ts +2 -0
- package/dist/lib/puck/field-buckets.js +1 -0
- package/dist/lib/puck/field-groups.d.ts +3 -3
- package/dist/lib/puck/field-groups.js +1 -1
- package/dist/lib/puck/field-markers.d.ts +2 -16
- package/dist/lib/puck/field-markers.js +1 -1
- package/dist/lib/puck/footer-block-row-name.d.ts +4 -0
- package/dist/lib/puck/footer-block-row-name.js +1 -0
- package/dist/lib/puck/footer-blocks.d.ts +2 -10
- package/dist/lib/puck/footer-blocks.js +1 -1
- package/dist/lib/puck/hidable.d.ts +2 -9
- package/dist/lib/puck/hidable.js +1 -1
- package/dist/lib/puck/palette-sections.d.ts +2 -0
- package/dist/lib/puck/palette-sections.js +1 -0
- package/dist/lib/puck/panel-fields.d.ts +2 -12
- package/dist/lib/puck/panel-fields.js +1 -1
- package/dist/lib/puck/panel-no-control-mark.d.ts +2 -7
- package/dist/lib/puck/panel-no-control-mark.js +1 -1
- package/dist/lib/puck/part-id.d.ts +1 -0
- package/dist/lib/puck/part-id.js +1 -0
- package/dist/lib/puck/pinned-seed-gate.d.ts +2 -12
- package/dist/lib/puck/pinned-seed-gate.js +1 -1
- package/dist/lib/puck/preview-click.d.ts +1 -2
- package/dist/lib/puck/preview-click.js +1 -1
- package/dist/lib/puck/preview-dom.d.ts +1 -6
- package/dist/lib/puck/preview-dom.js +1 -1
- package/dist/lib/puck/preview-events.d.ts +2 -0
- package/dist/lib/puck/preview-events.js +1 -0
- package/dist/lib/puck/preview-selection-context.d.ts +2 -19
- package/dist/lib/puck/preview-selection-context.js +1 -1
- package/dist/lib/puck/section-doc.d.ts +1 -5
- package/dist/lib/puck/section-doc.js +1 -1
- package/dist/lib/puck/section-groups.d.ts +1 -1
- package/dist/lib/puck/slot-child-name.d.ts +2 -8
- package/dist/lib/puck/slot-child-name.js +1 -1
- package/dist/lib/puck/slot-heal.d.ts +1 -2
- package/dist/lib/puck/slot-heal.js +1 -1
- package/dist/lib/puck/slot-section-spec.d.ts +1 -0
- package/dist/lib/puck/slot-section-spec.js +0 -0
- package/dist/lib/puck/slot-specs.d.ts +2 -13
- package/dist/lib/puck/style-clipboard.d.ts +1 -19
- package/dist/lib/puck/style-clipboard.js +1 -1
- package/dist/lib/puck/text-control-kinds.d.ts +2 -13
- package/dist/lib/puck/text-control-kinds.js +1 -1
- package/dist/lib/puck/text-parts.d.ts +2 -5
- package/dist/lib/puck/text-parts.js +1 -1
- package/dist/lib/puck/tree-reveal.d.ts +1 -11
- package/dist/lib/puck/tree-reveal.js +1 -1
- package/dist/lib/puck/use-section-ops.d.ts +2 -25
- package/dist/lib/puck/use-section-ops.js +1 -1
- package/dist/lib/reel.d.ts +1 -1
- package/dist/lib/section-template-types.d.ts +1 -21
- package/dist/lib/sections/new-section-node.d.ts +2 -0
- package/dist/lib/sections/new-section-node.js +1 -0
- package/dist/lib/source-rules/accent-token-name.d.ts +20 -0
- package/dist/lib/source-rules/accent-token-name.js +4 -0
- package/dist/lib/source-rules/button-corners.d.ts +13 -0
- package/dist/lib/source-rules/button-corners.js +2 -0
- package/dist/lib/source-rules/editor-chrome-note.d.ts +16 -0
- package/dist/lib/source-rules/editor-chrome-note.js +13 -0
- package/dist/lib/source-rules/editor-context-menu.d.ts +5 -0
- package/dist/lib/source-rules/editor-context-menu.js +4 -0
- package/dist/lib/source-rules/editor-puck-drag-off.d.ts +8 -0
- package/dist/lib/source-rules/editor-puck-drag-off.js +4 -0
- package/dist/lib/source-rules/efficient-easel-version.d.ts +9 -0
- package/dist/lib/source-rules/efficient-easel-version.js +2 -0
- package/dist/lib/source-rules/every-button-follows-the-button-colour.d.ts +12 -0
- package/dist/lib/source-rules/every-button-follows-the-button-colour.js +7 -0
- package/dist/lib/source-rules/flat-theme-palette.d.ts +10 -0
- package/dist/lib/source-rules/flat-theme-palette.js +8 -0
- package/dist/lib/source-rules/full-width-means-full.d.ts +8 -0
- package/dist/lib/source-rules/full-width-means-full.js +2 -0
- package/dist/lib/source-rules/heading-ceiling-sweep.d.ts +11 -0
- package/dist/lib/source-rules/heading-ceiling-sweep.js +8 -0
- package/dist/lib/source-rules/label-hints.d.ts +7 -0
- package/dist/lib/source-rules/label-hints.js +5 -0
- package/dist/lib/source-rules/label-vocabulary.d.ts +14 -0
- package/dist/lib/source-rules/label-vocabulary.js +5 -0
- package/dist/lib/source-rules/mirrored-rows-agree.d.ts +19 -0
- package/dist/lib/source-rules/mirrored-rows-agree.js +10 -0
- package/dist/lib/source-rules/no-comment-in-jsx.d.ts +9 -0
- package/dist/lib/source-rules/no-comment-in-jsx.js +7 -0
- package/dist/lib/source-rules/no-glyph-rows.d.ts +15 -0
- package/dist/lib/source-rules/no-glyph-rows.js +4 -0
- package/dist/lib/source-rules/one-colour-control.d.ts +7 -0
- package/dist/lib/source-rules/one-colour-control.js +2 -0
- package/dist/lib/source-rules/one-dropdown.d.ts +10 -0
- package/dist/lib/source-rules/one-dropdown.js +4 -0
- package/dist/lib/source-rules/one-editable-list.d.ts +16 -0
- package/dist/lib/source-rules/one-editable-list.js +6 -0
- package/dist/lib/source-rules/one-editor-list.d.ts +7 -0
- package/dist/lib/source-rules/one-editor-list.js +3 -0
- package/dist/lib/source-rules/one-right-click.d.ts +10 -0
- package/dist/lib/source-rules/one-right-click.js +2 -0
- package/dist/lib/source-rules/one-switch.d.ts +9 -0
- package/dist/lib/source-rules/one-switch.js +7 -0
- package/dist/lib/source-rules/one-up-level-link.d.ts +16 -0
- package/dist/lib/source-rules/one-up-level-link.js +18 -0
- package/dist/lib/source-rules/panel-colour-tokens.d.ts +48 -0
- package/dist/lib/source-rules/panel-colour-tokens.js +20 -0
- package/dist/lib/source-rules/panel-no-divider-lines.d.ts +14 -0
- package/dist/lib/source-rules/panel-no-divider-lines.js +10 -0
- package/dist/lib/source-rules/panel-one-arrow.d.ts +21 -0
- package/dist/lib/source-rules/panel-one-arrow.js +1 -0
- package/dist/lib/source-rules/panel-one-hover.d.ts +8 -0
- package/dist/lib/source-rules/panel-one-hover.js +2 -0
- package/dist/lib/source-rules/panel-rows-use-the-shared-row.d.ts +23 -0
- package/dist/lib/source-rules/panel-rows-use-the-shared-row.js +22 -0
- package/dist/lib/source-rules/part-slider-fills-row.d.ts +11 -0
- package/dist/lib/source-rules/part-slider-fills-row.js +29 -0
- package/dist/lib/source-rules/part-style-naming.d.ts +10 -0
- package/dist/lib/source-rules/part-style-naming.js +2 -0
- package/dist/lib/source-rules/puck-document-surfaces.d.ts +16 -0
- package/dist/lib/source-rules/puck-document-surfaces.js +8 -0
- package/dist/lib/source-rules/reduced-motion.d.ts +26 -0
- package/dist/lib/source-rules/reduced-motion.js +9 -0
- package/dist/lib/source-rules/route-content-width.d.ts +22 -0
- package/dist/lib/source-rules/route-content-width.js +5 -0
- package/dist/lib/source-rules/rule.d.ts +21 -0
- package/dist/lib/source-rules/rule.js +1 -0
- package/dist/lib/source-rules/section-theme-preview.d.ts +9 -0
- package/dist/lib/source-rules/section-theme-preview.js +8 -0
- package/dist/lib/source-rules/site-gutter.d.ts +6 -0
- package/dist/lib/source-rules/site-gutter.js +4 -0
- package/dist/lib/source-rules/site-square-by-default.d.ts +17 -0
- package/dist/lib/source-rules/site-square-by-default.js +3 -0
- package/dist/lib/source-rules/site-width.d.ts +11 -0
- package/dist/lib/source-rules/site-width.js +7 -0
- package/dist/lib/source-rules/size-token-paints.d.ts +8 -0
- package/dist/lib/source-rules/size-token-paints.js +11 -0
- package/dist/lib/source-rules/social-icons-import.d.ts +6 -0
- package/dist/lib/source-rules/social-icons-import.js +6 -0
- package/dist/lib/source-rules/source-status-is-shared.d.ts +7 -0
- package/dist/lib/source-rules/source-status-is-shared.js +1 -0
- package/dist/lib/source-rules/style-face-matches-size.d.ts +10 -0
- package/dist/lib/source-rules/style-face-matches-size.js +4 -0
- package/dist/lib/source-rules/support/accent-rename-bytes.d.ts +5 -0
- package/dist/lib/source-rules/support/accent-rename-bytes.js +1 -0
- package/dist/lib/source-rules/support/css-box-model.d.ts +5 -0
- package/dist/lib/source-rules/support/css-box-model.js +1 -0
- package/dist/lib/source-rules/support/css-length.d.ts +2 -0
- package/dist/lib/source-rules/support/css-length.js +1 -0
- package/dist/lib/source-rules/support/css-rules.d.ts +12 -0
- package/dist/lib/source-rules/support/css-rules.js +1 -0
- package/dist/lib/source-rules/support/css-source.d.ts +2 -0
- package/dist/lib/source-rules/support/css-source.js +3 -0
- package/dist/lib/source-rules/support/label-extraction.d.ts +14 -0
- package/dist/lib/source-rules/support/label-extraction.js +5 -0
- package/dist/lib/source-rules/support/panel-select-cascade.d.ts +72 -0
- package/dist/lib/source-rules/support/panel-select-cascade.js +2 -0
- package/dist/lib/source-rules/support/part-slider-call-sites.d.ts +9 -0
- package/dist/lib/source-rules/support/part-slider-call-sites.js +17 -0
- package/dist/lib/source-rules/support/registry-source.d.ts +14 -0
- package/dist/lib/source-rules/support/registry-source.js +3 -0
- package/dist/lib/source-rules/support/ts-source.d.ts +2 -0
- package/dist/lib/source-rules/support/ts-source.js +3 -0
- package/dist/lib/source-rules/theme-mode-controls-gone.d.ts +10 -0
- package/dist/lib/source-rules/theme-mode-controls-gone.js +5 -0
- package/dist/lib/source-rules/upload-intake-guard.d.ts +11 -0
- package/dist/lib/source-rules/upload-intake-guard.js +6 -0
- package/dist/lib/source-rules/word-styles.d.ts +8 -0
- package/dist/lib/source-rules/word-styles.js +4 -0
- package/dist/lib/theme-row-persist.js +1 -1
- package/dist/lib/theme-save-message.js +1 -1
- package/dist/lib/web-backend.d.ts +4 -59
- package/dist/lib/web-backend.js +1 -1
- package/dist/lib/website-document-addressing.d.ts +2 -0
- package/dist/lib/website-document-addressing.js +1 -0
- package/dist/lib/website-host-inbound.d.ts +1 -0
- package/dist/lib/website-host-inbound.js +1 -0
- package/dist/lib/website-session.d.ts +5 -0
- package/dist/lib/website-session.js +1 -0
- package/package.json +15 -5
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
export type PartSliderFillsRowOptions = {
|
|
3
|
+
readonly partPanel?: string;
|
|
4
|
+
readonly sheet?: string;
|
|
5
|
+
readonly shellSheet?: SourceFile;
|
|
6
|
+
readonly hosts: readonly string[];
|
|
7
|
+
readonly renderedHosts?: Readonly<Record<string, string>>;
|
|
8
|
+
readonly minFiles: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function reachesSlider(selector: string): boolean;
|
|
11
|
+
export declare function partSliderFillsRow(files: readonly SourceFile[], options: PartSliderFillsRowOptions): RuleReport;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import{DEFAULT_PANEL_W as k,MAX_PANEL_W as N,MIN_PANEL_W as y}from"../../components/panel-widths.js";import{isRuleSource as A,ruleChecks as F}from"./rule.js";import{parseRules as w}from"./support/css-box-model.js";import{readSliderCallSites as E,sliderFields as v}from"./support/part-slider-call-sites.js";const m=[["sf-editor","es-editor"],["sf-panel","sf-panel--right"],["sf-insp-body","sf-scroll"],["sf-part-panel"],["es-group"],["es-group-body"],["es-field","sf-part-field"],["es-field-ctl"]],_=["sf-slider","has-notch"],S=129;function $(r){const e=r.trim();if(!e.startsWith("."))return null;const s=e.split(".").filter(Boolean);return s.every(o=>/^[a-zA-Z0-9_-]+$/.test(o))?s:null}function c(r){if(/[+~:]/.test(r))return!1;const e=r.trim().split(/\s*(>)\s*|\s+/).filter(Boolean);if(!e.length)return!1;const s=$(e[e.length-1]);if(!s||!s.every(a=>_.includes(a)))return!1;let o=e.length-2,d=m.length-1,i=!1;for(;o>=0;){const a=e[o];if(a===">"){i=!0,o-=1;continue}const f=$(a);if(!f)return!1;let t=-1;for(let n=d;n>=0;n-=1){if(f.every(l=>m[n].includes(l))){t=n;break}if(i)break}if(t===-1)return!1;d=t-1,i=!1,o-=1}return!0}function W(r){const e=r.trim();if(e==="none")return 0;if(e==="auto"||e==="initial")return e==="auto"?1:0;const s=Number.parseFloat(e);return Number.isFinite(s)?s:0}function P(r){const e={grow:0,width:"auto",from:[]};for(const s of r)for(const o of w(s.css)){if(!o.selector.split(",").some(i=>c(i)))continue;let d=!1;for(const[i,a]of o.declarations)i==="flex"?(e.grow=W(a),d=!0):i==="flex-grow"?(e.grow=Number.parseFloat(a)||0,d=!0):i==="width"&&(e.width=a.trim(),d=!0);d&&e.from.push(`${s.name} ${o.selector}`)}return e}function x(r,e){return r.grow>=1||/^100%$/.test(r.width)?e:S}function j(r){return r.filter(e=>{const s=e.text.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/[^\n]*/g,"");return/SettingsField/.test(s)&&/<Slider[\s/>]/.test(s)}).map(e=>e.path).sort()}function I(r,e){const{check:s,report:o}=F("part slider fills row"),d=new Map(r.map(t=>[t.path,t]));if(s("the walk reaches the slot's own slider, and not the notch, a hover or another panel's row",c(".es-field-ctl > .sf-slider")&&c(".sf-insp-body .sf-slider")&&!c(".sf-slider.has-notch::before")&&!c(".es-field-ctl > .sf-slider:hover")&&!c(".sf-field .sf-slider")&&!c(".es-field-ctl > div > .sf-slider")),e.partPanel!==void 0){const t=d.get(e.partPanel);let n=null,l="";try{n=t?E(t.text):null}catch(u){l=u instanceof Error?u.message:String(u)}s(`read the part panel ${e.partPanel}`,!!t&&n!==null,l||"the file is not among those handed in"),s("\xA71 the part panel draws no slider since plan S2",n!==null&&n.length===0,`${e.partPanel} draws ${n?.length??"?"} slider(s) again \u2014 fields ${v(n??[]).join(", ")}. Since plan S2 its numbers are the text box's cells; if a slider is meant to come back, it comes back into the hosts and this section moves with it.`)}if(e.shellSheet!==void 0){const t=w(e.shellSheet.text).filter(n=>n.selector.trim()===".es-field-ctl");s(`\xA72 ${e.shellSheet.path} declares the control slot \`.es-field-ctl\``,t.length>0,"that is the slot `SettingsField` stacks a control into, and this rule is about how an item sizes inside it"),s("\xA72 the shell's control slot is still a flex row",t.length>0&&t[t.length-1].declarations.get("display")==="flex","`.es-field-ctl` is no longer `display: flex` in the installed shell. How a flex ITEM sizes is then not the question, and this rule would be answering the old one. Re-read the shell before trusting a verdict from here.")}if(e.sheet!==void 0){const t=d.get(e.sheet);s(`read the stylesheet ${e.sheet}`,!!t),s("\xA73 the installed shell stylesheet was handed in beside it",e.shellSheet!==void 0);const n=[...t?[{name:t.path,css:t.text}]:[],...e.shellSheet?[{name:e.shellSheet.path,css:e.shellSheet.text}]:[]],l=P(n),u=[{panel:y,content:192,what:"MIN_PANEL_W"},{panel:k.right,content:280,what:"the default"},{panel:N,content:520,what:"MAX_PANEL_W"}],b=u.map(h=>{const p=x(l,h.content),g=h.content-p;return` panel ${String(h.panel).padStart(3)} (${h.what.padEnd(11)}) field content ${String(h.content).padStart(3)} track ${String(p).padStart(3)} `+(g>0?`SHORT BY ${g} \u2014 ${Math.round(p/h.content*100)}% of the row`:"fills")}).join(`
|
|
2
|
+
`);for(const h of u)s(`\xA73 the stacked slider fills its row at ${h.what} (field content ${h.content}px)`,!!t&&x(l,h.content)===h.content,`the shell's stacked sliders do not fill their row.
|
|
3
|
+
|
|
4
|
+
${e.hosts.length} host(s) \u2014 ${e.hosts.join(", ")} \u2014 draw the shared \`Slider\` inside
|
|
5
|
+
\`SettingsField\`, whose control slot \`.es-field-ctl\` is \`display: flex\`. Nothing in ${e.sheet} makes \`.sf-slider\` take
|
|
6
|
+
that row, so it is \`flex: 0 1 auto\` and collapses to the UA's own ${S}px range input \u2014 the
|
|
7
|
+
same width whatever the panel is dragged to:
|
|
8
|
+
|
|
9
|
+
${b}
|
|
10
|
+
|
|
11
|
+
Resolved on \`.sf-slider\` in that slot: flex-grow ${l.grow}, width ${l.width}`+(l.from.length?`
|
|
12
|
+
from: ${l.from.join(`
|
|
13
|
+
`)}`:`
|
|
14
|
+
from: (no rule reaches it)`)+`
|
|
15
|
+
|
|
16
|
+
The shell asks for this in its own words (src/panel/SettingsField.tsx):
|
|
17
|
+
"The row is stacked rather than side-by-side so the control gets the panel's full
|
|
18
|
+
width \u2014 a 308px panel cannot afford a label column AND a usable slider."`)}const i=r.filter(t=>/\.tsx?$/.test(t.path)&&!A(t.path)&&(t.path.startsWith("components/")||t.path.startsWith("lib/")));s(`read the source (${i.length} .ts/.tsx files under components/ and lib/, at least ${e.minFiles})`,i.length>=e.minFiles);const a=j(i),f=[...e.hosts].sort();s(`\xA74 the files that draw a \`Slider\` inside a \`SettingsField\` are the ${f.length} known host(s)`,a.join(`
|
|
19
|
+
`)===f.join(`
|
|
20
|
+
`),`the set has changed.
|
|
21
|
+
|
|
22
|
+
known: ${f.join(", ")||"(none)"}
|
|
23
|
+
found: ${a.join(", ")||"(none)"}
|
|
24
|
+
|
|
25
|
+
A NEW one is a row \`.es-field-ctl > .sf-slider\` now fills and nothing here watches \u2014 add it to the hosts
|
|
26
|
+
(and render it for the direct-child check). One REMOVED is a guard pointing at nothing.`);for(const[t,n]of Object.entries(e.renderedHosts??{})){const l=/class="es-field-ctl"><([a-z]+) class="([^"]*)"/.exec(n);s(`\xA74 ${t}: the rendered row has a \`.es-field-ctl\` slot with an element child`,l!==null,"has it stopped going through `SettingsField`?"),l&&s(`\xA74 ${t}: the slot's first child is the \`.sf-slider\` itself`,l[2].split(/\s+/).includes("sf-slider"),`the control slot's first child is <${l[1]} class="${l[2]}">. Something wraps the slider, so
|
|
27
|
+
\`.es-field-ctl > .sf-slider\` no longer reaches it and that row is back to the UA's 129px \u2014 the wrapper is the
|
|
28
|
+
flex item now. Widening the selector to a descendant would NOT fix it; it would paint \`flex: 1\` on an element
|
|
29
|
+
whose parent is the wrapper and leave the measurement unchanged. Card 66247.`)}return o()}export{I as partSliderFillsRow,c as reachesSlider};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
export type PartStyleNamingOptions = {
|
|
3
|
+
readonly roleSheet?: string;
|
|
4
|
+
readonly panel?: string;
|
|
5
|
+
readonly bridge?: string;
|
|
6
|
+
readonly minSites: number;
|
|
7
|
+
readonly minWithScale: number;
|
|
8
|
+
readonly minWithoutScale: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function partStyleNaming(files: readonly SourceFile[], options: PartStyleNamingOptions): RuleReport;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{fontRoleOfClassName as y,FONT_ROLE_LABELS as x}from"../fonts.js";import{isRuleSource as w,ruleChecks as E}from"./rule.js";import{enclosingLiteral as N}from"./heading-ceiling-sweep.js";import{stripComments as b}from"./support/ts-source.js";import{typeScaleGroupOfClassName as u}from"../theme.js";const R=["app/","components/","lib/"],p={"font-display":"heading","font-heading2":"heading2","font-heading3":"heading3","font-body":"body","font-serif":"accent","font-mono":"label"},k={heading:"--font-role-heading",heading2:"--font-role-heading2",heading3:"--font-role-heading3",body:"--font-role-body",accent:"--font-role-accent",label:"--font-role-label"},$=[];function L(m){const n=[],t=new Set;for(const d of m){const c=b(d.text),l=/\bfont-(?:display|body|serif|mono)\b/g;let r;for(;r=l.exec(c);){const i=N(c,r.index);if(!i)continue;const o=c.slice(0,r.index).split(`
|
|
2
|
+
`).length,h=`${d.path}:${o}:${i}`;if(t.has(h))continue;t.add(h);const g=i.split(/\s+/).filter(a=>!a.slice(0,a.lastIndexOf(":")+1).includes("&")).map(a=>a.slice(a.lastIndexOf(":")+1)),f=Object.keys(p).filter(a=>g.includes(a));f.length!==0&&n.push({file:d.path,line:o,className:i,utilities:f})}}return n}function I(m,n){const{check:t,report:d}=E("part style naming"),c=m.filter(e=>/\.tsx?$/.test(e.path)&&!w(e.path)&&R.some(s=>e.path.startsWith(s))),l=L(c),r=new Map(m.map(e=>[e.path,e]));t(`read the source (${c.length} .ts/.tsx files)`,c.length>0,"found no source to sweep \u2014 this rule is not measuring anything"),t(`the scanner finds face-tagged class lists at all (${l.length}, at least ${n.minSites})`,l.length>=n.minSites);const i=l.filter(e=>u(e.className)!==null),o=l.filter(e=>u(e.className)===null);t(`\u2026both taggings are represented, so neither branch below is untested (${i.length} with a scale token, at least ${n.minWithScale})`,i.length>=n.minWithScale&&i.length>0),t(`\u2026and the face-without-a-size-knob population is real, not an artefact (${o.length}, at least ${n.minWithoutScale})`,o.length>=n.minWithoutScale&&o.length>0,`${o.length} class lists carry a face utility and no matching scale token`);const h=l.filter(e=>y(e.className)!==p[e.utilities[0]]);if(t("every class list in the census reads back as its own utility's role",h.length===0,h.slice(0,5).map(e=>`${e.file}:${e.line} has ${e.utilities[0]} but reads ${y(e.className)}`).join("; ")),n.roleSheet!==void 0){const e=r.get(n.roleSheet),s=Object.values(k).filter(O=>!e||!e.text.includes(O));t(`\u2026and every role named here is a custom property ${n.roleSheet} defines`,!!e&&s.length===0,e?s.join(", "):"the sheet was not read")}const g=Object.values(p).filter(e=>!(e in x));t("every face utility names a role the panel offers",g.length===0,g.join(", "));const f=Object.keys(x).filter(e=>!Object.values(p).includes(e));t("\u2026and the roles no utility can name are exactly the ones decided to have none",f.join()===$.join(),`no utility reaches: [${f}] \u2014 expected [${$}]`);const a=o.filter(e=>u(e.className)!==null);t("no class list without a scale token claims a size group",a.length===0,a.slice(0,5).map(e=>`${e.file}:${e.line}`).join("; ")),t("the reported case: a `font-display text-[22px]` heading is Headings, size group none",y("font-display text-[22px] font-medium")==="heading"&&u("font-display text-[22px] font-medium")===null),t("\u2026and genuinely body-tagged copy still reads as the body size group",u("text-[var(--size-body,16px)]")==="body");const S=l.filter(e=>e.utilities.length>1);if(t("every class list names at most one face",S.length===0,S.slice(0,5).map(e=>`${e.file}:${e.line} \u2014 ${e.utilities.join(" + ")}`).join("; ")),n.panel!==void 0){const e=r.get(n.panel),s=e?b(e.text):"";t(`read the part panel ${n.panel}`,!!e),t("the box's name comes from the FACE, not from a size group",s.includes("FONT_ROLE_LABELS[face.role]")&&!s.includes("TYPE_GROUP_LABELS[")),t("\u2026a face the page does not draw as its declared role is called Custom",/return\s*"Custom"/.test(s)&&s.includes('kind: "custom"')),t("\u2026and a part no size knob reaches says so under the text box",/sizeGroup === null && \(\s*<p className="es-field-hint sf-hint-keep">Fixed size/.test(s))}if(n.bridge!==void 0){const e=r.get(n.bridge),s=e?b(e.text):"";t(`read the preview's part bridge ${n.bridge}`,!!e),t("the preview publishes BOTH tags, each walked up to its own nearest declarer",s.includes("fontRoleOfClassName")&&s.includes("typeScaleGroupOfClassName")&&s.includes("el.parentElement")),t("\u2026and the element it reads from is one that declares EITHER tag",/partStyleSource\([\s\S]{0,200}fontRoleOfClassName[\s\S]{0,120}typeScaleGroupOfClassName/.test(s))}return d()}export{I as partStyleNaming};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
export type PuckDocumentMount = {
|
|
3
|
+
readonly migratedBy: string | null;
|
|
4
|
+
readonly why: string;
|
|
5
|
+
};
|
|
6
|
+
export type PuckDocumentSurfacesOptions = {
|
|
7
|
+
readonly mounts: Readonly<Record<string, PuckDocumentMount>>;
|
|
8
|
+
readonly migrator?: string;
|
|
9
|
+
readonly handedMigrator?: {
|
|
10
|
+
readonly rules: string;
|
|
11
|
+
readonly entry: string;
|
|
12
|
+
};
|
|
13
|
+
readonly storedFloor: number;
|
|
14
|
+
readonly minFiles: number;
|
|
15
|
+
};
|
|
16
|
+
export declare function puckDocumentSurfaces(files: readonly SourceFile[], options: PuckDocumentSurfacesOptions): RuleReport;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import{ruleChecks as P}from"./rule.js";const o=/<(Render|Puck|SiteEditorProvider|EditorStoreProvider|ScreenStoreProvider)\b[^<>]*\bdata=\{/,m=/migrateRenamedProps\s*\(/,g=/export function migrateRenamedProps\b/;function S(l,r){const{check:t,report:p}=P("puck document surfaces"),a=e=>l.find(s=>s.path===e)?.text;{const e=n=>`<${n}`;t("a mount is found",o.test(`${e("Render")} config={c} data={doc} />`)),t("\u2026across lines",o.test(`${e("SiteEditorProvider")}
|
|
2
|
+
config={c}
|
|
3
|
+
data={initial}
|
|
4
|
+
>`)),t("\u2026and the screen's store provider is one under either of its names",["Editor","Screen"].every(n=>o.test(`${e(`${n}StoreProvider`)} config={c} data={doc}>`))),t("\u2026and a tag named in prose is not one",!o.test(`the ${e("Puck")}> mounts, then its data={x} is read`)),t("\u2026nor the composition API",!o.test(`${e("Puck.Preview")} />`));const s="migrateRenamedProps";t("a call is found",m.test(`const d = ${s}(stored);`)),t("\u2026and a mention is not one",!m.test(`// runs ${s} first`)),t("the declaration is found, generic as it is",g.test(`export function ${s}<T>(data: T): T {`))}const d=l.filter(e=>/\.tsx?$/.test(e.path));t(`the source scan reached the app (${d.length} TS/TSX files, at least ${r.minFiles})`,d.length>=r.minFiles);const c=d.filter(e=>o.test(e.text)).map(e=>e.path).sort(),$=Object.keys(r.mounts).sort(),u=c.filter(e=>!(e in r.mounts)),f=$.filter(e=>!c.includes(e));t(`every mount is declared (${c.length} found) \u2014 a NEW one must say whether its document comes from stored props (migrate it) or is a mock (say so)`,u.length===0,u.map(e=>`UNDECLARED: ${e}`).join(`
|
|
5
|
+
`)),t("every declared mount is a real mount \u2014 a stale row would shelter a surface that later stops migrating",f.length===0,f.map(e=>`NOT FOUND: ${e}`).join(`
|
|
6
|
+
`));const i=Object.entries(r.mounts).filter(([,e])=>e.migratedBy!==null),h=i.filter(([,e])=>!m.test(a(e.migratedBy)??""));if(t(`SURFACES=${i.length} \u2014 every mount fed from STORED props runs migrateRenamedProps, so a renamed prop key cannot silently reset a live section`,h.length===0,h.map(([e,s])=>`${e}
|
|
7
|
+
\u2192 ${s.migratedBy} never calls migrateRenamedProps(`).join(`
|
|
8
|
+
`)),r.handedMigrator){const{rules:e,entry:s}=r.handedMigrator,n=a(e)??"";t(`${e} carries the REAL migrateRenamedProps, not a stand-in`,/import \{ migrateRenamedProps \} from "@\/lib\/puck\/renamed-props";/.test(n)&&/^\s*migrateRenamedProps,$/m.test(n)),t(`\u2026${s} passes those rules to the screen's mount`,/<(?:EditorScreenMount|ScreenMount)\b[^>]*addressing=\{WEBSITE_ADDRESSING\}/.test(a(s)??""))}return t(`at least ${r.storedFloor} mount(s) fed from stored props (found ${i.length})`,i.length>=r.storedFloor,"if this drops, a surface was reclassified as a mock rather than fixed"),r.migrator&&t(`${r.migrator} still exports migrateRenamedProps \u2014 the function \xA72 hunts calls of is live`,g.test(a(r.migrator)??"")),p()}export{S as puckDocumentSurfaces};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
export type ReducedMotionOptions = {
|
|
3
|
+
readonly globalSheet?: string;
|
|
4
|
+
readonly stylesheets?: readonly string[];
|
|
5
|
+
readonly editorOnly?: Readonly<Record<string, string>>;
|
|
6
|
+
readonly askingSites?: Readonly<Record<string, number>>;
|
|
7
|
+
readonly hook?: string;
|
|
8
|
+
readonly minScripts: number;
|
|
9
|
+
};
|
|
10
|
+
export declare const MOVING_PROPERTIES: string[];
|
|
11
|
+
export declare const REQUIRED_FADES: string[];
|
|
12
|
+
export declare const HARD_CODED_SMOOTH: RegExp;
|
|
13
|
+
export interface ReduceBlock {
|
|
14
|
+
body: string;
|
|
15
|
+
selector: string;
|
|
16
|
+
decls: string;
|
|
17
|
+
unlayered: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function readReduceBlock(css: string): ReduceBlock | null;
|
|
20
|
+
export declare function valueOf(decls: string, property: string): string | null;
|
|
21
|
+
export declare function hardCodedSmoothScrolls(file: SourceFile): {
|
|
22
|
+
file: string;
|
|
23
|
+
line: number;
|
|
24
|
+
text: string;
|
|
25
|
+
}[];
|
|
26
|
+
export declare function reducedMotion(files: readonly SourceFile[], options: ReducedMotionOptions): RuleReport;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import{isRuleSource as C,ruleChecks as I}from"./rule.js";import{stripCommentsAndStrings as O}from"./support/css-source.js";import{stripComments as S}from"./support/ts-source.js";const R=["all","transform","translate","rotate","scale","top","right","bottom","left","inset","margin","padding","width","height","max-height","max-width","min-height","min-width","gap","flex","flex-basis","grid-template-columns","grid-template-rows","background-position","object-position","offset-distance"],D=["opacity","color","background-color","border-color"],x=/\bbehaviou?r\s*:\s*["'`]smooth["'`]/,N=/\b(transition|animation)[a-z-]*\s*:[^;]*!important/i;function $(n){const o=n.search(/@media[^{]*prefers-reduced-motion\s*:\s*reduce/);if(o<0)return null;let e=0;const d=[];let r="";for(let c=0;c<o;c++){const t=n[c];if(t==="{"){const i=/^@layer\b/.test(r.trim());d.push(i),i&&e++,r=""}else t==="}"?(d.pop()&&e--,r=""):t===";"?r="":r+=t}const m=n.indexOf("{",o);if(m<0)return null;let u=0,g=-1;for(let c=m;c<n.length;c++)if(n[c]==="{")u++;else if(n[c]==="}"&&--u===0){g=c;break}if(g<0)return null;const s=n.slice(m+1,g),y=s.indexOf("{"),b=s.lastIndexOf("}");return y<0||b<0?null:{body:s,selector:s.slice(0,y).trim().replace(/\s+/g," "),decls:s.slice(y+1,b),unlayered:e===0}}function p(n,o){const e=new RegExp(`(?:^|;)\\s*${o}\\s*:([^;]*)`,"i"),d=n.match(e);return d?d[1].replace(/!important/i,"").trim().replace(/\s+/g," "):null}function j(n){const o=S(n.text),e=[];for(const[d,r]of o.split(`
|
|
2
|
+
`).entries())x.test(r)&&e.push({file:n.path,line:d+1,text:r.trim()});return e}function B(n,o){const e=O(n.text),d=o?e.replace(o,""):e,r=[];for(const[m,u]of d.split(`
|
|
3
|
+
`).entries())N.test(u)&&r.push(`${n.path}:${m+1} ${u.trim()}`);return r}function L(n,o){const{check:e,report:d}=I("reduced motion"),r=o.editorOnly??{},m=new Map(n.map(t=>[t.path,t]));{e("a rule that exists only in a comment is NOT counted",$(O(`/* the site should have a @media (prefers-reduced-motion: reduce) rule */
|
|
4
|
+
a { color: red; }`))===null);const i="@media (prefers-reduced-motion: reduce) { * { animation-duration: 1ms !important; } }";e("the layer check fails on a layered copy and passes on an unlayered one",$(i)?.unlayered===!0&&$(`@layer base { ${i} }`)?.unlayered===!1&&$(`@layer base { a { color: red; } } ${i}`)?.unlayered===!0),e("valueOf reads a declaration and drops !important",p("animation-duration: 1ms !important;","animation-duration")==="1ms"&&p("transition-property: opacity, color !important;","transition-property")==="opacity, color"&&p("animation-duration: 1ms;","animation")===null&&p("scroll-behavior: auto;","animation-delay")===null);const a=f=>(f??"").split(",").map(v=>v.trim()),h=a(p("transition-property: opacity, transform !important;","transition-property")),w=a(p("transition-property: opacity, color !important;","transition-property"));e("the moving-property check fails on a copy that lets transform through, and passes a fade-only list",h.some(f=>R.includes(f))&&!w.some(f=>R.includes(f))),e("the !important scan flags a motion declaration and not one in a comment",B({path:"fixture.css",text:`/* transition: x !important */
|
|
5
|
+
a { transition: opacity 1s !important; }`},null).length===1),e("the smooth-scroll regex bites, both ways",x.test('el.scrollIntoView({ behavior: "smooth" })')&&x.test("window.scrollTo({ top: 0, behavior: 'smooth' })")&&!x.test("el.scrollIntoView({ behavior: scrollBehavior() })")&&!x.test('window.scrollTo({ top: 0, behavior: "auto" })')),e("a commented example is not a breach",j({path:"fixture.ts",text:`// behavior: "smooth" in a comment
|
|
6
|
+
const a = 1;`}).length===0)}const u=n.filter(t=>t.path.endsWith(".css")),g=n.filter(t=>/\.tsx?$/.test(t.path)&&!t.path.endsWith(".d.ts")&&!C(t.path));let s=null;if(o.globalSheet!==void 0){const t=m.get(o.globalSheet),i=t?O(t.text):"";s=$(i);const a=o.globalSheet;e(`${a} was actually read`,i.length>1e4,`expected the real stylesheet, got ${i.length} chars`),e(`${a} has a real prefers-reduced-motion rule, not just prose about one`,s!==null&&s.decls.trim().length>0,"no `@media (prefers-reduced-motion: reduce)` block once comments are stripped, or it is empty. Before this rule landed the sheet's ONLY match for that string was a comment, which is the state this asserts is over."),e("the rule is UNLAYERED, so Tailwind's utilities cannot outrank it",s?.unlayered===!0,"the reduced-motion rule sits inside an @layer. Tailwind's utilities live in a later layer, so every `transition-*` class would beat it and the site would move for everyone, with nothing to show for it.");const h=s?.selector??"";e("the rule applies to every element and both generated boxes",/\*/.test(h)&&/::before/.test(h)&&/::after/.test(h),`selector \`${h}\` \u2014 it must start from \`*\` (a narrower one covers only what somebody remembered) and cover \`::before\` and \`::after\`: a generated box animates like any other`);const w=(s?.decls??"").split(";").map(l=>l.trim()).filter(l=>l.length>0),f=w.filter(l=>!/!important\s*$/i.test(l)).map(l=>l.split(":")[0].trim());e("every declaration carries !important",w.length>=4&&f.length===0,`${w.length} declaration(s), without !important: ${f.join(", ")||"(none)"}. Being unlayered beats Tailwind, but not the unlayered class rules in the same sheet and not an inline style.`);const v=p(s?.decls??"","transition-property"),E=(v??"").split(",").map(l=>l.trim().toLowerCase()),T=E.filter(l=>R.includes(l));e("the transition allow-list lets nothing move",v!==null&&T.length===0,v===null?"the rule must set `transition-property` \u2014 that is how movement is sorted from fades":`movement is back on the allow-list: ${T.join(", ")}. Anything on that list still animates for a visitor who asked for no motion.`);const M=D.filter(l=>!E.includes(l));e("the transition allow-list still lets fades through",M.length===0,`\`${M.join("`, `")}\` must stay transitionable. A fade carries no movement, helps people follow what changed, and harms nobody \u2014 dropping it is the crude rule this one was written instead of.`);const k=p(s?.decls??"","animation-duration");e("animations are collapsed to a short NON-ZERO time",k!==null&&/^(0?\.\d+|[1-9]\d*)m?s$/.test(k),`animation-duration is \`${k}\`; the rule must cap it (CSS cannot sort a keyframe list by what it moves), and exactly 0s stops \`animationend\` firing in some engines and hangs any component waiting on it.`),e("a loop that still repeats is still a loop: animation-iteration-count is 1",p(s?.decls??"","animation-iteration-count")==="1"),e("animations are not collapsed with `animation: none`, which can hide content",p(s?.decls??"","animation")===null,"an element whose static styles are invisible, made visible by a `forwards` fill, would never appear. Run the animation to its end instead."),e("a scroll the visitor did not ask for is movement too: scroll-behavior is auto",p(s?.decls??"","scroll-behavior")==="auto","the sheet sets `scroll-behavior: smooth` on html inside @layer base; this must turn it off."),e("the escape hatch exists and is spelled the one way",/\[data-motion-keep\]/.test(h),"the exemption attribute must appear in the selector, so one grep finds every exception.")}for(const t of o.stylesheets??[])e(`read the stylesheet ${t}`,m.has(t)&&t.endsWith(".css"));const y=u.flatMap(t=>B(t,t.path===o.globalSheet&&s?s.body:null));e(`nothing in the stylesheets can outrank the rule (${u.length} sheet(s))`,u.length>0&&y.length===0,u.length===0?"read no stylesheet \u2014 this check is not measuring anything":`!important motion declaration(s) outside the reduced-motion rule:
|
|
7
|
+
`+y.join(`
|
|
8
|
+
`)),e(`read the scripts (${g.length} .ts/.tsx files, at least ${o.minScripts})`,g.length>=o.minScripts);const b=g.flatMap(j),c=b.filter(t=>!(t.file in r));e("no shopper-facing code hard-codes a smooth scroll",c.length===0,'an explicit `behavior: "smooth"` beats the stylesheet, so this scroll animates for a visitor who asked for no motion:\n '+c.map(t=>`${t.file}:${t.line} ${t.text}`).join(`
|
|
9
|
+
`)+"\n Call `scrollBehavior()` from components/useReducedMotion.ts instead. It still scrolls \u2014 it just arrives instead of travelling.");for(const[t,i]of Object.entries(r)){const a=m.get(t);e(`the editor waiver is still real: ${t} (${i})`,!!a&&/editor/i.test(a.text.slice(0,400))&&b.some(h=>h.file===t),a?"its own opening no longer says it is the editor's, or it no longer holds the literal \u2014 the waiver covers nothing":"the file is gone \u2014 remove the stale entry")}for(const[t,i]of Object.entries(o.askingSites??{})){const a=m.get(t),h=a?(S(a.text).match(/behaviou?r:\s*scrollBehavior\(\)/g)??[]).length:-1;e(`${t} still asks scrollBehavior() ${i} time(s)`,h===i,a?`found ${h}. A shopper-facing scroll stopped asking \u2014 if a site was deliberately removed, change the number and say which.`:"the file is gone")}if(o.hook!==void 0){const t=m.get(o.hook),i=t?S(t.text):"";e(`${o.hook} exports scrollBehavior()`,/export function scrollBehavior\(\)/.test(i)),e(`${o.hook} names the media query once and shares it`,/export const REDUCED_MOTION_QUERY/.test(i),"the media query must be named once and shared, not typed out per reader");const a=(i.match(/prefers-reduced-motion/g)??[]).length;e(`the query string appears exactly once in ${o.hook}`,a===1,`it appears ${a} times. Two spellings of the same question can disagree; there must be exactly one.`)}return d()}export{x as HARD_CODED_SMOOTH,R as MOVING_PROPERTIES,D as REQUIRED_FADES,j as hardCodedSmoothScrolls,$ as readReduceBlock,L as reducedMotion,p as valueOf};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
export type NamedCap = {
|
|
3
|
+
readonly file: string;
|
|
4
|
+
readonly cls: string;
|
|
5
|
+
readonly why: string;
|
|
6
|
+
};
|
|
7
|
+
export type MovedColumn = {
|
|
8
|
+
readonly file: string;
|
|
9
|
+
readonly from: number;
|
|
10
|
+
readonly moved: number;
|
|
11
|
+
readonly caps: number;
|
|
12
|
+
readonly what: string;
|
|
13
|
+
readonly cap?: "site" | "chrome";
|
|
14
|
+
};
|
|
15
|
+
export type RouteContentWidthOptions = {
|
|
16
|
+
readonly measures?: readonly NamedCap[];
|
|
17
|
+
readonly ownedElsewhere?: readonly NamedCap[];
|
|
18
|
+
readonly moved?: readonly MovedColumn[];
|
|
19
|
+
readonly movedColumns?: number;
|
|
20
|
+
readonly minFiles: number;
|
|
21
|
+
};
|
|
22
|
+
export declare function routeContentWidth(files: readonly SourceFile[], options: RouteContentWidthOptions): RuleReport;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{CHROME_WIDTH_CAP_CLASS as v,PAGE_WIDTH as o,SITE_WIDTH_CAP_CLASS as u,SITE_WIDTH_VAR as $}from"../content/page-width.js";import{ruleChecks as C}from"./rule.js";const i=/max-w-\[1\d{3}px\]/,f=s=>s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),E=s=>/^\s*(\/\/|\*|\/\*)/.test(s);function x(s){const n=[];return s.text.split(`
|
|
2
|
+
`).forEach((t,h)=>{if(!E(t))for(const r of t.matchAll(new RegExp(i,"g")))n.push({file:s.path,line:h+1,cls:r[0]})}),n}const b=s=>s.cap==="chrome"?v:u;function S(s,n){const{check:t,report:h}=C("route content width"),r=n.measures??[],g=[...r,...n.ownedElsewhere??[]],m=n.moved??[];t("a site's width starts at 1000px",o.min===1e3,`PAGE_WIDTH.min is ${o.min}`),t("the band holds the narrowest site width",i.test(`max-w-[${o.min}px]`)),t("the band holds the built-in site width",i.test(`max-w-[${o.default}px]`)),t("a cap below the band is never looked at",!i.test("max-w-[999px]")&&!i.test("max-w-[680px]")),t("the replacement class is not itself a literal in the band",!i.test(u));{const e=x({path:"fixture.tsx",text:["// a doc line naming max-w-[1100px] is not a use",'<div className="mx-auto max-w-[1100px]" />'].join(`
|
|
3
|
+
`)});t("the reader finds a cap in code and skips a comment line",e.length===1&&e[0].line===2,JSON.stringify(e))}const c=s.filter(e=>/\.tsx?$/.test(e.path));t(`read the source (${c.length} TS/TSX files, at least ${n.minFiles})`,c.length>=n.minFiles,"fewer means the walk broke, and the census below is vacuous");const d=c.flatMap(x).filter(e=>!g.some(a=>a.file===e.file&&a.cls===e.cls));t("no route layout types its own content column (no literal cap in the 1000\u20131999px band)",d.length===0,`${d.length} cap(s) in the band are a hand-typed content column. Each should read ${u} \u2014 or, if it is a reading measure, be named as one with the reason it is one:
|
|
4
|
+
`+d.map(e=>`${e.file}:${e.line} ${e.cls}`).join(`
|
|
5
|
+
`));const p=new Map(c.map(e=>[e.path,e.text]));for(const e of r)t(`the measure is still there: ${e.file} ${e.cls}`,new RegExp(f(e.cls)).test(p.get(e.file)??""),"delete that measure rather than leaving a hole the next hard-coded column can sit in"),t(`\u2026and it carries its reason: ${e.file} ${e.cls}`,e.why.trim().length>40,"a measure needs its reason");for(const e of m){const a=p.get(e.file);if(!t(`read the moved column's file ${e.file} (${e.what})`,a!==void 0,"the file is gone \u2014 delete its row"))continue;const l=b(e);t(`${e.file} (${e.what}) declares a cap that reads ${$}`,l.includes($),l);const w=[...a.matchAll(new RegExp(f(l),"g"))].length;t(`${e.file} (${e.what}) carries ${e.caps} ${e.cap==="chrome"?"chrome":"site"} width cap(s)`,w===e.caps,`found ${w}`),t(`${e.file} (${e.what}) no longer types its old ${e.from}px column`,!new RegExp(f(`max-w-[${e.from}px]`)).test(a)),t(`${e.file} (${e.what}) gets wider at the default width, never narrower`,o.default>e.from,`it would NARROW by ${e.from-o.default}px`)}if(n.movedColumns!==void 0){const e=m.reduce((a,l)=>a+l.moved,0);t(`the moved list and the census agree: ${n.movedColumns} column(s) moved across ${m.length} file(s)`,e===n.movedColumns,`the rows add up to ${e} \u2014 a column moved without a row stops the report of what a merchant will SEE change matching the code`)}return h()}export{S as routeContentWidth};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type SourceFile = {
|
|
2
|
+
readonly path: string;
|
|
3
|
+
readonly text: string;
|
|
4
|
+
};
|
|
5
|
+
export type RuleCheck = {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly ok: boolean;
|
|
8
|
+
readonly detail?: string;
|
|
9
|
+
};
|
|
10
|
+
export type RuleReport = {
|
|
11
|
+
readonly rule: string;
|
|
12
|
+
readonly checks: readonly RuleCheck[];
|
|
13
|
+
};
|
|
14
|
+
export declare const RULES_DIR = "lib/source-rules/";
|
|
15
|
+
export declare function isRuleSource(path: string): boolean;
|
|
16
|
+
export declare function ruleChecks(rule: string): {
|
|
17
|
+
check(name: string, ok: boolean, detail?: string): boolean;
|
|
18
|
+
report(): RuleReport;
|
|
19
|
+
};
|
|
20
|
+
export declare function failedChecks(report: RuleReport): readonly RuleCheck[];
|
|
21
|
+
export declare function reportLines(report: RuleReport): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const u="lib/source-rules/";function c(r){return r.startsWith(u)}function s(r){const e=[];return{check(n,t,o){return e.push(t||o===void 0?{name:n,ok:t}:{name:n,ok:t,detail:o}),t},report(){return e.length===0&&e.push({name:`${r} checked nothing`,ok:!1}),{rule:r,checks:e}}}}function i(r){return r.checks.filter(e=>!e.ok)}function h(r){return r.checks.map(e=>e.ok?` ok ${e.name}`:` FAIL ${e.name}${e.detail?` \u2014 ${e.detail}`:""}`)}export{u as RULES_DIR,i as failedChecks,c as isRuleSource,h as reportLines,s as ruleChecks};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
export type SectionThemePreviewOptions = {
|
|
3
|
+
readonly home?: string;
|
|
4
|
+
readonly panels?: readonly string[];
|
|
5
|
+
readonly triggerPanels?: readonly string[];
|
|
6
|
+
readonly menuSheet?: string;
|
|
7
|
+
readonly minFiles: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function sectionThemePreview(files: readonly SourceFile[], options: SectionThemePreviewOptions): RuleReport;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import{ruleChecks as u}from"./rule.js";const o="sf-theme-page",d=new RegExp(`["\`\\s]${o}`),w=/from\s+"@\/components\/ThemeCard"/,m=/ThemeColourRow\b(?!s)/;function f(r,n){const t=r.indexOf(`
|
|
2
|
+
${n} {`);if(t<0)return"";const i=r.indexOf("{",t);return r.slice(i+1,r.indexOf(`
|
|
3
|
+
}`,i)).replace(/\/\*[\s\S]*?\*\//g,"")}function b(r,n){const{check:t,report:i}=u("section theme preview"),h=r.filter(e=>e.path.endsWith(".tsx")),c=e=>r.find(s=>s.path===e)?.text;t("the class reader finds the preview class written into markup",d.test(`<div className="${o} x" />`)),t("\u2026and not a longer class that only starts the same",!d.test(`<div className="x-${o}" />`)),t("a single colour row is told apart from the shared run",m.test("<ThemeColourRow label />")&&!m.test("<ThemeColourRows colors={c} />")),t("the rule-body reader cuts the comment and keeps the declaration",f(`
|
|
4
|
+
.m {
|
|
5
|
+
/* not #eff6ff */
|
|
6
|
+
background: var(--sf-accent-tint);
|
|
7
|
+
}
|
|
8
|
+
`,".m").trim()==="background: var(--sf-accent-tint);"),t(`read the .tsx files (${h.length}, at least ${n.minFiles})`,h.length>=n.minFiles,"fewer means the walk broke");const a=h.filter(e=>d.test(e.text)).map(e=>e.path);t(n.home?`\`.${o}\` is written by exactly one component, and it is ${n.home}`:`no file here writes \`.${o}\``,n.home?a.length===1&&a[0]===n.home:a.length===0,`it is written by ${a.length?a.join(", "):"nothing"}. Two files drawing one preview is the defect card 66503 closes, one level along: they match on the day they are written and drift on every day after \u2014 draw it with the builder's components/ThemeCard.tsx`);for(const e of n.panels??[]){const s=c(e)??"";t(`${e} reaches the drawing by import`,w.test(s),"it imports no shared drawing, so whatever it puts on screen is its own"),t(`${e} draws no colour row of its own`,s.length>0&&!m.test(s),"it reaches a row directly instead of drawing the shared run, which is how the two lists drifted apart the first time")}for(const e of n.triggerPanels??[])t(`${e} reaches the closed drawing by import too`,/ThemeCardTrigger/.test(c(e)??""),"a trigger assembled in the panel is a second file drawing a theme");if(n.menuSheet){const e=c(n.menuSheet)??"",s=f(e,".sf-secttheme-menu"),l=f(e,".sf-multi-menu");t(`the instrument found both rules in ${n.menuSheet}, so the absences below mean something`,/background/.test(s)&&/border/.test(l),`.sf-secttheme-menu read ${s.length} chars and .sf-multi-menu ${l.length}`),t("the theme list fills itself with the accent tint",/background:\s*var\(--sf-accent-tint\)/.test(s)),t("\u2026as the FILL, leaving the shared grey frame alone",!/border(-color)?:/.test(s)),t("\u2026and NO literal colour is written there",!/#[0-9a-fA-F]{3,8}\b/.test(s)&&!/\brgba?\(/.test(s),"it must be `--sf-accent-tint`"),t("the SHARED dropdown container is still untinted",!/--sf-accent-tint/.test(l),"`.sf-multi-menu` is worn by MultiSelectField too; tinting every dropdown was offered and declined")}return i()}export{b as sectionThemePreview};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
export type SiteGutterOptions = {
|
|
3
|
+
readonly minFiles: number;
|
|
4
|
+
readonly gutterFloor: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function siteGutter(files: readonly SourceFile[], options: SiteGutterOptions): RuleReport;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{SITE_GUTTER as f}from"../content/page-width.js";import{ruleChecks as h}from"./rule.js";const g=/px-\[(?:min\([^)]*,)?var\(--sf-site-gutter,(\d+)px\)/g;function u(s){return[...s.matchAll(g)].map(e=>({match:e[0],fallback:Number(e[1])}))}function p(s,e){const{check:a,report:c}=h("site gutter");{const t=i=>`var(--sf-site-gutter,${i}px)`,r=u([`className="px-[${t(40)}]"`,`"max-[720px]:px-[min(20px,${t(60)})]"`,'"px-10"',`"py-[${t(40)}]"`].join(`
|
|
2
|
+
`));a("the reader finds a whole gutter and a narrow step, with their fallbacks",r.length===2&&r[0].fallback===40&&r[1].fallback===60,JSON.stringify(r))}const l=s.filter(t=>/\.tsx?$/.test(t.path));a(`read the source (${l.length} TS/TSX files, at least ${e.minFiles})`,l.length>=e.minFiles,"fewer means the walk broke, and the check below is vacuous");let o=0;const n=[];for(const t of l)for(const r of u(t.text))o+=1,r.fallback!==f.default&&n.push(`${t.path}: ${r.match}`);return a(`found >= ${e.gutterFloor} literal gutters (found ${o})`,o>=e.gutterFloor,"fewer means the gutters went back to a hand-written number, or the reader broke"),a(`every --sf-site-gutter fallback is SITE_GUTTER.default (${f.default}px)`,n.length===0,`${n.length} gutter(s) name a second "standard gutter":
|
|
3
|
+
${n.join(`
|
|
4
|
+
`)}`),c()}export{p as siteGutter};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
type Tokens = Readonly<Record<string, string>>;
|
|
3
|
+
export type CornerPublishers = {
|
|
4
|
+
readonly siteCornerRadiusTokens: (v?: number | string | null) => Tokens;
|
|
5
|
+
readonly cornerRadiusTokens: (v?: number | string | null) => Tokens;
|
|
6
|
+
readonly cornerStyleToPx: Readonly<Record<string, number | null>>;
|
|
7
|
+
readonly normalizeCornerStyle: (v?: string | null) => string;
|
|
8
|
+
readonly sectionCornerStyle: (value: unknown) => unknown;
|
|
9
|
+
};
|
|
10
|
+
export type SiteSquareByDefaultOptions = {
|
|
11
|
+
readonly corners?: CornerPublishers;
|
|
12
|
+
readonly livePage?: string;
|
|
13
|
+
readonly canvas?: string;
|
|
14
|
+
readonly minFiles: number;
|
|
15
|
+
};
|
|
16
|
+
export declare function siteSquareByDefault(files: readonly SourceFile[], options: SiteSquareByDefaultOptions): RuleReport;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{ruleChecks as f}from"./rule.js";const c=["--radius","--radius-xs","--radius-sm","--radius-md","--radius-lg","--radius-xl","--radius-2xl","--radius-3xl","--radius-4xl"],i=(a,r)=>JSON.stringify(a)===JSON.stringify(r);function h(a){const r=[];return a.text.split(`
|
|
2
|
+
`).forEach((s,u)=>{for(const o of s.matchAll(/["'`]([^"'`]*\brounded-full\b[^"'`]*)["'`]/g))/(^|\s)px-(?!0\b|0\.5\b|1\b|px\b)[\d[]/.test(o[1])&&r.push(`${a.path}:${u+1}`)}),r}function g(a,r){const{check:s,report:u}=f("site square by default"),o=e=>a.find(n=>n.path===e),t=r.corners;if(t){const e=t.siteCornerRadiusTokens(null);s("a website that has chosen no corners publishes every step of the radius scale a `rounded-*` utility reads",c.every(n=>n in e),`missing: ${c.filter(n=>!(n in e)).join(", ")}`),s("\u2026and the card's and the button's own properties","--sf-card-radius"in e&&"--sf-button-radius"in e),s("\u2026every one of them 0 \u2014 square",Object.values(e).every(n=>n==="0"),JSON.stringify(e)),s("\u2026exactly what a site that chose 0 publishes",i(e,t.cornerRadiusTokens(0))),s("an absent value and a blank one are the same statement",i(t.siteCornerRadiusTokens(void 0),e)&&i(t.siteCornerRadiusTokens(""),e)&&i(t.siteCornerRadiusTokens(" "),e)),s("a website that has chosen keeps its number",i(t.siteCornerRadiusTokens(16),t.cornerRadiusTokens(16))&&t.siteCornerRadiusTokens(16)["--radius-lg"]==="1rem"),s('the retired `large` word is still 16, and `small` is still "not set"',t.cornerStyleToPx[t.normalizeCornerStyle("large")]===16&&t.cornerStyleToPx[t.normalizeCornerStyle("small")]===null),s('`cornerRadiusTokens(null)` publishes NOTHING \u2014 not set there means "follow the site"',i(t.cornerRadiusTokens(null),{})),s("\u2026so an untouched section declares no radius of its own",t.sectionCornerStyle(null)===void 0,JSON.stringify(t.sectionCornerStyle(null)))}if(r.livePage!==void 0){const e=o(r.livePage);if(s(`read the live page's layout ${r.livePage}`,!!e)){const n=e.text;s("the live page publishes the site's corners through siteCornerRadiusTokens",/const siteCornerTokens = siteCornerRadiusTokens\(/.test(n)&&/\.\.\.siteCornerTokens,/.test(n)),s("\u2026and never through the two that publish nothing for an untouched site",!/\bcornerRadiusTokens\(|\bcornerStyleTokens\(/.test(n))}}if(r.canvas!==void 0){const e=o(r.canvas);if(s(`read the editor canvas ${r.canvas}`,!!e)){const n=e.text;s("the editor canvas publishes them through the same function",/\.\.\.siteCornerRadiusTokens\(cornerRadiusPx\),/.test(n)&&/siteCardRadiusTokens\(siteCornerRadiusTokens\(cornerRadiusPx\)\)/.test(n)),s("\u2026and nowhere through the bare one",!/(?<![A-Za-z])cornerRadiusTokens\(/.test(n))}}{const e=h({path:"fixture.tsx",text:['<button className="rounded-full px-4 py-2">Sign in</button>','<span className="rounded-full px-1 text-xs">3</span>','<span className="h-2 w-2 rounded-full bg-fg" />',"<i className={`rounded-full px-[10px]`} />"].join(`
|
|
3
|
+
`)});s("the pill sweep flags a padded circle, and neither a count badge nor a dot",i(e,["fixture.tsx:1","fixture.tsx:4"]),JSON.stringify(e))}const l=a.filter(e=>e.path.endsWith(".tsx"));s(`the pill sweep read the .tsx files (${l.length}, at least ${r.minFiles})`,l.length>=r.minFiles&&l.length>0);const d=l.flatMap(h);return s("no control with horizontal padding wears the circle utility",d.length===0,d.join(", ")),u()}export{g as siteSquareByDefault};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
export type SiteWidthOptions = {
|
|
3
|
+
readonly minFiles: number;
|
|
4
|
+
readonly capFloor: number;
|
|
5
|
+
readonly sections?: readonly string[];
|
|
6
|
+
readonly tokenSheets?: readonly string[];
|
|
7
|
+
readonly chromeStyled?: readonly string[];
|
|
8
|
+
readonly chromeClassed?: readonly string[];
|
|
9
|
+
readonly carrier?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function siteWidth(files: readonly SourceFile[], options: SiteWidthOptions): RuleReport;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{CHROME_WIDTH_CAP_CLASS as S,PAGE_WIDTH as f,PRE_CHROME_WIDTH_CAP_CLASS as b,PRE_SITE_WIDTH_CAP_CLASS as _,SITE_WIDTH_CAP_CLASS as d,SITE_WIDTH_VAR as g,siteWidthTokens as p}from"../content/page-width.js";import{ruleChecks as C}from"./rule.js";const y=a=>a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),T=a=>/^\s*(\/\/|\*|\/\*)/.test(a);function m(a,i){const t=[];for(const o of a)o.text.split(`
|
|
2
|
+
`).forEach((r,h)=>{i.test(r)&&!T(r)&&t.push({file:o.path,line:h+1,text:r})});return t}function u(a,i){const t=/max-w-\[([^\]]+)\]/.exec(a);if(!t)return null;const o=/^var\((--[\w-]+),\s*([^)]+)\)$/.exec(t[1].trim());return o?i[o[1]]??o[2]:t[1].trim()}function E(a,i){const{check:t,report:o}=C("site width"),r=e=>a.find(n=>n.path===e);{const e=["max-w-[",f.default,"px]"].join(""),n={path:"fixture.tsx",text:[`const a = "mx-auto ${e}";`,`// a note naming ${e} is not a use`,`const b = "${d} px-10";`,`const c = "max-w-[var(${g},1400px)]";`].join(`
|
|
3
|
+
`)},s=m([n],new RegExp(y(_)));t("R1 flags a bare 1600 cap, and not a comment naming one",s.length===1&&s[0].line===1,JSON.stringify(s));const l=m([n],new RegExp(`max-w-\\[var\\(${g}`));t("R2 finds every cap that reads the token, and only the drifted one differs",l.length===2&&l.filter(c=>!c.text.includes(d)).map(c=>c.line).join()==="4",JSON.stringify(l)),t("the cascade reader takes the fallback unset and the published width when set",u(d,p(void 0))===`${f.default}px`&&u(d,p(1400))==="1400px")}const h=a.filter(e=>/\.tsx?$/.test(e.path));t(`read the source (${h.length} TS/TSX files, at least ${i.minFiles})`,h.length>=i.minFiles,"fewer means the walk broke, and every absence below is vacuous");const w=m(h,new RegExp(y(_)));t("R1 no page-width cap is a hand-written 1600 literal any more",w.length===0,`page width is still hard-coded in ${w.length} place(s):
|
|
4
|
+
${w.map(e=>`${e.file}:${e.line}`).join(`
|
|
5
|
+
`)}`);const $=m(h,new RegExp(`max-w-\\[var\\(${g}`));t(`found >= ${i.capFloor} caps that read the site width token (found ${$.length})`,$.length>=i.capFloor,"fewer means caps went back to a number, or the reader broke");const x=$.filter(e=>/max-w-\[var\(--sf-site-width[^\]]*\]/.exec(e.text)?.[0]!==d).map(e=>`${e.file}:${e.line} \u2014 ${e.text.trim()}`);t("R2 every page-width cap reads the site token, with the built-in as its fallback",x.length===0,`${x.length} cap(s) do not match SITE_WIDTH_CAP_CLASS (${d}):
|
|
6
|
+
${x.join(`
|
|
7
|
+
`)}`);for(const e of i.sections??[]){const n=r(e),s=[...(n?.text??"").matchAll(/"([^"]*max-w-\[[^\]]+\][^"]*)"/g)].map(c=>c[1]);if(!t(`${e} carries a page-width cap`,s.length>0,n?"it no longer carries one":"it is not among the files handed in"))continue;const l=s.filter(c=>u(c,p(void 0))!==`${f.default}px`||u(c,p(1400))!=="1400px");t(`${e} renders at ${f.default} unset, and at the site's width when set`,l.length===0,l.join(" \xB7 "))}for(const e of i.tokenSheets??[]){const n=/--sf-site-width:\s*([^;]+);/.exec(r(e)?.text??"");t(`${e} defines the token at the built-in width`,n?.[1].trim()===`${f.default}px`,n?`it defines ${n[1].trim()}`:`${e} defines no --sf-site-width (or was not read)`)}for(const e of i.chromeStyled??[]){const n=r(e)?.text??"";t(`${e} caps at the site's width (chromeWidthStyle)`,/chromeWidthStyle\(/.test(n),`${e} does not cap at the site's width`),t(`${e} does not cap at the built-in number (pageWidthStyle)`,n.length>0&&!/^(?!.*\/\/).*\bpageWidthStyle\(/m.test(n),`${e} still caps at the built-in number, so the chrome ignores the site's width`)}for(const e of i.chromeClassed??[]){const n=r(e)?.text??"";t(`${e} carries ${S}`,n.includes(S),"its tabs sit one gutter wide of the page"),t(`${e} no longer carries the old ${b}`,n.length>0&&!n.includes(b))}if(i.carrier){const e=r(i.carrier)?.text??"",n=/const carrierStyle = \{([\s\S]*?)\} as CSSProperties;/.exec(e),s=/const contentThemeStyle = \{([\s\S]*?)\} as CSSProperties;/.exec(e);t(`${i.carrier} builds a carrierStyle`,!!n)&&n&&t("the site width is published on the accent carrier, so the chrome can read it",/siteWidthTokens\(/.test(n[1]),"the site width is not published on the accent carrier, so the chrome cannot read it"),t(`${i.carrier} builds a contentThemeStyle`,!!s)&&s&&t("\u2026and not on <main> as well \u2014 one emitter, not two",!/siteWidthTokens\(/.test(s[1]),"the site width is still published on <main> as well")}return o()}export{E as siteWidth};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
export type SizeTokenPaintsOptions = {
|
|
3
|
+
readonly baseline?: Readonly<Record<string, number>>;
|
|
4
|
+
readonly minFiles: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function stripComments(src: string): string;
|
|
7
|
+
export declare function brokenCount(src: string): number;
|
|
8
|
+
export declare function sizeTokenPaints(files: readonly SourceFile[], options: SizeTokenPaintsOptions): RuleReport;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import{ruleChecks as d}from"./rule.js";const b=/text-\[(?!length:)[^\]]*--size-[a-zA-Z]+[^\]]*\]/g;function w(t){let i="",e=0;const l=t.length;for(;e<l;){const a=t[e],h=t[e+1];if(a==="/"&&h==="/"){for(;e<l&&t[e]!==`
|
|
2
|
+
`;)i+=" ",e++;continue}if(a==="/"&&h==="*"){for(;e<l&&!(t[e]==="*"&&t[e+1]==="/");)i+=t[e]===`
|
|
3
|
+
`?`
|
|
4
|
+
`:" ",e++;i+=" ",e+=2;continue}if(a==="`"){for(i+=a,e++;e<l;){if(t[e]==="\\"){i+=t[e]+(t[e+1]??""),e+=2;continue}if(t[e]==="`"){i+="`",e++;break}i+=t[e],e++}continue}if(a==='"'||a==="'"){let o=e+1,r=!1;for(;o<l&&t[o]!==`
|
|
5
|
+
`;){if(t[o]==="\\"){o+=2;continue}if(t[o]===a){r=!0;break}o++}if(!r){i+=a,e++;continue}i+=t.slice(e,o+1),e=o+1;continue}i+=a,e++}return i}function f(t){return(w(t).match(b)??[]).length}function x(){const t="text-[";return[{name:"empty source",src:"",want:0},{name:"no size token at all",src:`<p className="${t}13px] font-mono">hi</p>`,want:0},{name:"the broken form",src:`<p className="${t}var(--size-label,13px)]">hi</p>`,want:1},{name:"the working form",src:`<p className="${t}length:var(--size-label,13px)]">hi</p>`,want:0},{name:"two on one line, desktop and phone",src:`<h2 className="${t}var(--size-headingMedium,44px)] max-[800px]:${t}var(--size-headingMedium,32px)]">t</h2>`,want:2},{name:"wrapped in min(), still broken",src:`<h1 className="${t}min(var(--size-headingLarge,64px),var(--sf-heading-max,9999px))]">t</h1>`,want:1},{name:"a line comment is not code",src:`// ${t}var(--size-label,13px)] is the broken form`,want:0},{name:"a block comment is not code",src:`/* ${t}var(--size-label,13px)] */`,want:0},{name:"an apostrophe does not stop a later comment being stripped",src:`<p>it doesn't paint</p>
|
|
6
|
+
// ${t}var(--size-label,13px)]
|
|
7
|
+
<p>that's why</p>`,want:0}]}function $(t,i){const{check:e,report:l}=d("size token paints"),a=i.baseline??{};let h=!0;for(const n of x()){const s=f(n.src);h=e(`fixture "${n.name}": ${n.want}`,s===n.want,`got ${s}`)&&h}if(!h)return e("the extractor is right, so its verdict on the tree means something",!1,"a fixture failed above"),l();const o=t.filter(n=>/\.tsx?$/.test(n.path));e(`read the source (${o.length} TS/TSX files, at least ${i.minFiles})`,o.length>=i.minFiles,"fewer means the walk broke, and the check below is vacuous");const r=[],p=[];let m=0;for(const n of o){const s=f(n.text);m+=s;const c=a[n.path]??0;s>c?r.push(`${n.path} \u2014 ${s} broken, baseline allows ${c}`):s<c&&p.push(`${n.path} \u2014 ${s} broken, baseline says ${c}`)}const u=Object.values(a).reduce((n,s)=>n+s,0);e(`no text size is written in the form that compiles to \`color:\` (${m} broken place(s) found, the baseline allows ${u})`,r.length===0,`${r.length} file(s) over budget:
|
|
8
|
+
${r.join(`
|
|
9
|
+
`)}
|
|
10
|
+
Write it as \`text-[length:var(--size-<role>,<built-in>)]\`. Without \`length:\` Tailwind reads the var() as a
|
|
11
|
+
colour, the declaration is dropped as invalid, and the site's text style setting reaches nothing.`);for(const[n]of Object.entries(a))e(`the baseline entry is still a file: ${n}`,o.some(s=>s.path===n),"delete the stale entry");for(const n of p)e(`improved \u2014 lower its baseline entry: ${n}`,!0);return l()}export{f as brokenCount,$ as sizeTokenPaints,w as stripComments};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
export type SocialIconsImportOptions = {
|
|
3
|
+
readonly helper?: string;
|
|
4
|
+
readonly minFiles: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function socialIconsImport(files: readonly SourceFile[], options: SocialIconsImportOptions): RuleReport;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{ruleChecks as d}from"./rule.js";const a=n=>JSON.stringify(n),g="the website builder's components/socialIcons.tsx (`<SocialMark>`)",$=/\.(ts|tsx|js|jsx|mjs|cjs)$/,u=/(?:from\s*|import\s*\(\s*|require\s*\(\s*|import\s+)["'](@tabler\/[^"']+)["']/g,f=/(?:import\s*\*\s*as\s+\w+\s*from|export\s*\*\s*(?:as\s+\w+\s*)?from)\s*["']@tabler\/[^"']+["']/,x=["Instagram","Facebook","Tiktok","Youtube","X","Linkedin","Pinterest","Whatsapp"],c=n=>[...n.matchAll(u)].map(t=>t[1]);function k(n,t){const{check:r,report:b}=d("social icons import");{const e=["@tabler","icons-react"].join("/"),s=[["a named import",`import { IconBrandX } from ${a(e)};`,!1],["a dynamic import()",`const m = await import(${a(e)});`,!1],["a require()",`const t = require(${a(e)});`,!1],["a side-effect import",`import ${a(e)};`,!1],["a namespace import",`import * as Tabler from ${a(e)};`,!0],["a star re-export",`export * from ${a(e)};`,!0],["a named star re-export",`export * as T from ${a(e)};`,!0]];for(const[l,o,p]of s)r(`the reader finds ${l}`,c(o).length===1),r(`\u2026and ${p?"flags":"does not flag"} it as the whole set`,f.test(o)===p);r("a package that is not Tabler is not Tabler",c('import x from "@tablerish/x";').length===0)}const i=n.filter(e=>$.test(e.path));r(`read the source (${i.length} JS/TS files, at least ${t.minFiles})`,i.length>=t.minFiles,"fewer means the walk broke, and every check below is vacuous");const m=i.filter(e=>c(e.text).length>0&&e.path!==t.helper).map(e=>e.path);r(t.helper?`only ${t.helper} imports Tabler`:"no file here imports Tabler",m.length===0,`Tabler is imported outside its one home:
|
|
2
|
+
${m.join(`
|
|
3
|
+
`)}
|
|
4
|
+
Draw social marks through ${g} instead.`);const h=i.filter(e=>f.test(e.text)).map(e=>e.path);if(r("nothing imports the whole Tabler set",h.length===0,`a namespace or star import from Tabler ships all ~6,000 icons (552 KB gzip):
|
|
5
|
+
${h.join(`
|
|
6
|
+
`)}`),t.helper){const e=i.find(s=>s.path===t.helper);if(r(`read the helper ${t.helper}`,!!e,"the social marks have lost their one home")&&e){const s=[...new Set(c(e.text))];r("the helper imports from @tabler/icons-react only (the React build, per-icon rewritable)",s.length===1&&s[0]==="@tabler/icons-react",JSON.stringify(s));const l=x.flatMap(o=>[`IconBrand${o}`,`IconBrand${o}Filled`]).filter(o=>!new RegExp(`\\b${o},`).test(e.text));r("the helper imports the sixteen marks by name",l.length===0,`no longer imports ${l.join(", ")}`)}}return b()}export{k as socialIconsImport};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
export type SourceStatusIsSharedOptions = {
|
|
3
|
+
readonly home?: string;
|
|
4
|
+
readonly callers: readonly string[];
|
|
5
|
+
readonly minFiles: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function sourceStatusIsShared(files: readonly SourceFile[], options: SourceStatusIsSharedOptions): RuleReport;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ruleChecks as d}from"./rule.js";const n=/sf-blogsource-status/,r=/from "@\/components\/SourceStatus"/;function f(m,t){const{check:s,report:i}=d("source status is shared");s("the import reader finds the shared block's import",r.test('import SourceStatus from "@/components/SourceStatus";')),s("\u2026and not a module whose name only starts the same",!r.test('import X from "@/components/SourceStatusLegacy";')),s("the class reader finds a hand-drawn status line",n.test('<p className="sf-blogsource-status" data-state={s}>'));const a=m.filter(e=>e.path.endsWith(".tsx"));s(`read the .tsx files (${a.length}, at least ${t.minFiles})`,a.length>=t.minFiles,"fewer means the walk broke, and every absence below is vacuous");const h=a.filter(e=>e.path!==t.home&&n.test(e.text)).map(e=>e.path);if(s(t.home?`only ${t.home} draws the status line`:"no file here draws the status line",h.length===0,`${h.join(" \xB7 ")} \u2014 draw it with the builder's components/SourceStatus.tsx`),t.home){const e=a.find(l=>l.path===t.home);s(`\u2026and ${t.home} really does draw it`,!!e&&n.test(e?.text??""))}const o=t.callers.filter(e=>!r.test(a.find(l=>l.path===e)?.text??""));s(`every named caller imports the shared block (${t.callers.length})`,t.callers.length>0&&o.length===0,t.callers.length===0?"no caller is named \u2014 this half measures nothing":o.join(" \xB7 "));const c=a.filter(e=>e.path!==t.home&&!t.callers.includes(e.path)&&r.test(e.text)).map(e=>e.path);return s("\u2026and no caller is missing from the list",c.length===0,c.join(" \xB7 ")),i()}export{f as sourceStatusIsShared};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type RuleReport, type SourceFile } from "./rule.js";
|
|
2
|
+
export type StyleFaceMatchesSizeOptions = {
|
|
3
|
+
readonly drawRoots: readonly string[];
|
|
4
|
+
readonly themeSheet?: string;
|
|
5
|
+
readonly accentMarkup?: readonly string[];
|
|
6
|
+
readonly fontStore?: string;
|
|
7
|
+
readonly minFiles: number;
|
|
8
|
+
readonly pairFloor: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function styleFaceMatchesSize(files: readonly SourceFile[], options: StyleFaceMatchesSizeOptions): RuleReport;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{ruleChecks as m}from"./rule.js";const u={"font-display":"headingLarge","font-heading2":"headingMedium","font-heading3":"headingSmall","font-serif":"accent","font-body":"body","font-mono":"label"};function p(i){const n=/(?<![-\w:])((?:[a-z0-9-]+(?:-\[[^\]]*\])?:)*)text-\[length:[^"'`]*?--size-(headingLarge|headingMedium|headingSmall|accent|body|label)\b/g,t=new Set;for(const o of i.matchAll(n))o[1]===""&&t.add(o[2]);return t.size===1?[...t][0]:null}function $(i){const n=/(?<![-\w])((?:[a-z0-9-]+(?:-\[[^\]]*\])?:)*)(font-display|font-heading2|font-heading3|font-serif|font-body|font-mono)(?![-\w])/g,t=new Set;for(const o of i.matchAll(n))o[1].includes("&")||t.add(o[2]);return[...t]}function f(i){const n=p(i);if(!n)return null;const t=$(i);return t.length===1?{step:n,face:t[0]}:null}const w=i=>i.replace(/\/\*[\s\S]*?\*\//g,"").replace(/^\s*\/\/.*$/gm,"");function S(i,n){const{check:t,report:o}=m("style face matches size"),d=e=>i.find(s=>s.path===e)?.text;{const e=r=>`text-[length:var(--size-${r},44px)]`,s=r=>JSON.stringify(f(r));t("a face and a base size are read as a pair",s(`"font-display ${e("headingMedium")}"`)==='{"step":"headingMedium","face":"font-display"}'),t("\u2026a size inside a variant is not the base",f(`"font-heading2 max-[800px]:${e("headingSmall")}"`)===null),t("\u2026the base wins over a variant on the same line",s(`"font-heading3 ${e("headingSmall")} md:${e("headingMedium")}"`)==='{"step":"headingSmall","face":"font-heading3"}'),t("\u2026a face behind a variant naming `&` is a descendant's, not this element's",f(`"group-[&_em]:font-serif ${e("headingLarge")}"`)===null),t("\u2026while a bare `[&_em]:` face is still read as this element's (the reader as it shipped)",s(`"[&_em]:font-serif ${e("accent")}"`)==='{"step":"accent","face":"font-serif"}'),t("\u2026and two faces on one line are left to a reader",f(`"font-display font-mono ${e("label")}"`)===null)}const l=i.filter(e=>/\.tsx?$/.test(e.path)&&n.drawRoots.some(s=>e.path.startsWith(s)));t(`read the drawing roots ${n.drawRoots.join(", ")} (${l.length} TS/TSX files, at least ${n.minFiles})`,l.length>=n.minFiles,"fewer means the walk broke, and the sweep below is vacuous");let h=0;const g=[];for(const e of l)e.text.split(`
|
|
2
|
+
`).forEach((s,r)=>{const a=f(s);if(!a||(h+=1,a.step==="body"))return;const c=u[a.face];c!==a.step&&g.push(`${e.path}:${r+1} \u2014 size says ${a.step}, face says ${a.face} (${c})`)});if(t(`found >= ${n.pairFloor} lines naming one face and one size (found ${h})`,h>=n.pairFloor,"fewer means the reader broke"),t("\xA73 no text takes its face from one style row and its size from another",g.length===0,`these lines name one style for their size and another for their face, so the merchant cannot fix the text from one row:
|
|
3
|
+
`+g.join(`
|
|
4
|
+
`)),n.themeSheet){const e=d(n.themeSheet)??"";if(t(`read the site stylesheet ${n.themeSheet}`,e.length>0)){for(const a of["2","3"])t(`\xA71 --font-heading${a} exists and resolves --font-role-heading${a}`,new RegExp(`--font-heading${a}:\\s*var\\(--font-role-heading${a}\\)`).test(e),`without it a section cannot DRAW the face Site styles \u25B8 Heading ${a} sets, and that Font control moves zero pixels again`);const s=e.slice(e.indexOf("@theme inline"));for(const a of["2","3"]){const c=s.match(new RegExp(`--font-heading${a}:[^;]*;`))?.[0]??"";t(`\xA72 \u2026and in @theme inline --font-heading${a} points at its OWN role, never back at Heading 1`,c.includes(`--font-role-heading${a}`),`\`--font-heading${a}\` resolves ${c.trim()||"nothing"}`)}t("\xA74 the Accent size is read, in CSS (`em.text-accent` sets var(--size-accent, inherit))",/em\.text-accent\s*\{[^}]*font-size:\s*var\(--size-accent,\s*inherit\)/.test(e.replace(/\/\*[\s\S]*?\*\//g,"")),"the `em.text-accent` rule is gone, so Site styles \u25B8 Accent \u25B8 Size is a dead control again"),t("\xA74 \u2026and it falls back to `inherit`, never a number",!/--size-accent,\s*\d/.test(e));const r=e.slice(e.indexOf(":root"),e.indexOf("@theme inline"));for(const a of["2","3"])t(`\xA75 :root defaults --font-role-heading${a} to Heading 1's face, so the swap is invisible until a face is chosen`,new RegExp(`--font-role-heading${a}:\\s*var\\(--font-role-heading\\)`).test(r),"every site that has never opened that row would change typeface")}}for(const e of n.accentMarkup??[]){const s=d(e);t(`\xA74 ${e} does not set the accent size in the MARKUP`,s!==void 0&&!w(s).includes("--size-accent"),s===void 0?"it is not among the files handed in":"that moves 900 published strings for a size `em.text-accent` already delivers")}if(n.fontStore){const e=d(n.fontStore)??"",s=e.slice(e.indexOf("const pick ="),e.indexOf("// DERIVED from FONT_ROLES"));t(`\xA75 ${n.fontStore}'s normalizeFonts falls Heading 2 and Heading 3 back to Heading 1`,/role === "heading2" \|\| role === "heading3"/.test(s),"a site that chose Oswald for its headings would get two Space Grotesk ones")}return o()}export{S as styleFaceMatchesSize};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const UTILITY_ROOTS: readonly string[];
|
|
2
|
+
export declare const rootsLongestFirst: () => string[];
|
|
3
|
+
export declare function asPreAccentRename(html: string): string;
|
|
4
|
+
export declare function asPreButtonToken(html: string): string;
|
|
5
|
+
export declare function asPreTokenRenames(html: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const o=["bg","text","border","border-t","border-r","border-b","border-l","border-x","border-y","outline","ring","ring-offset","fill","stroke","from","via","to","accent","caret","decoration","divide","shadow","placeholder"],n=()=>[...o].sort((t,r)=>r.length-t.length),e=new RegExp(String.raw`(?<![a-z0-9-])`+String.raw`((?:[a-z0-9@_.\-\[\]\/]+:)*(?:${n().join("|")}))`+String.raw`-accent(-2)?`+String.raw`((?:\/(?:\d{1,3}|\[[^\]\s]*\]))?)`+String.raw`(?![a-z0-9-])`,"g");function c(t){return t.split("--color-accent").join("--color-gold").split("--accent").join("--gold").replace(e,"$1-gold$2$3")}const i=new RegExp(String.raw`(?<![a-z0-9-])`+String.raw`((?:[a-z0-9@_.\-\[\]\/]+:)*(?:${n().join("|")}))`+String.raw`-btn(-2)?`+String.raw`((?:\/(?:\d{1,3}|\[[^\]\s]*\]))?)`+String.raw`(?![a-z0-9-])`,"g"),a=new RegExp(String.raw`(?<![a-z0-9-])((?:[a-z0-9@_.\-\[\]\/]+:)*)text-btn-ink(?![a-z0-9-])`,"g");function l(t){return t.split("--color-btn-ink").join("--color-accent-ink").split("--color-btn-2").join("--color-accent-2").split("--color-btn").join("--color-accent").split("--btn-ink").join("--accent-ink").split("--btn-2").join("--accent-2").split("--btn").join("--accent").replace(a,"$1text-bg").replace(i,"$1-accent$2$3")}function g(t){return c(l(t))}export{o as UTILITY_ROOTS,c as asPreAccentRename,l as asPreButtonToken,g as asPreTokenRenames,n as rootsLongestFirst};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{stripCommentsAndStrings as f}from"./css-source.js";const l=new Map;function u(t){const o=l.get(t);if(o)return o;const p=f(t),e=[],a=/([^{}]+)\{([^{}]*)\}/g;let n;for(;n=a.exec(p);){const s=n[1].trim().replace(/\s+/g," ");if(!s||s.startsWith("@"))continue;const c=new Map;for(const r of n[2].split(";")){const i=r.indexOf(":");i<0||c.set(r.slice(0,i).trim(),r.slice(i+1).trim())}e.push({selector:s,declarations:c})}return l.set(t,e),e}export{u as parseRules};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function d(r,u){let e=r;for(let o=0;o<32;o++){const i=e.lastIndexOf("var(");if(i===-1)return e;let l=0,a=-1;for(let c=i+3;c<e.length;c++)if(e[c]==="(")l++;else if(e[c]===")"&&(l--,l===0)){a=c;break}if(a===-1)throw new Error(`unbalanced var() in: ${r}`);const n=e.slice(i+4,a),t=n.indexOf(","),s=(t===-1?n:n.slice(0,t)).trim(),m=t===-1?"":n.slice(t+1).trim(),f=u[s]??m;if(!f)throw new Error(`var ${s} unset with no fallback in: ${r}`);e=e.slice(0,i)+f+e.slice(a+1)}throw new Error(`var() resolution did not converge: ${r}`)}function h(r,u,e={}){const o=d(r,e).replace(/([\d.]+)rem/g,(n,t)=>`(${Number(t)*16})`).replace(/([\d.]+)px/g,"($1)").replace(/([\d.]+)vw/g,`($1*${u}/100)`);if(!/^[\d\s+*/().,a-z-]+$/i.test(o))throw new Error(`unexpected chars: ${o}`);const a=new Function("calc","min","max","clamp",`return (${o});`)(n=>n,Math.min,Math.max,(n,t,s)=>Math.max(n,Math.min(t,s)));if(!Number.isFinite(a))throw new Error(`did not evaluate to a number: ${r}`);return Math.round(a*100)/100}export{h as evalLength,d as resolveVars};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type Rule = {
|
|
2
|
+
media: string | null;
|
|
3
|
+
selector: string;
|
|
4
|
+
decls: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function blockEnd(src: string, open: number): number;
|
|
7
|
+
export declare function readRules(src: string, media?: string | null, out?: Rule[], selector?: string | null): Rule[];
|
|
8
|
+
export declare function andMedia(outer: string | null, inner: string): string;
|
|
9
|
+
export declare function ownDeclarations(body: string): string;
|
|
10
|
+
export declare function mediaApplies(media: string | null, width: number): boolean;
|
|
11
|
+
export declare function declFor(rules: Rule[], selector: string, prop: string, width: number): string | null;
|
|
12
|
+
export declare function declForClasses(rules: Rule[], classes: readonly string[], prop: string, width: number): string | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function m(e,n){let t=0;for(let i=n;i<e.length;i+=1)if(e[i]==="{")t+=1;else if(e[i]==="}"&&(t-=1,t===0))return i;return e.length}function p(e,n=null,t=[],i=null){if(i!==null){const s=g(e);s.trim()&&t.push({media:n,selector:i,decls:s})}let r=0;for(;r<e.length;){const s=e.indexOf("{",r);if(s===-1)break;const o=m(e,s);let c=r;for(let u=s;u>r;u-=1)if(";}".includes(e[u-1])){c=u;break}const l=e.slice(c,s).trim(),f=e.slice(s+1,o);l.startsWith("@media")?p(f,h(n,l),t,i):l.startsWith("@")?p(f,n,t,i):l&&p(f,n,t,l),r=o+1}return t}function h(e,n){const t=n.replace(/^@media\s*/,"").trim();return e===null?`@media ${t}`:`${e} and ${t}`}function g(e){let n="",t=0;for(;t<e.length;){const i=e.indexOf("{",t);if(i===-1){n+=e.slice(t);break}let r=i;for(;r>t&&!";}".includes(e[r-1]);)r-=1;n+=e.slice(t,r),t=m(e,i)+1}return n}function d(e,n){if(!e)return!0;const i=e.replace(/^@media\s*/,"").trim().split(/\s+and\s+/).map(r=>r.trim());for(const r of i){const s=/^\(\s*max-width:\s*([\d.]+)(px|rem)\s*\)$/.exec(r),o=/^\(\s*min-width:\s*([\d.]+)(px|rem)\s*\)$/.exec(r),c=/^\(\s*width\s*(<=|>=|<|>)\s*([\d.]+)(px|rem)\s*\)$/.exec(r),l=/^\(\s*([\d.]+)(px|rem)\s*(<=|>=|<|>)\s*width\s*\)$/.exec(r);if(s){if(n>a(s[1],s[2]))return!1}else if(o){if(n<a(o[1],o[2]))return!1}else if(c){if(!x(n,c[1],a(c[2],c[3])))return!1}else if(l){const f={"<":">",">":"<","<=":">=",">=":"<="}[l[3]];if(!x(n,f,a(l[1],l[2])))return!1}else return!1}return!0}const $=16;function a(e,n){return n==="rem"?Number(e)*$:Number(e)}function x(e,n,t){return n==="<"?e<t:n==="<="?e<=t:n===">"?e>t:e>=t}function w(e,n,t,i){let r=null;for(const s of e){if(!d(s.media,i)||!s.selector.split(",").map(f=>f.trim()).includes(n))continue;const c=new RegExp(`(?:^|;)\\s*${t}\\s*:\\s*([^;]+)`,"g");let l;for(;(l=c.exec(s.decls))!==null;)r=l[1].trim()}return r}function O(e,n,t,i){const r=new Set(n.map(o=>`.${o}`));let s=null;for(const o of e){if(!d(o.media,i)||!o.selector.split(/(?<!\\),/).map(u=>u.trim().replace(/\\/g,"")).some(u=>r.has(u)))continue;const l=new RegExp(`(?:^|;)\\s*${t}\\s*:\\s*([^;]+)`,"g");let f;for(;(f=l.exec(o.decls))!==null;)s=f[1].trim()}return s}export{h as andMedia,m as blockEnd,w as declFor,O as declForClasses,d as mediaApplies,g as ownDeclarations,p as readRules};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
function l(t,u){let e="",n=0;for(;n<t.length;){const i=t[n];if(i==="/"&&t[n+1]==="*"){const o=t.indexOf("*/",n+2),r=o<0?t.length:o+2;e+=t.slice(n,r).replace(/[^\n]/g," "),n=r;continue}if((i==='"'||i==="'")&&!u){const o=i;for(e+=" ",n++;n<t.length&&t[n]!==o;)e+=t[n]===`
|
|
2
|
+
`?`
|
|
3
|
+
`:" ",t[n]==="\\"&&(n++,n<t.length&&(e+=" ")),n++;e+=" ",n++;continue}e+=i,n++}return e}function f(t){return l(t,!1)}function h(t){return l(t,!0)}export{h as stripComments,f as stripCommentsAndStrings};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SourceFile } from "../rule.js";
|
|
2
|
+
export declare const BUILDERS: string[];
|
|
3
|
+
export type FieldLabel = {
|
|
4
|
+
line: number;
|
|
5
|
+
label: string;
|
|
6
|
+
item: boolean;
|
|
7
|
+
builder?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function readLabels(src: string): FieldLabel[];
|
|
10
|
+
export declare function readRowLabels(src: string): FieldLabel[];
|
|
11
|
+
export declare const JSX_LABEL: RegExp;
|
|
12
|
+
export declare function readJsxLabels(src: string): FieldLabel[];
|
|
13
|
+
export declare function isPanelFile(file: SourceFile): boolean;
|
|
14
|
+
export declare function readPanelLabels(src: string): FieldLabel[];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
const m=["text","textarea","plainText","copyText","plainTextarea","linkField","textList","upload","toggle","checkbox","select","searchSelect","segmented","slider","colorInput","focalPicker","sizeField","backgroundField","multiSelect"];function A(e){const t=e.split(`
|
|
2
|
+
`),n=new RegExp(`(?<![A-Za-z0-9_.$])(${m.join("|")})\\("((?:[^"\\\\]|\\\\.)*)"`,"g"),l=/label:\s*"((?:[^"\\]|\\.)*)"/g,i=new RegExp(`(?<![A-Za-z0-9_.$])(${m.join("|")})\\(\\s*$`),o=/^\s*"((?:[^"\\]|\\.)*)"\s*,/,r=[];let c=0,h=0;const f=[];return t.forEach((a,u)=>{const d=u+1,b=h>0;for(const s of a.matchAll(n))r.push({line:d,label:s[2],item:b,builder:s[1]});const g=i.exec(a);if(g){let s=u+1;for(;s<t.length&&t[s].trim()==="";)s+=1;const p=s<t.length?o.exec(t[s]):null;p&&r.push({line:s+1,label:p[1],item:b,builder:g[1]})}for(const s of a.matchAll(l)){const p=a.slice(0,s.index??0),x=u>0?t[u-1].trim():"";(/type:\s*"/.test(p)||/type:\s*"[a-z]+"\s*,?$/.test(x))&&r.push({line:d,label:s[1],item:b})}for(let s=0;s<a.length;s+=1)a[s]==="{"?(/arrayFields:\s*$/.test(a.slice(0,s))&&(f.push(c),h+=1),c+=1):a[s]==="}"&&(c-=1,f.length&&c===f[f.length-1]&&(f.pop(),h-=1))}),r}function L(e,t){let n=0,l=-1;for(let o=t-1;o>=0;o-=1){const r=e[o];if(r==="}")n+=1;else if(r==="{"){if(n===0){l=o;break}n-=1}}if(l<0)return"";let i=0;for(let o=l;o<e.length;o+=1)if(e[o]==="{")i+=1;else if(e[o]==="}"&&(i-=1,i===0))return e.slice(l,o+1);return e.slice(l)}function $(e){let t=0,n="";for(const l of e){if(l==="{"||l==="["){t+=1;continue}if(l==="}"||l==="]"){t-=1;continue}t===1&&(n+=l)}return n}function w(e,t){const n=$(L(e,t));return/(^|[,;])\s*(value|fields|render|resolveFields|panelId|href|amount|meta)\s*:/.test(n)}function y(e){const t=e.split(`
|
|
3
|
+
`),n=[];for(let o=0,r=0;o<t.length;o+=1)n.push(r),r+=t[o].length+1;const l=/label:\s*"((?:[^"\\]|\\.)*)"/g,i=[];return t.forEach((o,r)=>{for(const c of o.matchAll(l))w(e,n[r]+(c.index??0))||i.push({line:r+1,label:c[1],item:!1})}),i}const F=/(?<![-A-Za-z])label="((?:[^"\\]|\\.)*)"/g;function E(e){const t=[];return e.split(`
|
|
4
|
+
`).forEach((n,l)=>{for(const i of n.matchAll(F))t.push({line:l+1,label:i[1],item:!1})}),t}const j=/className="sf-field"|from\s*["']efficient-easel/;function S(e){return e.path.startsWith("components/")&&e.path.endsWith(".tsx")&&j.test(e.text)}const k=6;function O(e,t){for(let n=t;n<Math.min(e.length,t+k);n+=1){if(/^\s*\}[,)]?\s*$/.test(e[n])&&n>t)return!1;if(/\bonClick:/.test(e[n]))return!0}return!1}function N(e){const t=new Set,n=e.split(`
|
|
5
|
+
`);return[...A(e),...y(e),...E(e)].filter(l=>{const i=`${l.line}:${l.label}`;return t.has(i)?!1:(t.add(i),!O(n,l.line-1))})}export{m as BUILDERS,F as JSX_LABEL,S as isPanelFile,E as readJsxLabels,A as readLabels,N as readPanelLabels,y as readRowLabels};
|