@birdapi/velinstyle 0.7.0 → 0.9.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/README.de.md +135 -224
- package/README.md +134 -223
- package/cli/blueprint.js +8 -0
- package/cli/blueprints/bottom-nav-mobile.html +17 -0
- package/cli/blueprints/cookie-consent.html +9 -0
- package/cli/blueprints/empty-state.html +5 -0
- package/cli/blueprints/filter-bar.html +15 -0
- package/cli/blueprints/notification-center.html +13 -0
- package/cli/blueprints/onboarding.html +23 -0
- package/cli/blueprints/pricing-table.html +20 -0
- package/cli/blueprints/settings-panel.html +20 -0
- package/cli/cli-manifest.json +97 -0
- package/cli/docgen/extract-a11y.js +89 -0
- package/cli/docgen/extract-attributes.js +95 -0
- package/cli/docgen/extract-cli.js +50 -0
- package/cli/docgen/extract-components.js +175 -0
- package/cli/docgen/extract-meta.js +32 -0
- package/cli/docgen/extract-rules.js +76 -0
- package/cli/docgen/extract-tokens.js +79 -0
- package/cli/docgen/extract-utilities.js +75 -0
- package/cli/docgen/markdown.js +43 -0
- package/cli/docs-generate.js +196 -0
- package/cli/index.js +236 -5
- package/cli/layout-audit.js +325 -0
- package/cli/meta.js +97 -0
- package/cli/perf-audit.js +174 -0
- package/cli/pii-scanner.js +134 -0
- package/cli/scaffold-recipes.json +70 -0
- package/cli/scaffold.js +155 -0
- package/cli/scanner-rules-data.js +62 -0
- package/cli/scanner.js +136 -1
- package/cli/search-index.js +163 -0
- package/cli/tokens-build.js +21 -1
- package/cli/tokens-validate.js +69 -0
- package/components/a11y-entry.js +42 -0
- package/components/a11y-utils.js +71 -0
- package/components/focus-manager.js +45 -8
- package/components/index.js +40 -11
- package/components/runtime/component-loaders.js +43 -0
- package/components/runtime/index.js +77 -0
- package/components/runtime-entry.js +10 -0
- package/components/sanitize.js +135 -28
- package/components/shadow-a11y-styles.js +18 -0
- package/components/velin-accordion.js +12 -0
- package/components/velin-announcer.js +35 -0
- package/components/velin-bottom-nav.js +108 -0
- package/components/velin-carousel.js +2 -2
- package/components/velin-code-block.js +142 -0
- package/components/velin-combobox.js +149 -0
- package/components/velin-command.js +142 -0
- package/components/velin-copy.js +18 -5
- package/components/velin-counter.js +155 -0
- package/components/velin-dialog.js +9 -1
- package/components/velin-email.js +94 -0
- package/components/velin-flip.js +220 -0
- package/components/velin-icon.js +99 -22
- package/components/velin-lightbox.js +8 -3
- package/components/velin-live-dot.js +90 -0
- package/components/velin-menubar.js +83 -0
- package/components/velin-modal.js +6 -3
- package/components/velin-popover.js +34 -2
- package/components/velin-rating.js +91 -0
- package/components/velin-reveal.js +22 -0
- package/components/velin-scroll-top.js +1 -1
- package/components/velin-search.js +267 -0
- package/components/velin-secure-field.js +99 -0
- package/components/velin-segmented-control.js +108 -0
- package/components/velin-sheet.js +107 -0
- package/components/velin-sparkline.js +227 -0
- package/components/{velin-stepper-wc.js → velin-stepper.js} +37 -8
- package/components/velin-theme-toggle.js +284 -60
- package/components/velin-toast.js +4 -0
- package/components/{velin-tooltip-wc.js → velin-tooltip.js} +22 -3
- package/core/a11y/component-contracts.json +221 -0
- package/core/attributes/index.js +6 -0
- package/core/attributes/registry.js +268 -0
- package/core/highlight/highlight.js +114 -0
- package/core/highlight/index.js +29 -0
- package/core/highlight/languages/_utils.js +52 -0
- package/core/highlight/languages/blade.js +16 -0
- package/core/highlight/languages/css.js +26 -0
- package/core/highlight/languages/html.js +17 -0
- package/core/highlight/languages/js.js +58 -0
- package/core/highlight/languages/json.js +14 -0
- package/core/highlight/languages/markdown.js +16 -0
- package/core/highlight/languages/php.js +39 -0
- package/core/highlight/languages/plain.js +7 -0
- package/core/highlight/languages/shell.js +33 -0
- package/core/highlight/languages/sql.js +20 -0
- package/core/highlight/languages/typescript.js +28 -0
- package/core/highlight/observe.js +53 -0
- package/core/highlight/registry.js +107 -0
- package/core/highlight/render.js +35 -0
- package/core/highlight/types.js +28 -0
- package/core/meta/build.js +293 -0
- package/core/meta/index.js +17 -0
- package/core/meta/schema.js +49 -0
- package/core/motion/effects.js +54 -0
- package/core/motion/index.js +76 -0
- package/core/motion/scheduler.js +72 -0
- package/core/motion/scroll.js +32 -0
- package/core/motion/stagger.js +24 -0
- package/core/search/docs-url.js +96 -0
- package/core/search/engine.js +139 -0
- package/core/search/highlight.js +48 -0
- package/core/search/index.js +126 -0
- package/core/search/providers.js +36 -0
- package/core/search/types.js +61 -0
- package/core/search/worker-client.js +56 -0
- package/core/search/worker.js +20 -0
- package/dist/chunks/a11y-utils-IRC7LOPF.js +16 -0
- package/dist/chunks/attributes-3BZLI7XF.js +364 -0
- package/dist/chunks/attributes-4N5JP4CG.js +364 -0
- package/dist/chunks/attributes-5Q2WXGRC.js +374 -0
- package/dist/chunks/attributes-AIR7SVLK.js +364 -0
- package/dist/chunks/attributes-BGRN5XZO.js +364 -0
- package/dist/chunks/attributes-BVSQAYLR.js +364 -0
- package/dist/chunks/attributes-CDYFUY7Y.js +364 -0
- package/dist/chunks/attributes-JMYJZBPE.js +364 -0
- package/dist/chunks/attributes-R5JY3X4O.js +374 -0
- package/dist/chunks/attributes-STYCS3O5.js +391 -0
- package/dist/chunks/attributes-TNSPQTHX.js +374 -0
- package/dist/chunks/attributes-TPANLMLF.js +391 -0
- package/dist/chunks/blade-LIQLVKZQ.js +19 -0
- package/dist/chunks/browser-MRKYZPEH.js +1149 -0
- package/dist/chunks/chunk-2354NEBP.js +109 -0
- package/dist/chunks/chunk-2VNA7G7S.js +109 -0
- package/dist/chunks/chunk-42VVPW6N.js +132 -0
- package/dist/chunks/chunk-7FFAMRFV.js +44 -0
- package/dist/chunks/chunk-7MCGPHVC.js +69 -0
- package/dist/chunks/chunk-AQLRJSGG.js +109 -0
- package/dist/chunks/chunk-EWPSDMQT.js +109 -0
- package/dist/chunks/chunk-F7OLHTGX.js +22 -0
- package/dist/chunks/chunk-J472ERN2.js +242 -0
- package/dist/chunks/chunk-LA7264K4.js +85 -0
- package/dist/chunks/chunk-M5CHQVP6.js +61 -0
- package/dist/chunks/chunk-MKZP2TBH.js +89 -0
- package/dist/chunks/chunk-QTJJ3DPO.js +109 -0
- package/dist/chunks/chunk-RDE3QE7Z.js +89 -0
- package/dist/chunks/chunk-RSMRR27R.js +109 -0
- package/dist/chunks/chunk-RZQVWFHW.js +109 -0
- package/dist/chunks/chunk-SRL6EWHP.js +109 -0
- package/dist/chunks/chunk-U4XHMZ7D.js +109 -0
- package/dist/chunks/chunk-V5YIUA6G.js +109 -0
- package/dist/chunks/chunk-XIET2VSZ.js +109 -0
- package/dist/chunks/chunk-XYLJ2GCB.js +205 -0
- package/dist/chunks/chunk-Z4JYQ2XX.js +51 -0
- package/dist/chunks/chunk-ZPSPKUYD.js +109 -0
- package/dist/chunks/css-MQ5YU77P.js +28 -0
- package/dist/chunks/highlight-GJHHV4KM.js +37 -0
- package/dist/chunks/highlight-XTHRKBA3.js +37 -0
- package/dist/chunks/html-ZODC4DLQ.js +20 -0
- package/dist/chunks/js-MNSNYTJU.js +7 -0
- package/dist/chunks/json-VEM2HFH3.js +17 -0
- package/dist/chunks/markdown-E7OPGS3S.js +19 -0
- package/dist/chunks/php-MLL7T3LT.js +41 -0
- package/dist/chunks/plain-LJJDC5TN.js +8 -0
- package/dist/chunks/runtime-entry.js +14 -0
- package/dist/chunks/shell-QTRK4U4E.js +35 -0
- package/dist/chunks/sql-PPWLHGJ2.js +23 -0
- package/dist/chunks/typescript-JT3CCYWL.js +30 -0
- package/dist/chunks/velin-accordion-FBIPAEXG.js +105 -0
- package/dist/chunks/velin-accordion-QAQO7BI7.js +117 -0
- package/dist/chunks/velin-announcer-PVTXXCWR.js +33 -0
- package/dist/chunks/velin-bottom-nav-2N2TYWOF.js +86 -0
- package/dist/chunks/velin-bottom-nav-7GEMPIM6.js +86 -0
- package/dist/chunks/velin-bottom-nav-CTU6FFZV.js +101 -0
- package/dist/chunks/velin-bottom-nav-N3DFQR66.js +86 -0
- package/dist/chunks/velin-carousel-Y5VCOABZ.js +213 -0
- package/dist/chunks/velin-carousel-ZTBANFQQ.js +213 -0
- package/dist/chunks/velin-code-block-ASUBTAQV.js +119 -0
- package/dist/chunks/velin-code-block-CIAURDRJ.js +132 -0
- package/dist/chunks/velin-code-block-TSUKPY4C.js +123 -0
- package/dist/chunks/velin-code-block-Y6CM726Q.js +119 -0
- package/dist/chunks/velin-collapse-5XYBUIID.js +80 -0
- package/dist/chunks/velin-combobox-2KDTCQ7N.js +155 -0
- package/dist/chunks/velin-combobox-NXS4QB4R.js +155 -0
- package/dist/chunks/velin-combobox-UGA56HP3.js +155 -0
- package/dist/chunks/velin-command-CKDNO4BW.js +148 -0
- package/dist/chunks/velin-command-M7RNHUPR.js +136 -0
- package/dist/chunks/velin-command-ONKO2A3J.js +136 -0
- package/dist/chunks/velin-command-TMLSRGIU.js +136 -0
- package/dist/chunks/velin-copy-G2TZ5YBB.js +81 -0
- package/dist/chunks/velin-copy-NYDBOKXT.js +81 -0
- package/dist/chunks/velin-copy-T5KDTDXP.js +81 -0
- package/dist/chunks/velin-countdown-4ZVDGK4R.js +101 -0
- package/dist/chunks/velin-countdown-5XSTN3BH.js +101 -0
- package/dist/chunks/velin-countdown-7AIY66CU.js +101 -0
- package/dist/chunks/velin-counter-ISCTEAHI.js +118 -0
- package/dist/chunks/velin-counter-L5FYJF7A.js +121 -0
- package/dist/chunks/velin-dialog-D2GUHSCD.js +144 -0
- package/dist/chunks/velin-dialog-ELFOZIXZ.js +152 -0
- package/dist/chunks/velin-dialog-GTTZI564.js +147 -0
- package/dist/chunks/velin-dialog-IJLFDXAJ.js +144 -0
- package/dist/chunks/velin-dialog-TNS3WXMB.js +144 -0
- package/dist/chunks/velin-drawer-BE6TYN3H.js +130 -0
- package/dist/chunks/velin-drawer-DLL2GLT6.js +130 -0
- package/dist/chunks/velin-drawer-MJS6WCF7.js +130 -0
- package/dist/chunks/velin-dropdown-PLUIFA5M.js +172 -0
- package/dist/chunks/velin-email-N5B5ZYTE.js +90 -0
- package/dist/chunks/velin-email-NTM6DG3V.js +90 -0
- package/dist/chunks/velin-email-RBX6ZR4S.js +90 -0
- package/dist/chunks/velin-haptic-4QQTM3RK.js +50 -0
- package/dist/chunks/velin-icon-7M76DOPB.js +164 -0
- package/dist/chunks/velin-icon-KZO3IEHG.js +164 -0
- package/dist/chunks/velin-icon-WTFPG2DD.js +191 -0
- package/dist/chunks/velin-icon-ZTSKUOVN.js +193 -0
- package/dist/chunks/velin-lightbox-6E7CAATP.js +149 -0
- package/dist/chunks/velin-lightbox-I75U6JF4.js +140 -0
- package/dist/chunks/velin-lightbox-QOTB4PRL.js +140 -0
- package/dist/chunks/velin-lightbox-UCD6N7HF.js +140 -0
- package/dist/chunks/velin-live-dot-RJHX2DGY.js +74 -0
- package/dist/chunks/velin-live-dot-XHUZCIDZ.js +67 -0
- package/dist/chunks/velin-menubar-I426AED2.js +86 -0
- package/dist/chunks/velin-menubar-J66JXRWW.js +86 -0
- package/dist/chunks/velin-menubar-SFEXJE56.js +86 -0
- package/dist/chunks/velin-modal-2HOBXE6F.js +176 -0
- package/dist/chunks/velin-modal-5HYNNNUI.js +176 -0
- package/dist/chunks/velin-modal-AEOTZ67O.js +177 -0
- package/dist/chunks/velin-modal-GLI2XZZB.js +176 -0
- package/dist/chunks/velin-persist-DFPL3QP4.js +110 -0
- package/dist/chunks/velin-popover-EXINRIGJ.js +145 -0
- package/dist/chunks/velin-popover-HQW6Q2Y6.js +172 -0
- package/dist/chunks/velin-popover-OXX7MRZ6.js +145 -0
- package/dist/chunks/velin-popover-T72HK3DF.js +151 -0
- package/dist/chunks/velin-popover-YKCCROJG.js +145 -0
- package/dist/chunks/velin-progress-ring-E4YRLZHH.js +66 -0
- package/dist/chunks/velin-rating-6CDBI6DG.js +91 -0
- package/dist/chunks/velin-rating-N4DI2XNL.js +91 -0
- package/dist/chunks/velin-rating-XRQJV4LO.js +91 -0
- package/dist/chunks/velin-scroll-top-F2FCZNYD.js +58 -0
- package/dist/chunks/velin-scroll-top-OHYVVHHY.js +58 -0
- package/dist/chunks/velin-scrollspy-OKCT4FDV.js +50 -0
- package/dist/chunks/velin-search-2XRQFBCS.js +556 -0
- package/dist/chunks/velin-search-4YHJWYSS.js +556 -0
- package/dist/chunks/velin-search-7B4KJ6TX.js +557 -0
- package/dist/chunks/velin-search-LWHY23Q5.js +474 -0
- package/dist/chunks/velin-secure-field-6PV7RZDJ.js +95 -0
- package/dist/chunks/velin-secure-field-H7FSBUJW.js +95 -0
- package/dist/chunks/velin-secure-field-OFNAXCDP.js +95 -0
- package/dist/chunks/velin-segmented-control-EKL4E3BL.js +109 -0
- package/dist/chunks/velin-segmented-control-GYD5BIXQ.js +109 -0
- package/dist/chunks/velin-segmented-control-PQX6LKOJ.js +109 -0
- package/dist/chunks/velin-sheet-3OGI4HD7.js +119 -0
- package/dist/chunks/velin-sheet-OUK6572T.js +119 -0
- package/dist/chunks/velin-sheet-R2Y67X6H.js +119 -0
- package/dist/chunks/velin-sparkline-GVEANJVW.js +184 -0
- package/dist/chunks/velin-sparkline-KDA3GQAN.js +162 -0
- package/dist/chunks/velin-sparkline-XVTFC6GE.js +181 -0
- package/dist/chunks/velin-stepper-2ZIHLKGM.js +141 -0
- package/dist/chunks/velin-stepper-7ECRUYHD.js +148 -0
- package/dist/chunks/velin-stepper-FYEUEWE6.js +141 -0
- package/dist/chunks/velin-stepper-X54WGHDG.js +141 -0
- package/dist/chunks/velin-stepper-wc-QU3KRVQR.js +133 -0
- package/dist/chunks/velin-tabs-5UQ5ZVMV.js +138 -0
- package/dist/chunks/velin-theme-toggle-32J2CBNC.js +291 -0
- package/dist/chunks/velin-theme-toggle-BNNS7WLV.js +285 -0
- package/dist/chunks/velin-theme-toggle-NO7V4TWI.js +285 -0
- package/dist/chunks/velin-theme-toggle-QELCJM2Q.js +291 -0
- package/dist/chunks/velin-toast-OMMOA2DK.js +109 -0
- package/dist/chunks/velin-toast-RCZ4CSUC.js +109 -0
- package/dist/chunks/velin-toast-RSF2IHT3.js +113 -0
- package/dist/chunks/velin-toast-WEUBTJG6.js +109 -0
- package/dist/chunks/velin-tooltip-664IOHZA.js +112 -0
- package/dist/chunks/velin-tooltip-BDHGRTQB.js +112 -0
- package/dist/chunks/velin-tooltip-HVUOKNVX.js +112 -0
- package/dist/chunks/velin-tooltip-wc-LXDVT6DE.js +103 -0
- package/dist/chunks/worker-client-R6DBYPFT.js +46 -0
- package/dist/llms.test.txt +40 -0
- package/dist/llms.txt +40 -0
- package/dist/search-index.json +1773 -0
- package/dist/search-index.test.json +1773 -0
- package/dist/themes/brutalist.min.css +1 -1
- package/dist/themes/midnight.min.css +1 -1
- package/dist/themes/pastel.min.css +1 -1
- package/dist/themes/retro.min.css +1 -1
- package/dist/themes/sunset.min.css +1 -1
- package/dist/velin-agent.json +684 -0
- package/dist/velin-agent.test.json +684 -0
- package/dist/velinstyle-components.iife.js +7959 -2199
- package/dist/velinstyle-components.js +7982 -2174
- package/dist/velinstyle-components.min.js +521 -90
- package/dist/velinstyle.css +1290 -282
- package/dist/velinstyle.d.ts +54 -0
- package/dist/velinstyle.min.css +1 -1
- package/examples/search-index.schema.json +33 -0
- package/package.json +57 -10
- package/src/a11y/authentication.css +37 -0
- package/src/a11y/consistent-help.css +22 -0
- package/src/a11y/dragging-alternatives.css +30 -0
- package/src/a11y/focus-appearance.css +20 -0
- package/src/a11y/high-contrast-aaa.css +16 -26
- package/src/a11y/security.css +18 -0
- package/src/a11y/skip-link.css +3 -1
- package/src/a11y/sr-only.css +1 -1
- package/src/base/reset.css +0 -25
- package/src/base/wc-placeholder.css +102 -0
- package/src/components/attributes.css +42 -0
- package/src/components/highlight.css +182 -0
- package/src/components/input-group.css +1 -2
- package/src/components/nav.css +151 -151
- package/src/components/search.css +95 -0
- package/src/components/stepper.css +2 -4
- package/src/components/timeline.css +1 -2
- package/src/components/tooltip.css +2 -4
- package/src/layout/container.css +5 -5
- package/src/layout/grid.css +8 -8
- package/src/layout/patterns.css +5 -5
- package/src/themes/brutalist.css +2 -11
- package/src/themes/corporate.css +0 -9
- package/src/themes/earth.css +0 -9
- package/src/themes/forest.css +0 -9
- package/src/themes/midnight.css +1 -10
- package/src/themes/neon.css +0 -9
- package/src/themes/nordic.css +0 -9
- package/src/themes/ocean.css +0 -9
- package/src/themes/pastel.css +1 -10
- package/src/themes/retro.css +2 -11
- package/src/themes/sharp.css +0 -7
- package/src/themes/soft.css +0 -7
- package/src/themes/sunset.css +1 -10
- package/src/tokens/color.css +45 -46
- package/src/tokens/fonts.css +10 -0
- package/src/tokens/motion.css +7 -0
- package/src/tokens/typography.css +4 -5
- package/src/utilities/animation.css +99 -3
- package/src/utilities/chart-animation.css +101 -0
- package/src/utilities/display.css +3 -3
- package/src/utilities/divide.css +2 -6
- package/src/utilities/filter-effects.css +103 -0
- package/src/utilities/responsive.css +4 -4
- package/src/utilities/safe-area.css +39 -0
- package/src/utilities/scroll-animation.css +34 -0
- package/src/utilities/spacing.css +17 -8
- package/src/utilities/text.css +4 -1
- package/src/velinstyle.css +12 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const VISIBLE_CLASS = 'velin-in-view';
|
|
2
|
+
|
|
3
|
+
const EFFECT_MAP = {
|
|
4
|
+
reveal: ['velin-animate-on-scroll'],
|
|
5
|
+
fade: ['velin-animate-on-scroll', 'velin-animate-on-scroll--fade'],
|
|
6
|
+
slide: ['velin-animate-on-scroll', 'velin-animate-on-scroll--slide-up'],
|
|
7
|
+
'slide-up': ['velin-animate-on-scroll', 'velin-animate-on-scroll--slide-up'],
|
|
8
|
+
'slide-down': ['velin-animate-on-scroll', 'velin-animate-on-scroll--slide-down'],
|
|
9
|
+
'slide-left': ['velin-animate-on-scroll', 'velin-animate-on-scroll--slide-left'],
|
|
10
|
+
'slide-right': ['velin-animate-on-scroll', 'velin-animate-on-scroll--slide-right'],
|
|
11
|
+
scale: ['velin-animate-on-scroll', 'velin-animate-on-scroll--scale'],
|
|
12
|
+
parallax: ['velin-parallax'],
|
|
13
|
+
'parallax-slow': ['velin-parallax', 'velin-parallax--slow'],
|
|
14
|
+
hover: ['velin-animate-hover'],
|
|
15
|
+
'hover-lift': ['velin-animate-hover'],
|
|
16
|
+
flip: ['velin-animate-scale-in'],
|
|
17
|
+
blur: ['velin-animate-fade-in'],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Apply motion effect classes from velin-* attribute values.
|
|
22
|
+
* @param {HTMLElement} el
|
|
23
|
+
* @param {Record<string, string>} attrs keyed by effect name
|
|
24
|
+
*/
|
|
25
|
+
export function applyEffects(el, attrs = {}) {
|
|
26
|
+
const classes = new Set();
|
|
27
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
28
|
+
if (value === 'false' || value === 'off') continue;
|
|
29
|
+
const mapped = EFFECT_MAP[name];
|
|
30
|
+
if (mapped) mapped.forEach((c) => classes.add(c));
|
|
31
|
+
if (name === 'slide' && value && value !== 'true' && value !== '') {
|
|
32
|
+
const key = `slide-${value}`;
|
|
33
|
+
EFFECT_MAP[key]?.forEach((c) => classes.add(c));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
classes.forEach((c) => el.classList.add(c));
|
|
37
|
+
if (!el.classList.contains('velin-animate-on-scroll') && attrs.reveal !== undefined) {
|
|
38
|
+
el.classList.add('velin-animate-on-scroll');
|
|
39
|
+
}
|
|
40
|
+
return [...classes];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function markVisible(el) {
|
|
44
|
+
el.classList.add(VISIBLE_CLASS);
|
|
45
|
+
el.dataset.velinVisible = 'true';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function prefersReducedMotion() {
|
|
49
|
+
return (
|
|
50
|
+
typeof window !== 'undefined' &&
|
|
51
|
+
window.matchMedia &&
|
|
52
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { observeInView, disconnectInViewObserver } from './scheduler.js';
|
|
2
|
+
import { applyEffects, markVisible, prefersReducedMotion, VISIBLE_CLASS } from './effects.js';
|
|
3
|
+
import { enhanceStagger } from './stagger.js';
|
|
4
|
+
import { bindSmoothScroll } from './scroll.js';
|
|
5
|
+
|
|
6
|
+
const MOTION_ATTRS = ['velin-reveal', 'velin-fade', 'velin-slide', 'velin-scale', 'velin-parallax', 'velin-hover'];
|
|
7
|
+
|
|
8
|
+
let teardownFns = [];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Initialize motion system: in-view reveals, stagger, smooth scroll anchors.
|
|
12
|
+
* @param {object} [options]
|
|
13
|
+
* @param {HTMLElement|Document} [options.root]
|
|
14
|
+
* @param {string} [options.selector]
|
|
15
|
+
*/
|
|
16
|
+
export function initMotion(options = {}) {
|
|
17
|
+
const root = options.root || (typeof document !== 'undefined' ? document : null);
|
|
18
|
+
if (!root) return () => {};
|
|
19
|
+
|
|
20
|
+
const selector =
|
|
21
|
+
options.selector ||
|
|
22
|
+
[
|
|
23
|
+
'[velin-reveal]',
|
|
24
|
+
'[velin-fade]',
|
|
25
|
+
'[velin-slide]',
|
|
26
|
+
'[velin-scale]',
|
|
27
|
+
'[velin-parallax]',
|
|
28
|
+
'[velin-hover]',
|
|
29
|
+
'.velin-animate-on-scroll',
|
|
30
|
+
].join(',');
|
|
31
|
+
|
|
32
|
+
const elements = root.querySelectorAll(selector);
|
|
33
|
+
const reduced = prefersReducedMotion();
|
|
34
|
+
|
|
35
|
+
for (const el of elements) {
|
|
36
|
+
if (el.dataset.velinMotionInit) continue;
|
|
37
|
+
el.dataset.velinMotionInit = '1';
|
|
38
|
+
|
|
39
|
+
const attrs = {};
|
|
40
|
+
for (const name of MOTION_ATTRS) {
|
|
41
|
+
if (el.hasAttribute(name)) attrs[name.replace('velin-', '')] = el.getAttribute(name) || 'true';
|
|
42
|
+
}
|
|
43
|
+
applyEffects(el, attrs);
|
|
44
|
+
|
|
45
|
+
if (reduced) {
|
|
46
|
+
markVisible(el);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const stop = observeInView(el, (node) => markVisible(node));
|
|
51
|
+
teardownFns.push(stop);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
enhanceStagger(root);
|
|
55
|
+
bindSmoothScroll(root);
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
teardownFns.forEach((fn) => fn());
|
|
59
|
+
teardownFns = [];
|
|
60
|
+
disconnectInViewObserver();
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** @deprecated use initMotion */
|
|
65
|
+
export function initReveal(options = {}) {
|
|
66
|
+
return initMotion({
|
|
67
|
+
root: typeof document !== 'undefined' ? document : null,
|
|
68
|
+
selector: options.selector || '.velin-animate-on-scroll',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const velinMotion = { init: initMotion, observe: observeInView, markVisible, VISIBLE_CLASS };
|
|
73
|
+
export { observeInView, disconnectInViewObserver } from './scheduler.js';
|
|
74
|
+
export { applyEffects, markVisible, prefersReducedMotion, VISIBLE_CLASS } from './effects.js';
|
|
75
|
+
export { enhanceStagger, applyStagger } from './stagger.js';
|
|
76
|
+
export { smoothScrollTo, bindSmoothScroll } from './scroll.js';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const pending = new Set();
|
|
2
|
+
let rafId = 0;
|
|
3
|
+
|
|
4
|
+
export function scheduleInView(callback) {
|
|
5
|
+
pending.add(callback);
|
|
6
|
+
if (!rafId) {
|
|
7
|
+
rafId = requestAnimationFrame(() => {
|
|
8
|
+
rafId = 0;
|
|
9
|
+
const batch = [...pending];
|
|
10
|
+
pending.clear();
|
|
11
|
+
batch.forEach((fn) => fn());
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let sharedObserver = null;
|
|
17
|
+
const observed = new WeakMap();
|
|
18
|
+
|
|
19
|
+
export function getInViewObserver(options = {}) {
|
|
20
|
+
if (typeof IntersectionObserver === 'undefined') return null;
|
|
21
|
+
if (!sharedObserver) {
|
|
22
|
+
sharedObserver = new IntersectionObserver(
|
|
23
|
+
(entries) => {
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
if (!entry.isIntersecting) continue;
|
|
26
|
+
const cb = observed.get(entry.target);
|
|
27
|
+
if (cb) scheduleInView(() => cb(entry.target));
|
|
28
|
+
const once = entry.target.dataset.velinOnce !== 'false';
|
|
29
|
+
if (once) sharedObserver.unobserve(entry.target);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
threshold: options.threshold ?? 0.1,
|
|
34
|
+
rootMargin: options.rootMargin ?? '0px 0px -40px 0px',
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return sharedObserver;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function observeInView(el, onVisible, opts = {}) {
|
|
42
|
+
const reduced =
|
|
43
|
+
typeof window !== 'undefined' &&
|
|
44
|
+
window.matchMedia &&
|
|
45
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
46
|
+
const motionOff = el.closest('[data-velin-motion="off"]') || el.dataset.velinMotion === 'off';
|
|
47
|
+
|
|
48
|
+
if (reduced || motionOff) {
|
|
49
|
+
scheduleInView(() => onVisible(el, true));
|
|
50
|
+
return () => {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const io = getInViewObserver(opts);
|
|
54
|
+
if (!io) {
|
|
55
|
+
scheduleInView(() => onVisible(el, true));
|
|
56
|
+
return () => {};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
observed.set(el, onVisible);
|
|
60
|
+
io.observe(el);
|
|
61
|
+
return () => {
|
|
62
|
+
observed.delete(el);
|
|
63
|
+
io.unobserve(el);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function disconnectInViewObserver() {
|
|
68
|
+
if (sharedObserver) {
|
|
69
|
+
sharedObserver.disconnect();
|
|
70
|
+
sharedObserver = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function smoothScrollTo(target, options = {}) {
|
|
2
|
+
const reduced =
|
|
3
|
+
typeof window !== 'undefined' &&
|
|
4
|
+
window.matchMedia &&
|
|
5
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
6
|
+
const el = typeof target === 'string' ? document.querySelector(target) : target;
|
|
7
|
+
if (!el) return;
|
|
8
|
+
el.scrollIntoView({
|
|
9
|
+
behavior: reduced || options.instant ? 'auto' : 'smooth',
|
|
10
|
+
block: options.block || 'start',
|
|
11
|
+
inline: options.inline || 'nearest',
|
|
12
|
+
});
|
|
13
|
+
if (el.tabIndex < 0 && !el.hasAttribute('tabindex')) {
|
|
14
|
+
el.setAttribute('tabindex', '-1');
|
|
15
|
+
}
|
|
16
|
+
el.focus({ preventScroll: true });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function bindSmoothScroll(root = document) {
|
|
20
|
+
root.querySelectorAll('[velin-scroll]').forEach((link) => {
|
|
21
|
+
if (link.dataset.velinScrollBound) return;
|
|
22
|
+
link.dataset.velinScrollBound = '1';
|
|
23
|
+
link.addEventListener('click', (e) => {
|
|
24
|
+
const href = link.getAttribute('href');
|
|
25
|
+
if (!href || !href.startsWith('#')) return;
|
|
26
|
+
const target = document.querySelector(href);
|
|
27
|
+
if (!target) return;
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
smoothScrollTo(target);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const STAGGER_ATTR = 'velin-stagger';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Apply stagger delay to direct children.
|
|
5
|
+
* @param {HTMLElement} listEl
|
|
6
|
+
* @param {string} [value] base delay ms or multiplier
|
|
7
|
+
*/
|
|
8
|
+
export function applyStagger(listEl, value = 'true') {
|
|
9
|
+
const base = value === 'true' || value === '' ? 60 : parseInt(value, 10) || 60;
|
|
10
|
+
const children = [...listEl.children];
|
|
11
|
+
children.forEach((child, i) => {
|
|
12
|
+
child.style.setProperty('--velin-stagger-delay', `${i * base}ms`);
|
|
13
|
+
child.classList.add('velin-stagger-item');
|
|
14
|
+
});
|
|
15
|
+
listEl.classList.add('velin-stagger');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function enhanceStagger(root = document) {
|
|
19
|
+
root.querySelectorAll(`[${STAGGER_ATTR}]`).forEach((el) => {
|
|
20
|
+
if (el.dataset.velinStaggerDone) return;
|
|
21
|
+
el.dataset.velinStaggerDone = '1';
|
|
22
|
+
applyStagger(el, el.getAttribute(STAGGER_ATTR) || 'true');
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const DOCS_MOUNT = '/docs/';
|
|
2
|
+
|
|
3
|
+
const DOC_ROOT_SEGMENTS = new Set([
|
|
4
|
+
'getting-started',
|
|
5
|
+
'extend',
|
|
6
|
+
'guides',
|
|
7
|
+
'utilities',
|
|
8
|
+
'components',
|
|
9
|
+
'forms',
|
|
10
|
+
'layout',
|
|
11
|
+
'content',
|
|
12
|
+
'customize',
|
|
13
|
+
'animations',
|
|
14
|
+
'about',
|
|
15
|
+
'helpers',
|
|
16
|
+
'generated',
|
|
17
|
+
'migration',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Docs root URL (…/docs/) — stable even when <base> mis-resolves the script under …/components/assets/.
|
|
22
|
+
*/
|
|
23
|
+
export function getDocsBaseUrl() {
|
|
24
|
+
if (typeof document === 'undefined') {
|
|
25
|
+
return 'https://example.invalid/docs/';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const script = document.querySelector('script[src*="doc-search"]');
|
|
29
|
+
if (script?.src) {
|
|
30
|
+
const u = new URL(script.src);
|
|
31
|
+
const i = u.pathname.indexOf(DOCS_MOUNT);
|
|
32
|
+
if (i !== -1) {
|
|
33
|
+
return u.origin + u.pathname.slice(0, i + DOCS_MOUNT.length);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const path = window.location.pathname.replace(/\\/g, '/');
|
|
38
|
+
const idx = path.indexOf(DOCS_MOUNT);
|
|
39
|
+
if (idx >= 0) {
|
|
40
|
+
return new URL(path.slice(0, idx + DOCS_MOUNT.length), window.location.origin).href;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return new URL('./', window.location.href).href;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function splitUrlHash(url) {
|
|
47
|
+
const i = url.indexOf('#');
|
|
48
|
+
if (i === -1) return { path: url, hash: '' };
|
|
49
|
+
return { path: url.slice(0, i), hash: url.slice(i) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Strip a mistaken current-section prefix (e.g. components/getting-started/…). */
|
|
53
|
+
export function relativizeDocsPathname(pathname) {
|
|
54
|
+
const docsIdx = pathname.indexOf(DOCS_MOUNT);
|
|
55
|
+
if (docsIdx < 0) return null;
|
|
56
|
+
const rest = pathname.slice(docsIdx + DOCS_MOUNT.length);
|
|
57
|
+
const parts = rest.split('/').filter(Boolean);
|
|
58
|
+
if (
|
|
59
|
+
parts.length >= 2 &&
|
|
60
|
+
DOC_ROOT_SEGMENTS.has(parts[0]) &&
|
|
61
|
+
DOC_ROOT_SEGMENTS.has(parts[1]) &&
|
|
62
|
+
parts[0] !== parts[1]
|
|
63
|
+
) {
|
|
64
|
+
return parts.slice(1).join('/');
|
|
65
|
+
}
|
|
66
|
+
return rest;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolve a search-index URL to a full navigable URL under the docs root.
|
|
71
|
+
* @param {string} url
|
|
72
|
+
*/
|
|
73
|
+
export function resolveDocsSearchUrl(url) {
|
|
74
|
+
if (!url || url === '#') return '';
|
|
75
|
+
if (typeof window === 'undefined') return url;
|
|
76
|
+
|
|
77
|
+
const { path, hash } = splitUrlHash(url);
|
|
78
|
+
if (!path) return hash || '';
|
|
79
|
+
|
|
80
|
+
let rel = path;
|
|
81
|
+
if (/^https?:\/\//i.test(path)) {
|
|
82
|
+
try {
|
|
83
|
+
const fixed = relativizeDocsPathname(new URL(path).pathname);
|
|
84
|
+
rel = fixed ?? path;
|
|
85
|
+
} catch {
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const target = new URL(rel || '', getDocsBaseUrl());
|
|
92
|
+
return target.href + (hash && !target.href.includes('#') ? hash : '');
|
|
93
|
+
} catch {
|
|
94
|
+
return '';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { CATEGORY_BOOST, normalizeEntry } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight fuzzy/substring search engine (no external deps).
|
|
5
|
+
*/
|
|
6
|
+
export class VelinSearchEngine {
|
|
7
|
+
constructor() {
|
|
8
|
+
/** @type {import('./types.js').SearchEntry[]} */
|
|
9
|
+
this._entries = [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** @param {import('./types.js').SearchEntry[]} entries */
|
|
13
|
+
setEntries(entries) {
|
|
14
|
+
this._entries = entries.map((e) => normalizeEntry(e)).filter(Boolean);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @param {import('./types.js').SearchEntry[]} entries */
|
|
18
|
+
addEntries(entries) {
|
|
19
|
+
const next = entries.map((e) => normalizeEntry(e)).filter(Boolean);
|
|
20
|
+
const ids = new Set(this._entries.map((e) => e.id));
|
|
21
|
+
for (const e of next) {
|
|
22
|
+
if (!ids.has(e.id)) {
|
|
23
|
+
this._entries.push(e);
|
|
24
|
+
ids.add(e.id);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} query
|
|
31
|
+
* @param {object} [opts]
|
|
32
|
+
* @param {number} [opts.limit]
|
|
33
|
+
* @param {number} [opts.minChars]
|
|
34
|
+
* @param {number} [opts.fuzzy] 0–1 threshold for typo tolerance
|
|
35
|
+
* @param {import('./types.js').SearchCategory[]} [opts.categories]
|
|
36
|
+
*/
|
|
37
|
+
query(query, opts = {}) {
|
|
38
|
+
const q = String(query || '').trim().toLowerCase();
|
|
39
|
+
const minChars = opts.minChars ?? 2;
|
|
40
|
+
const limit = opts.limit ?? 12;
|
|
41
|
+
const fuzzy = opts.fuzzy ?? 0.2;
|
|
42
|
+
const categories = opts.categories;
|
|
43
|
+
|
|
44
|
+
if (q.length < minChars) {
|
|
45
|
+
return { results: [], groups: {} };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const scored = [];
|
|
49
|
+
for (const entry of this._entries) {
|
|
50
|
+
if (categories && categories.length && !categories.includes(entry.category)) continue;
|
|
51
|
+
const s = scoreEntry(entry, q, fuzzy);
|
|
52
|
+
if (s > 0) scored.push({ entry, score: s });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
scored.sort((a, b) => b.score - a.score);
|
|
56
|
+
const results = scored.slice(0, limit).map((x) => ({ ...x.entry, _score: x.score }));
|
|
57
|
+
|
|
58
|
+
/** @type {Record<string, typeof results>} */
|
|
59
|
+
const groups = {};
|
|
60
|
+
for (const r of results) {
|
|
61
|
+
if (!groups[r.category]) groups[r.category] = [];
|
|
62
|
+
groups[r.category].push(r);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { results, groups };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {import('./types.js').SearchEntry} entry
|
|
71
|
+
* @param {string} q
|
|
72
|
+
* @param {number} fuzzy
|
|
73
|
+
*/
|
|
74
|
+
function scoreEntry(entry, q, fuzzy) {
|
|
75
|
+
const title = entry.title.toLowerCase();
|
|
76
|
+
const excerpt = (entry.excerpt || '').toLowerCase();
|
|
77
|
+
const keywords = (entry.keywords || []).join(' ').toLowerCase();
|
|
78
|
+
const boost = (CATEGORY_BOOST[entry.category] || 1) * (entry.weight || 1);
|
|
79
|
+
|
|
80
|
+
let score = 0;
|
|
81
|
+
|
|
82
|
+
if (title === q) score = 100;
|
|
83
|
+
else if (title.startsWith(q)) score = 70;
|
|
84
|
+
else if (title.includes(q)) score = 50;
|
|
85
|
+
else if (keywords.includes(q)) score = 35;
|
|
86
|
+
else if (excerpt.includes(q)) score = 25;
|
|
87
|
+
else if (fuzzy > 0 && fuzzyMatch(title, q, fuzzy)) score = 40;
|
|
88
|
+
else if (fuzzy > 0 && fuzzyMatch(keywords, q, fuzzy)) score = 28;
|
|
89
|
+
else if (fuzzy > 0 && fuzzyMatch(excerpt, q, fuzzy)) score = 18;
|
|
90
|
+
else if (subsequenceMatch(title, q)) score = 22;
|
|
91
|
+
else if (fuzzy > 0) {
|
|
92
|
+
for (const word of title.split(/[^a-z0-9]+/)) {
|
|
93
|
+
if (word.length >= 3 && fuzzyMatch(word, q, fuzzy)) {
|
|
94
|
+
score = 32;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return score > 0 ? score * boost : 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function subsequenceMatch(haystack, needle) {
|
|
104
|
+
let j = 0;
|
|
105
|
+
for (let i = 0; i < haystack.length && j < needle.length; i++) {
|
|
106
|
+
if (haystack[i] === needle[j]) j++;
|
|
107
|
+
}
|
|
108
|
+
return j === needle.length;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Allow ~1 edit per 4 chars when fuzzy threshold met */
|
|
112
|
+
function fuzzyMatch(text, query, threshold) {
|
|
113
|
+
if (!text || !query) return false;
|
|
114
|
+
if (text.includes(query)) return true;
|
|
115
|
+
if (subsequenceMatch(text, query)) return true;
|
|
116
|
+
const maxDist = Math.max(1, Math.floor(query.length * threshold * 2));
|
|
117
|
+
return levenshtein(text.slice(0, Math.min(text.length, query.length + 8)), query) <= maxDist;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function levenshtein(a, b) {
|
|
121
|
+
const m = a.length;
|
|
122
|
+
const n = b.length;
|
|
123
|
+
if (m === 0) return n;
|
|
124
|
+
if (n === 0) return m;
|
|
125
|
+
const dp = new Uint16Array((n + 1) * (m + 1));
|
|
126
|
+
for (let j = 0; j <= n; j++) dp[j] = j;
|
|
127
|
+
for (let i = 1; i <= m; i++) {
|
|
128
|
+
dp[i * (n + 1)] = i;
|
|
129
|
+
for (let j = 1; j <= n; j++) {
|
|
130
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
131
|
+
dp[i * (n + 1) + j] = Math.min(
|
|
132
|
+
dp[(i - 1) * (n + 1) + j] + 1,
|
|
133
|
+
dp[i * (n + 1) + (j - 1)] + 1,
|
|
134
|
+
dp[(i - 1) * (n + 1) + (j - 1)] + cost,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return dp[m * (n + 1) + n];
|
|
139
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape HTML and wrap query matches in <mark class="velin-search-hit">.
|
|
3
|
+
* @param {string} text
|
|
4
|
+
* @param {string} query
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function highlightHtml(text, query) {
|
|
8
|
+
const raw = String(text || '');
|
|
9
|
+
const q = String(query || '').trim();
|
|
10
|
+
if (!q || q.length < 2) return escapeHtml(raw);
|
|
11
|
+
const lower = raw.toLowerCase();
|
|
12
|
+
const ql = q.toLowerCase();
|
|
13
|
+
let start = lower.indexOf(ql);
|
|
14
|
+
let len = q.length;
|
|
15
|
+
if (start === -1) {
|
|
16
|
+
start = fuzzySubsequenceIndex(lower, ql);
|
|
17
|
+
if (start === -1) return escapeHtml(raw);
|
|
18
|
+
len = Math.min(q.length, raw.length - start);
|
|
19
|
+
}
|
|
20
|
+
return (
|
|
21
|
+
escapeHtml(raw.slice(0, start)) +
|
|
22
|
+
'<mark class="velin-search-hit">' +
|
|
23
|
+
escapeHtml(raw.slice(start, start + len)) +
|
|
24
|
+
'</mark>' +
|
|
25
|
+
escapeHtml(raw.slice(start + len))
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function escapeHtml(s) {
|
|
30
|
+
return s
|
|
31
|
+
.replace(/&/g, '&')
|
|
32
|
+
.replace(/</g, '<')
|
|
33
|
+
.replace(/>/g, '>')
|
|
34
|
+
.replace(/"/g, '"');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @returns {number} start index of subsequence or -1 */
|
|
38
|
+
function fuzzySubsequenceIndex(haystack, needle) {
|
|
39
|
+
let j = 0;
|
|
40
|
+
let start = -1;
|
|
41
|
+
for (let i = 0; i < haystack.length && j < needle.length; i++) {
|
|
42
|
+
if (haystack[i] === needle[j]) {
|
|
43
|
+
if (j === 0) start = i;
|
|
44
|
+
j++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return j === needle.length ? start : -1;
|
|
48
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { VelinSearchEngine } from './engine.js';
|
|
2
|
+
import { collectProviderEntries } from './providers.js';
|
|
3
|
+
import { normalizeEntry, SEARCH_CATEGORIES } from './types.js';
|
|
4
|
+
|
|
5
|
+
export { VelinSearchEngine } from './engine.js';
|
|
6
|
+
export { highlightHtml } from './highlight.js';
|
|
7
|
+
export { registerSearchProvider, unregisterSearchProvider, listSearchProviders } from './providers.js';
|
|
8
|
+
export { SEARCH_CATEGORIES, CATEGORY_BOOST, normalizeEntry } from './types.js';
|
|
9
|
+
export { getDocsBaseUrl, resolveDocsSearchUrl, relativizeDocsPathname } from './docs-url.js';
|
|
10
|
+
|
|
11
|
+
const defaultEngine = new VelinSearchEngine();
|
|
12
|
+
let indexLoaded = false;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {import('./types.js').SearchEntry[]|{ entries?: import('./types.js').SearchEntry[] }} data
|
|
16
|
+
*/
|
|
17
|
+
function parseIndexPayload(data) {
|
|
18
|
+
if (Array.isArray(data)) return data;
|
|
19
|
+
if (data && Array.isArray(data.entries)) return data.entries;
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Singleton search API.
|
|
25
|
+
*/
|
|
26
|
+
export const velinSearch = {
|
|
27
|
+
engine: defaultEngine,
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Load JSON index from URL (offline after fetch) or pass entries directly.
|
|
31
|
+
* @param {string|import('./types.js').SearchEntry[]} source
|
|
32
|
+
*/
|
|
33
|
+
async loadIndex(source) {
|
|
34
|
+
let entries = [];
|
|
35
|
+
if (typeof source === 'string') {
|
|
36
|
+
const res = await fetch(source);
|
|
37
|
+
if (!res.ok) throw new Error(`Failed to load search index: ${res.status}`);
|
|
38
|
+
entries = parseIndexPayload(await res.json());
|
|
39
|
+
} else if (Array.isArray(source)) {
|
|
40
|
+
entries = source;
|
|
41
|
+
}
|
|
42
|
+
defaultEngine.setEntries(entries);
|
|
43
|
+
const providerEntries = await collectProviderEntries();
|
|
44
|
+
if (providerEntries.length) defaultEngine.addEntries(providerEntries);
|
|
45
|
+
indexLoaded = true;
|
|
46
|
+
return entries.length;
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Merge entries without replacing the full index.
|
|
51
|
+
* @param {import('./types.js').SearchEntry[]} entries
|
|
52
|
+
*/
|
|
53
|
+
addEntries(entries) {
|
|
54
|
+
defaultEngine.addEntries(entries);
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Refresh entries from registered providers.
|
|
59
|
+
*/
|
|
60
|
+
async refreshProviders() {
|
|
61
|
+
const providerEntries = await collectProviderEntries();
|
|
62
|
+
if (providerEntries.length) defaultEngine.addEntries(providerEntries);
|
|
63
|
+
return providerEntries.length;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} query
|
|
68
|
+
* @param {object} [options]
|
|
69
|
+
*/
|
|
70
|
+
async query(query, options = {}) {
|
|
71
|
+
if (!indexLoaded && defaultEngine._entries.length === 0) {
|
|
72
|
+
await this.refreshProviders();
|
|
73
|
+
}
|
|
74
|
+
return defaultEngine.query(query, options);
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
isReady() {
|
|
78
|
+
return indexLoaded || defaultEngine._entries.length > 0;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create an isolated search instance (for multiple widgets).
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* @param {{ worker?: boolean, workerUrl?: string }} [config]
|
|
87
|
+
*/
|
|
88
|
+
export function createSearch(config = {}) {
|
|
89
|
+
const engine = new VelinSearchEngine();
|
|
90
|
+
const wantWorker = config.worker === true;
|
|
91
|
+
let workerClient = null;
|
|
92
|
+
|
|
93
|
+
async function getWorker() {
|
|
94
|
+
if (!wantWorker || workerClient) return workerClient;
|
|
95
|
+
if (typeof Worker === 'undefined') return null;
|
|
96
|
+
const { createSearchWorker } = await import('./worker-client.js');
|
|
97
|
+
workerClient = createSearchWorker(config.workerUrl);
|
|
98
|
+
return workerClient;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
engine,
|
|
103
|
+
async loadIndex(source) {
|
|
104
|
+
let entries = [];
|
|
105
|
+
if (typeof source === 'string') {
|
|
106
|
+
const res = await fetch(source);
|
|
107
|
+
entries = parseIndexPayload(await res.json());
|
|
108
|
+
} else if (Array.isArray(source)) {
|
|
109
|
+
entries = source;
|
|
110
|
+
}
|
|
111
|
+
engine.setEntries(entries);
|
|
112
|
+
const w = await getWorker();
|
|
113
|
+
if (w) await w.setEntries(engine._entries);
|
|
114
|
+
return entries.length;
|
|
115
|
+
},
|
|
116
|
+
async query(q, opts) {
|
|
117
|
+
const w = await getWorker();
|
|
118
|
+
if (w) return w.query(q, opts);
|
|
119
|
+
return engine.query(q, opts);
|
|
120
|
+
},
|
|
121
|
+
addEntries(entries) {
|
|
122
|
+
engine.addEntries(entries);
|
|
123
|
+
void getWorker().then((w) => w?.setEntries(engine._entries));
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|