diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000000..e94f8140cc --- /dev/null +++ b/.browserslistrc @@ -0,0 +1 @@ +defaults diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..af3a8b5b40 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,239 @@ +version: 2.1 + +aliases: + - &install_yarn_version + name: Install specific Yarn version + command: | + curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.22.5 + echo 'export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH"' >> $BASH_ENV + - &restore_yarn_cache + name: Restore Yarn cache + keys: + - yarn-{{ .Branch }}-packages-{{ checksum "yarn.lock" }} + - &save_yarn_cache + name: Save Yarn cache + key: yarn-{{ .Branch }}-packages-{{ checksum "yarn.lock" }} + paths: + - ~/.cache/yarn + - &run_yarn_install + name: Install dependencies + command: yarn install --frozen-lockfile + - &restore_dist_folders + name: Restore dist folders + command: | + set -exu + + mkdir -p packages/autocomplete-shared/dist + mkdir -p packages/autocomplete-core/dist + mkdir -p packages/autocomplete-js/dist + mkdir -p packages/autocomplete-preset-algolia/dist + mkdir -p packages/autocomplete-plugin-algolia-insights/dist + mkdir -p packages/autocomplete-plugin-recent-searches/dist + mkdir -p packages/autocomplete-plugin-redirect-url/dist + mkdir -p packages/autocomplete-plugin-query-suggestions/dist + mkdir -p packages/autocomplete-plugin-tags/dist + + cp -R /tmp/workspace/packages/autocomplete-shared/dist packages/autocomplete-shared + cp -R /tmp/workspace/packages/autocomplete-core/dist packages/autocomplete-core + cp -R /tmp/workspace/packages/autocomplete-js/dist packages/autocomplete-js + cp -R /tmp/workspace/packages/autocomplete-preset-algolia/dist packages/autocomplete-preset-algolia + cp -R /tmp/workspace/packages/autocomplete-plugin-algolia-insights/dist packages/autocomplete-plugin-algolia-insights + cp -R /tmp/workspace/packages/autocomplete-plugin-recent-searches/dist packages/autocomplete-plugin-recent-searches + cp -R /tmp/workspace/packages/autocomplete-plugin-redirect-url/dist packages/autocomplete-plugin-redirect-url + cp -R /tmp/workspace/packages/autocomplete-plugin-query-suggestions/dist packages/autocomplete-plugin-query-suggestions + cp -R /tmp/workspace/packages/autocomplete-plugin-tags/dist packages/autocomplete-plugin-tags + +defaults: &defaults + working_directory: ~/autocomplete + docker: + - image: cimg/node:22.14.0 + +references: + workspace_root: &workspace_root /tmp/workspace + attach_workspace: &attach_workspace + attach_workspace: + at: *workspace_root + +jobs: + build: + <<: *defaults + steps: + - checkout + - run: *install_yarn_version + - restore_cache: *restore_yarn_cache + - run: *run_yarn_install + - save_cache: *save_yarn_cache + - run: + name: Build + command: yarn run build + - run: + name: Move dist folders to workspace + command: | + set -exu + + mkdir -p /tmp/workspace/packages/autocomplete-shared/dist + mkdir -p /tmp/workspace/packages/autocomplete-core/dist + mkdir -p /tmp/workspace/packages/autocomplete-js/dist + mkdir -p /tmp/workspace/packages/autocomplete-preset-algolia/dist + mkdir -p /tmp/workspace/packages/autocomplete-plugin-algolia-insights/dist + mkdir -p /tmp/workspace/packages/autocomplete-plugin-recent-searches/dist + mkdir -p /tmp/workspace/packages/autocomplete-plugin-redirect-url/dist + mkdir -p /tmp/workspace/packages/autocomplete-plugin-query-suggestions/dist + mkdir -p /tmp/workspace/packages/autocomplete-plugin-tags/dist + + cp -R packages/autocomplete-shared/dist /tmp/workspace/packages/autocomplete-shared + cp -R packages/autocomplete-core/dist /tmp/workspace/packages/autocomplete-core + cp -R packages/autocomplete-js/dist /tmp/workspace/packages/autocomplete-js + cp -R packages/autocomplete-preset-algolia/dist /tmp/workspace/packages/autocomplete-preset-algolia + cp -R packages/autocomplete-plugin-algolia-insights/dist /tmp/workspace/packages/autocomplete-plugin-algolia-insights + cp -R packages/autocomplete-plugin-recent-searches/dist /tmp/workspace/packages/autocomplete-plugin-recent-searches + cp -R packages/autocomplete-plugin-redirect-url/dist /tmp/workspace/packages/autocomplete-plugin-redirect-url + cp -R packages/autocomplete-plugin-query-suggestions/dist /tmp/workspace/packages/autocomplete-plugin-query-suggestions + cp -R packages/autocomplete-plugin-tags/dist /tmp/workspace/packages/autocomplete-plugin-tags + - persist_to_workspace: + root: *workspace_root + paths: + - packages + test_lint: + <<: *defaults + steps: + - checkout + - *attach_workspace + - run: *install_yarn_version + - restore_cache: *restore_yarn_cache + - run: *run_yarn_install + - save_cache: *save_yarn_cache + - run: *restore_dist_folders + - run: + name: Linting + command: yarn run lint + test_lint_css: + <<: *defaults + steps: + - checkout + - run: *install_yarn_version + - restore_cache: *restore_yarn_cache + - run: *run_yarn_install + - save_cache: *save_yarn_cache + - run: + name: Linting CSS + command: yarn run lint:css + test_metadata: + <<: *defaults + steps: + - checkout + - *attach_workspace + - run: *install_yarn_version + - run: + name: Test package versions + command: yarn run test:versions + test_types: + <<: *defaults + steps: + - checkout + - run: *install_yarn_version + - restore_cache: *restore_yarn_cache + - run: *run_yarn_install + - save_cache: *save_yarn_cache + - run: + name: Type checking + command: yarn run test:types + test_unit: + <<: *defaults + steps: + - checkout + - *attach_workspace + - run: *install_yarn_version + - restore_cache: *restore_yarn_cache + - run: *run_yarn_install + - save_cache: *save_yarn_cache + - run: *restore_dist_folders + - run: + name: Unit tests + command: yarn run test --maxWorkers=4 + test_size: + <<: *defaults + steps: + - checkout + - *attach_workspace + - run: *install_yarn_version + - restore_cache: *restore_yarn_cache + - run: *run_yarn_install + - save_cache: *save_yarn_cache + - run: *restore_dist_folders + - run: + name: Test packages size + command: yarn run test:size + prepare_release: + <<: *defaults + steps: + - checkout + - *attach_workspace + - run: *install_yarn_version + - restore_cache: *restore_yarn_cache + - run: *run_yarn_install + - run: + name: Prepare a pull request for next release + command: | + git config --global user.email "autocomplete-bot@algolia.com" + git config --global user.name "Autocomplete[bot]" + yarn run shipjs prepare --yes --no-browse + release: + <<: *defaults + steps: + - checkout + - *attach_workspace + - run: *install_yarn_version + - restore_cache: *restore_yarn_cache + - run: *run_yarn_install + - save_cache: *save_yarn_cache + - run: *restore_dist_folders + - run: + name: Release if needed + command: | + git config --global user.email "autocomplete-bot@algolia.com" + git config --global user.name "Autocomplete[bot]" + yarn run shipjs trigger + +workflows: + version: 2 + ci: + when: + not: + equal: [scheduled_pipeline, << pipeline.trigger_source >>] + jobs: + - build + - test_metadata + - test_lint: + requires: + - build + - test_lint_css + - test_types + - test_unit: + requires: + - build + - test_size: + context: fx-libraries + requires: + - build + - release: + context: fx-libraries + requires: + - test_unit + - test_lint + - test_types + - test_size + - build + filters: + branches: + only: next + scheduled release: + # This workflow is triggered by a schedule pipeline. + # See: https://app.circleci.com/settings/project/github/algolia/autocomplete/triggers + when: + and: + - equal: [scheduled_pipeline, << pipeline.trigger_source >>] + - equal: [scheduled_release, << pipeline.schedule.name >>] + jobs: + - prepare_release: + context: fx-libraries diff --git a/.coveralls.yml b/.coveralls.yml deleted file mode 100644 index 91600595a1..0000000000 --- a/.coveralls.yml +++ /dev/null @@ -1 +0,0 @@ -service_name: travis-ci diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..40fee3a434 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000000..3b926cd620 --- /dev/null +++ b/.env.sample @@ -0,0 +1 @@ +GITHUB_TOKEN= diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..6746fb63f8 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,11 @@ +.cache +.parcel-cache +build +coverage +dist +node_modules +examples/twitter-compose-with-typeahead +examples/slack-with-emojis-and-commands +examples/react-instantsearch +examples/vue-instantsearch +examples/react diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 9d8f7522ce..0000000000 --- a/.eslintrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": [ - "eslint:recommended", - "airbnb/base", - "algolia/es5" - ], - "rules": { - "id-length": 0, - "no-cond-assign": 0, - "new-cap": 0 - } -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000..6de6662d5d --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,120 @@ +const OFF = 0; +// const WARNING = 1; +const ERROR = 2; + +module.exports = { + extends: ['algolia', 'algolia/jest', 'algolia/react', 'algolia/typescript'], + globals: { + __DEV__: false, + __TEST__: false, + }, + settings: { + react: { + pragma: 'React', + version: 'detect', + }, + 'import/resolver': { + node: { + extensions: ['.js', '.ts', '.tsx'], + }, + }, + }, + rules: { + curly: 2, + 'no-param-reassign': OFF, + 'valid-jsdoc': OFF, + 'no-shadow': OFF, + 'prefer-template': OFF, + 'jest/no-disabled-tests': OFF, + 'react/prop-types': OFF, + 'react/no-unescaped-entities': OFF, + 'new-cap': OFF, + 'eslint-comments/disable-enable-pair': [ERROR, { allowWholeFile: true }], + 'import/extensions': OFF, + '@typescript-eslint/camelcase': [ + ERROR, + { + allow: ['__autocomplete_', 'aa_core', 'aa_js'], + }, + ], + // Useful to call functions like `nodeItem?.scrollIntoView()`. + 'no-unused-expressions': OFF, + complexity: OFF, + 'import/order': [ + ERROR, + { + alphabetize: { + order: 'asc', + caseInsensitive: true, + }, + 'newlines-between': 'always', + groups: ['builtin', 'external', 'parent', 'sibling', 'index'], + pathGroups: [ + { + pattern: '@/**/*', + group: 'parent', + position: 'before', + }, + ], + pathGroupsExcludedImportTypes: ['builtin'], + }, + ], + }, + overrides: [ + { + files: ['test/**/*'], + rules: { + 'import/no-extraneous-dependencies': OFF, + }, + }, + { + files: ['packages/autocomplete-js/**/*/setProperties.ts'], + rules: { + 'eslint-comments/no-unlimited-disable': OFF, + }, + }, + { + files: [ + 'packages/autocomplete-core/**/*', + 'packages/autocomplete-js/**/*', + ], + rules: { + 'no-restricted-globals': [ + 'error', + { + name: 'window', + message: 'Use the `environment` param to access this property.', + }, + { + name: 'document', + message: 'Use the `environment` param to access this property.', + }, + ], + }, + }, + { + files: ['**/__tests__/**'], + rules: { + 'no-restricted-globals': OFF, + }, + }, + { + files: ['**/rollup.config.js', 'stories/**/*', '**/__tests__/**'], + rules: { + 'import/no-extraneous-dependencies': OFF, + }, + }, + { + files: ['scripts/**/*', '*.config.js'], + rules: { + 'import/no-commonjs': OFF, + }, + }, + { + files: ['examples/**/*'], + rules: { + 'spaced-comment': OFF, + }, + }, + ], +}; diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index af83dc703c..0000000000 --- a/.gitattributes +++ /dev/null @@ -1,6 +0,0 @@ -dist/autocomplete.js -diff -dist/autocomplete.min.js -diff -dist/autocomplete.jquery.js -diff -dist/autocomplete.jquery.min.js -diff -dist/autocomplete.angular.js -diff -dist/autocomplete.angular.min.js -diff diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index ef6bde4a16..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,16 +0,0 @@ - - -**Do you want to request a *feature* or report a *bug*?** - -**What is the current behavior?** - -**If the current behavior is a bug, please provide all the steps to reproduce and a minimal -[JSFiddle](https://jsfiddle.net/) example or a repository on GitHub that we can `npm install` -and `npm start`.** - -**What is the expected behavior?** diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 0000000000..7dacaf23f4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Help us improve Autocomplete. +--- + +## Description + + + +## Reproduction + + + +[**Preview →**](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/playground?file=/app.tsx) + +**Steps** + +1. Go to `...` +2. Click on `...` +3. Scroll down to `...` +4. See error + +## Expected behavior + + + +## Environment + +- OS: [e.g. Windows / Linux / macOS / iOS / Android] +- Browser: [e.g. Chrome, Safari] +- Autocomplete version: [e.g. 1.0.0] diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..470efbdfbd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Feature request + url: https://github.com/algolia/autocomplete/discussions/new + about: Request a feature to add to Autocomplete. + - name: Ask a question + url: https://github.com/algolia/autocomplete/discussions/new + about: Ask questions and discuss with other community members. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d62725d5a3..28cad2c8b8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,7 +8,7 @@ **Result** diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml new file mode 100644 index 0000000000..857c6dac52 --- /dev/null +++ b/.github/workflows/pkg-pr-new.yml @@ -0,0 +1,53 @@ +name: Preview Packages + +on: + pull_request: + push: + branches: + - next + +permissions: + contents: read + pull-requests: write + checks: write + +jobs: + preview: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Build packages + run: yarn build + + - name: Publish preview packages + run: > + npx pkg-pr-new publish + --compact + --template './examples/github-repositories-custom-plugin' + --template './examples/instantsearch' + --template './examples/playground' + --template './examples/preview-panel-in-modal' + --template './examples/react-renderer' + --template './examples/starter-algolia' + --template './examples/starter' + --template './examples/reshape' + --template './examples/vue' + './packages/autocomplete-core' + './packages/autocomplete-js' + './packages/autocomplete-plugin-algolia-insights' + './packages/autocomplete-plugin-query-suggestions' + './packages/autocomplete-plugin-recent-searches' + './packages/autocomplete-plugin-redirect-url' + './packages/autocomplete-plugin-tags' + './packages/autocomplete-preset-algolia' + './packages/autocomplete-shared' + './packages/autocomplete-theme-classic' diff --git a/.gitignore b/.gitignore index f7060b76ec..3534aed6bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,20 @@ -*.swp +# Generated files +node_modules/ .DS_Store - -.grunt -_SpecRunner.html +.eslintcache +*.log coverage/ +.cache +.parcel-cache + +# Bundle build files +dist/ -node_modules -npm-debug.log +# IDE +.vscode/ +.idea/ -bower_components +# Environment files +.env -*.iml -.idea -.vscode -netlify-dist/ +!examples/*/.env diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..7d41c735d7 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.14.0 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..5af8c6b083 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +.cache +.parcel-cache +build +coverage +dist +node_modules diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..6e93c0d52f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "proseWrap": "never", + "singleQuote": true, + "trailingComma": "es5" +} diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000000..8620b6e62c --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,33 @@ +{ + "plugins": [ + "stylelint-prettier", + "stylelint-no-unsupported-browser-features" + ], + "extends": [ + "stylelint-config-standard", + "stylelint-config-sass-guidelines", + "stylelint-order", + "stylelint-a11y/recommended", + "stylelint-prettier/recommended" + ], + "rules": { + "order/properties-alphabetical-order": true, + "no-descending-specificity": null, + "selector-class-pattern": [ + "^aa(-(?:[A-Z][a-z]+Plugin))?-(?:[A-Z][a-z]+)+(?:--[a-z]+(?:[A-Z][a-z]+)?)?$" + ], + "prettier/prettier": true, + "max-nesting-depth": null, + "rule-empty-line-before": [ + "always", + { "ignore": ["after-comment", "first-nested", "inside-block"] } + ], + "selector-max-compound-selectors": null, + "plugin/no-unsupported-browser-features": [ + null, + { + "severity": "warning" + } + ] + } +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 480229c17d..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,32 +0,0 @@ -language: node_js -branches: - only: - - master - - develop -env: - matrix: - - TEST_SUITE=unit - - TEST_SUITE=integration BROWSER='firefox' - - TEST_SUITE=integration BROWSER='safari:9' - - TEST_SUITE=integration BROWSER='safari:10' - - TEST_SUITE=integration BROWSER='safari:11' - - TEST_SUITE=integration BROWSER='internet explorer:9' - - TEST_SUITE=integration BROWSER='internet explorer:10' - - TEST_SUITE=integration BROWSER='internet explorer:11' - - TEST_SUITE=integration BROWSER='MicrosoftEdge' - - TEST_SUITE=integration BROWSER='chrome' - - TEST_SUITE=examples - global: - - secure: wuQpdVaYsj4JGZcYGPPMa6GQHA6RFCEDm1F9IOTOJLAPo9XS3+gcwU3uFqZONf9+f/l7LrF0tpsl1BTDiAQJkig1Z851mPNj0XaMWnBL+2cyfJPzAYBIj18A7xKnyvRWXKtTeDo5l6DKYiA86VQmZSQWulUVYzr7qeIWvONLqrqLpW2Gl910khy2sDO6LJSHqTvXcEwRtL6sIf0O93wxIwGuZM1EBAmZsorvZYSH3wILhT98Jj458OMxm8u2CgoFjjI62tNAksRGJ8E6iDNJOHyGPFxAQhGoqAQWBspHCQWt9aOFGNweig+ReulE9g0Oqfpy2YeE6XYb3rZyUjgDFSBWj4fnwUDqBInn30s4t4tmd0LLrc9VbMg/BkdoPlMMl+FAo/g/82tkAkIoFPfx/ZAlQ3zcVs3L3hPkh3597RWoXLdNIuRqquxaFGadumLRVUZElreMCEQUbk1mOEkbLf/tgW60ZUL9fJbl0qJK0uPXFX/sufxYVC1YfuRX3RpUL1bME8KyhW+zXTkz+x6AqosQYcsdXAzU4VWVJc5fyYjb8rSNMyWkmx+2T4w/DX6nF9ii6ybKm5sN3Z4LBB5ih73/MlKQLxqZTBKYJ5V8MdkTq2raW9nUB+WARd+UOBbmPnsYU3wqW+rXPgwiaVIj6ew6QsCgjQsLWswYwRb6oN8= - - secure: OluVlPpTgaAr1O/dzRvgBBk0M3w8uu1G3wRq9b3AdcldKuLf0kUoCrlh9cyBmowj+JgrfZ1DAyOFKwcyQAhb/HN+ha7BwRf4PNkb2/n5vlDeY7tE9ULuS9ROXIhrskVYUUh391ghQvbw45M7rrUyyJyo+B6Kv4RrwQObcpv9D/M81JRx4QfAAgrNNsCFTwjaf0DFMnmTXyXKIBusqa2DJxb7xslw1Kcu+qEYNWNB8wPoV9O3LOyfPIL5zt6j1HgUim998QBDNaI2rispH2lSPh6vKbomMlfDEDFCVHTmdKqbIb1TC/1/HlHiL5Kgesysev3QtfFjprTYwFZHXwElSrGYeFB+br11RQvOd8L7TCrGIdhGlBQLA6sISiohNrpZ6yGvsA+8JmMMsdOOO+wkHgQkWpzyFFdt+/Ek8+WeP04TsYAh1EPS3FGFeC/1ZqaazYITUaaNXaBqIp3Em07RB1eqyiu7KuO2wY74K3Y4fIAX3l6Od/E+DnUZhrUwwxZ+MnUx+SSZ4oGg4hL8OBrG0f/gaRwOWx/ieqyrcvzIpXYecKrxpYGzsM1NsbVzBiSsCSIt3lYbVxVVJiEjRLKb8L0QVvj4K4yfyQAAjWhcmz+MyCDLnFroaCmmohPF/gorsiaqEuiqTqXyFOAiMsEjZK513UMvPXK4lWWRt3a1/PI= - - secure: IDIoTyLjgu8yxGhvx2xCgdZWkLB900LnseELvetSm2RtbgQrpWd8yOh33JOK1ZQpiZcfJDWP6abuNmep7bR5p0KJLM8masMVHUiKbCJ1aF/79+poFObV+r4ibUjJn6bDKmn5ryaLeVTKHZMymUN2TUkkbF8OjKNU6LcDkoBRIm96YvAw4VRzFLS2HNlUOsD0eztQrxHi1YA8aEAHT0xcIe0o95qz9380UHKCKRMAkxav615R+3nYYPbTdYbk8/KHmaWWBLe1J8gN8fBFRQWsrWfcafhoVcNNePRKbppmBevxLOwe5qnK1OUlzSl1B0SriGtZdxfZTO3S3fJc90pHkPkHVOVcU7jjmte38uOQGbkgBA5j1c30dGcTocgHTGtQmFmXcn/t39hA6dGZk+Pu454Q5Owkg40cQQ6bMDgsO0VwbCE4RxCGE/2xcQeI+wE1wgSvGcFNprdUu7mLBWJePr+aBPUTQZWmGmwtkaSAZvpyq7ugZ4s/t1w53K6jG7MstYsjSINLo1qpB4IkMpganqAWuTzLwSCI5Nmx4EMX2kwt+IEJL3urJFjHjowqqeoFwqTGS4iegIGj0RMWI2XcMHPBThylJD0A5ig7jirS0IPktgF7q9wZg5flqRgugu2e2AywDNSfBNXE6iffl5dRimh4uIg/0URAQAk8TpKhlt4= - - secure: pkJy89qKiNosG74EODLK3LOtUOu/mFOMwlMQ65sEIpoJkXGnNiDFSTxkdV1uA5seAUg6HM+u2z94Axo3FbKkM4b8y85rKYi8YbP3w1UCnDjQzRSbqLq0eYaiDK5pTtilddurRzEF7pYLazRC2TYg76QNHzoSWDgE1ZOmLBQt8YOn7QMCTAH2H1upAmMcEiWCTkCru616/WD3WTPsxPQLB841LdsSa2UAJh3RerdOR4E4BGQ5XoNvSRhv/Tlufhobq3stGovYHAzim58ODZonQzgPDRN+FgnRyJRXBLN0KOjknIKeD8BUq/UV6m/kM7HS+eJgVVLgPvG2Cm+dZTYrjZl9A/dzNpvtl2fla0w9alE4wh3qMyooD8SCyPoP3ijkIYoqKM8dDncUD/Wlc/3N+wOn7tP1dPMdXxuWcPugL14Nto+ZftRZcGSlT50EvrELqblJ+k0H1/kN6bd45syKp6WTVyHrlSRr2aq4fs9IKNa+2ss1dM8iDLyfwOG24oZm3TaDl1RjbwuxiB1OvjfhNvxWYkAHnJmkfu2kvHen2L0WHT8xaVs0W9IqKPbO/pCGxYGb+z29G4no+FZ2WWnNaNWOV7Ur3eDwj/0B9JKyi0t/NBiXw7SQ3hQWR9S/xwhDyAin4StmDzRx4bAdnjV5ycIetGcP3qYDP5LuTkljNJU= - - secure: kn/miFmdfndFNHSeDdB/oAJKCTCqK9bk97dDymsG3SPgKmWaY2YU5wUvGaJKBrRSnzUlEbAbx5z7kvjZIWVD5JQ0xDqOIE9xi/Qe5r7heNZxaNhFCIRAK4KJIaRbT0xz8xWMzWkUmwbJTZjvUMLqV7CucTQRBeu72OjP/t6DzKe9HJWKk+tG/nrkV7NnquaPasHLO+U6Ef2SVBQfcPW5RX3wt03DN7BNVEZZu2SJIM4YaEauPp/Wy9Z3EHMDWZLq6XiI7FBH7oG4OVFpZN48TtUasqDQfdgev1lu6sfE+BADhxkwbeg4iswmsVEW7+3jN639NeRZ6gsH8LR+QIf38MB232sevOvUeqIwwyYl1yMrXIUPYbdY4ELY1H1m7qriq3vHL/0EhDJ/3POk4yfGxDt4JnS0cGCu8ygicSKn4fl3G9XY/tONuwwjGg0YGlbhgYD4Z9sgNfdhgp6DQ+a0zQIwLbGhYBT9CSzwsyHEVrPXkwLPG8tISRFIDkB6Oz1eFYnB1vPejbvOzGG8jDdseuU/Q4GV+Y5acpdqidJunyUcQKdeZAvOdsVFlefU6hTfA73dTpdtpFOwqbd0kBKr9TGbck919lJom3IkGTbi3O4VdQb29EU3M0uMaFHtrpHeaMQFECrOSGcdH/viWAuyPcdGv6Oq42+uq8tkjObjUOM= -node_js: stable -before_script: - - yarn build -script: - - yarn test:ci -cache: yarn -addons: - sauce_connect: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ec95dac71..4eb217c2f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,409 +1,1596 @@ - -# [0.37.0](https://github.com/algolia/autocomplete.js/compare/v0.36.0...v0.37.0) (2019-08-30) +## [1.19.7](https://github.com/algolia/autocomplete/compare/v1.19.6...v1.19.7) (2026-03-10) + + +### Bug Fixes + +* panel position in positioned container ([#763](https://github.com/algolia/autocomplete/issues/763)) ([#1336](https://github.com/algolia/autocomplete/issues/1336)) ([8d4b512](https://github.com/algolia/autocomplete/commit/8d4b5121021b62e91331a6f5cc013cc30f36f166)) + + + +## [1.19.6](https://github.com/algolia/autocomplete/compare/v1.19.5...v1.19.6) (2026-02-17) + + +### Bug Fixes + +* **core:** only use standard scrollIntoView function for auto-scroll ([#1333](https://github.com/algolia/autocomplete/issues/1333)) ([88dfc58](https://github.com/algolia/autocomplete/commit/88dfc580c66a93c0c3096d984b9d23ab9dceeb65)) + + + +## [1.19.5](https://github.com/algolia/autocomplete/compare/v1.19.4...v1.19.5) (2026-02-03) + + +### Bug Fixes + +* **autocomplete-preset-algolia:** support Unicode in ReverseHighlight ([#1330](https://github.com/algolia/autocomplete/issues/1330)) ([#1331](https://github.com/algolia/autocomplete/issues/1331)) ([3a3d96a](https://github.com/algolia/autocomplete/commit/3a3d96aa528fd12df7422863f1bc249589f919e4)), closes [#1317](https://github.com/algolia/autocomplete/issues/1317) + + + +## [1.19.4](https://github.com/algolia/autocomplete/compare/v1.19.3...v1.19.4) (2025-09-10) + + +### Bug Fixes + +* **perf:** pause loading icon animation on initial load ([#1325](https://github.com/algolia/autocomplete/issues/1325)) ([939db17](https://github.com/algolia/autocomplete/commit/939db17fbbc386b254664f62d9663ab8dfbeea4f)) + + + +## [1.19.3](https://github.com/algolia/autocomplete/compare/v1.19.2...v1.19.3) (2025-09-02) + + +### Bug Fixes + +* **perf:** pause animations when not displayed ([#1323](https://github.com/algolia/autocomplete/issues/1323)) ([075ab98](https://github.com/algolia/autocomplete/commit/075ab98e55ad0ad2aadf75f0d671083ee2449dd3)), closes [#1322](https://github.com/algolia/autocomplete/issues/1322) + + + +## [1.19.2](https://github.com/algolia/autocomplete/compare/v1.19.1...v1.19.2) (2025-05-20) + + +### Bug Fixes + +* **a11y:** add aria-label to div (algolia[#963](https://github.com/algolia/autocomplete/issues/963)) ([#1313](https://github.com/algolia/autocomplete/issues/1313)) ([aab3d6b](https://github.com/algolia/autocomplete/commit/aab3d6b06310fdc1202e9ca8dbb63a38ba2d2fad)) + + + +## [1.19.1](https://github.com/algolia/autocomplete/compare/v1.19.0...v1.19.1) (2025-04-29) + + +### Bug Fixes + +* **redirect:** race condition with mismatched query ([#1310](https://github.com/algolia/autocomplete/issues/1310)) ([531b078](https://github.com/algolia/autocomplete/commit/531b0783a2f29ceea7da670dc7b552dc90a53a81)) + + + +# [1.19.0](https://github.com/algolia/autocomplete/compare/v1.18.1...v1.19.0) (2025-04-22) + + +### Features + +* **redirect:** await submit for pending requests ([#1309](https://github.com/algolia/autocomplete/issues/1309)) ([bf03241](https://github.com/algolia/autocomplete/commit/bf032415ff5c87b6d728b6b017233f7d10052640)) + + + +## [1.18.1](https://github.com/algolia/autocomplete/compare/v1.18.0...v1.18.1) (2025-02-11) + + +### Bug Fixes + +* **autocomplete:** filter out empty collections in aria-controls ([#1302](https://github.com/algolia/autocomplete/issues/1302)) ([d4cca8c](https://github.com/algolia/autocomplete/commit/d4cca8c0bab8cb666bae380b7f100ac86e4cb124)) + + + +# [1.18.0](https://github.com/algolia/autocomplete/compare/v1.17.9...v1.18.0) (2025-01-28) + + +### Features + +* compatibility with React 19 types ([#1299](https://github.com/algolia/autocomplete/issues/1299)) ([77599c6](https://github.com/algolia/autocomplete/commit/77599c6ca0229d116ea59f36b99f25b058ac4f21)) + + + +## [1.17.9](https://github.com/algolia/autocomplete/compare/v1.17.8...v1.17.9) (2025-01-07) + + +### Bug Fixes + +* **tags:** use proper type on default template remove button ([#1293](https://github.com/algolia/autocomplete/issues/1293)) ([c52c5d5](https://github.com/algolia/autocomplete/commit/c52c5d5ba3087c2f7ec835942d62a60c7b041353)) + + + +## [1.17.8](https://github.com/algolia/autocomplete/compare/v1.17.7...v1.17.8) (2024-12-09) + + +### Bug Fixes + +* **insights-plugin:** prevent authenticated token being set as the userToken ([#1291](https://github.com/algolia/autocomplete/issues/1291)) ([08e9cd0](https://github.com/algolia/autocomplete/commit/08e9cd06e2669153cecbd0d800d5df1fbb431c53)) +* **plugins:** support translations in local storage recent searches plugins ([#1286](https://github.com/algolia/autocomplete/issues/1286)) ([893ad5f](https://github.com/algolia/autocomplete/commit/893ad5ffb9ca4b44b349cd9156d2775e5668d371)), closes [#1285](https://github.com/algolia/autocomplete/issues/1285) + + + +## [1.17.7](https://github.com/algolia/autocomplete/compare/v1.17.6...v1.17.7) (2024-11-05) + + +### Bug Fixes + +* **plugins:** add translations for query suggestions and recent searches plugins ([#1283](https://github.com/algolia/autocomplete/issues/1283)) ([572cd3c](https://github.com/algolia/autocomplete/commit/572cd3cb269b9288cc48df13d945a600fc7f83e5)), closes [#1282](https://github.com/algolia/autocomplete/issues/1282) + + + +## [1.17.6](https://github.com/algolia/autocomplete/compare/v1.17.5...v1.17.6) (2024-10-16) + + +### Bug Fixes + +* **shared:** import adequate FacetHit types for v4 and v5 search client ([#1280](https://github.com/algolia/autocomplete/issues/1280)) ([7d14539](https://github.com/algolia/autocomplete/commit/7d14539b6e7fc4c6c01d3d7e5046f1ce28c4e1ff)) + + + +## [1.17.5](https://github.com/algolia/autocomplete/compare/v1.17.4...v1.17.5) (2024-10-15) + + +### Bug Fixes + +* **core:** use updated aria control attribute for root element ([#1278](https://github.com/algolia/autocomplete/issues/1278)) ([f64497b](https://github.com/algolia/autocomplete/commit/f64497b2fab125910da6f71c4a50268bf0b70d27)) + + + +## [1.17.4](https://github.com/algolia/autocomplete/compare/v1.17.3...v1.17.4) (2024-07-11) + + +### Bug Fixes + +* **deps:** update search-insights and instantsearch dependencies ([#1268](https://github.com/algolia/autocomplete/issues/1268)) ([ebcc557](https://github.com/algolia/autocomplete/commit/ebcc557ac32c221e266030642768d91f26b11d86)) + + + +## [1.17.3](https://github.com/algolia/autocomplete/compare/v1.17.2...v1.17.3) (2024-07-09) + + +### Bug Fixes + +* **algoliasearch:** correctly retrieve headers for v5 ([#1263](https://github.com/algolia/autocomplete/issues/1263)) ([148b677](https://github.com/algolia/autocomplete/commit/148b677c69dca4c00d6399a4d2bff296fec82f42)) +* **core:** allow number index for hit attribute [#1261](https://github.com/algolia/autocomplete/issues/1261) (by @aldenquimby in [#1262](https://github.com/algolia/autocomplete/issues/1262)) ([0ae0c5c](https://github.com/algolia/autocomplete/commit/0ae0c5c7afd9b7a8aa20d17578e070513bfe696a)) + + + +## [1.17.2](https://github.com/algolia/autocomplete/compare/v1.17.1...v1.17.2) (2024-05-28) + + +### Bug Fixes + +* **js:** better accessibility for submit button label ([#1254](https://github.com/algolia/autocomplete/issues/1254)) ([e0304ae](https://github.com/algolia/autocomplete/commit/e0304ae671ea61821968867d50aab9e3e23c728b)) + + + +## [1.17.1](https://github.com/algolia/autocomplete/compare/v1.17.0...v1.17.1) (2024-05-14) + + +### Bug Fixes + +* **js:** allow body scroll when detached mode responsively disabled ([#1251](https://github.com/algolia/autocomplete/issues/1251)) ([710f86b](https://github.com/algolia/autocomplete/commit/710f86bcce3998cbcb0942ff9321e73428244589)), closes [#1250](https://github.com/algolia/autocomplete/issues/1250) + + + +# [1.17.0](https://github.com/algolia/autocomplete/compare/v1.16.0...v1.17.0) (2024-02-13) + + +### Features + +* **dom:** Add labels to buttons ([#1234](https://github.com/algolia/autocomplete/issues/1234)) ([12d1596](https://github.com/algolia/autocomplete/commit/12d15960e395e80f945284f9e7d41d6dc8dac5a5)) + + + +# [1.16.0](https://github.com/algolia/autocomplete/compare/v1.15.1...v1.16.0) (2024-02-06) + + +### Features + +* **recent-searches:** expose createLocalStorage ([#1240](https://github.com/algolia/autocomplete/issues/1240)) ([c813b3e](https://github.com/algolia/autocomplete/commit/c813b3ebe9640b36cd3fda5921d969dd115c0028)) + + + +## [1.15.1](https://github.com/algolia/autocomplete/compare/v1.15.0...v1.15.1) (2024-01-30) + + +### Bug Fixes + +* ignore composition events with option ([#1238](https://github.com/algolia/autocomplete/issues/1238)) ([fba16e5](https://github.com/algolia/autocomplete/commit/fba16e56bce6daa1982b947f1c74072a4b4800c2)) +* **insights:** guard against user token override while auth token is set ([#1237](https://github.com/algolia/autocomplete/issues/1237)) ([190e562](https://github.com/algolia/autocomplete/commit/190e562973aa25d7e37e6e508eb2b96e66e4ccba)) + + + +# [1.15.0](https://github.com/algolia/autocomplete/compare/v1.14.0...v1.15.0) (2024-01-17) + + +### Features + +* **insights:** support `authenticatedUserToken` ([#1233](https://github.com/algolia/autocomplete/issues/1233)) ([bd398ee](https://github.com/algolia/autocomplete/commit/bd398eea8755b88fc7e288acd10b616dd252cef5)) + + + +# [1.14.0](https://github.com/algolia/autocomplete/compare/v1.13.0...v1.14.0) (2024-01-16) + + +### Bug Fixes + +* focus detached input on iOS (#653) ([#1231](https://github.com/algolia/autocomplete/issues/1231)) ([3b569b6](https://github.com/algolia/autocomplete/commit/3b569b665454591ac818bb087a679acddc32d05b)) + + +### Features + +* **insights:** allow to pass init params ([#1230](https://github.com/algolia/autocomplete/issues/1230)) ([186ff9b](https://github.com/algolia/autocomplete/commit/186ff9b5e04b0f6db178a000c33452b5fdf5c7ec)) + + + +# [1.13.0](https://github.com/algolia/autocomplete/compare/v1.12.2...v1.13.0) (2023-12-26) + + +### Features + +* **insights:** update default version to support `authenticatedUserToken` ([#1225](https://github.com/algolia/autocomplete/issues/1225)) ([3e4c180](https://github.com/algolia/autocomplete/commit/3e4c1802a5d013a0d0aaa17e5c560d2a2bd31cd9)) + + + +## [1.12.2](https://github.com/algolia/autocomplete/compare/v1.12.1...v1.12.2) (2023-11-28) + + +### Bug Fixes + +* **insights:** do not throw if insightsClient is undefined in server environments ([#1220](https://github.com/algolia/autocomplete/issues/1220)) ([0692375](https://github.com/algolia/autocomplete/commit/069237576d3a1e8b61341c06640543b1aadf568a)) + + + +## [1.12.1](https://github.com/algolia/autocomplete/compare/v1.12.0...v1.12.1) (2023-10-31) + + +### Bug Fixes + +* **behaviour:** clear completion on "reset" ([#1215](https://github.com/algolia/autocomplete/issues/1215)) ([caa3ef8](https://github.com/algolia/autocomplete/commit/caa3ef8d2fa650f52997c7b661a74a9d5cbca9cd)) + + + +# [1.12.0](https://github.com/algolia/autocomplete/compare/v1.11.1...v1.12.0) (2023-10-24) + + +### Features + +* allow extra arguments in `sendEvent` signature ([#1210](https://github.com/algolia/autocomplete/issues/1210)) ([20d20a2](https://github.com/algolia/autocomplete/commit/20d20a296c87f8bc0aa6f776e5a7d384308a3312)) +* pass `insights` option default to `undefined` ([#1198](https://github.com/algolia/autocomplete/issues/1198)) ([2f5c683](https://github.com/algolia/autocomplete/commit/2f5c683cccca681345e855a2c28c3606919c73e4)) +* support Insights opt-in from the Dashboard ([#1205](https://github.com/algolia/autocomplete/issues/1205)) ([77c2aff](https://github.com/algolia/autocomplete/commit/77c2afff75f1603c5a58ebb1c46ca68d5e0a162b)) + + + +## [1.11.1](https://github.com/algolia/autocomplete/compare/v1.11.0...v1.11.1) (2023-10-02) + + +### Bug Fixes + +* **css:** hide empty panel layout in detached mode ([#1192](https://github.com/algolia/autocomplete/issues/1192)) ([973feaf](https://github.com/algolia/autocomplete/commit/973feafbcca1abb2ded4e3ec5a9b9c491f0a848e)) +* generate elements ids in a consistent manner ([#1194](https://github.com/algolia/autocomplete/issues/1194)) ([a76b914](https://github.com/algolia/autocomplete/commit/a76b914a3b7a28825a4af67edca51098698742f3)) +* **types:** correct type for SearchClient ([#1195](https://github.com/algolia/autocomplete/issues/1195)) ([a0228e4](https://github.com/algolia/autocomplete/commit/a0228e48d326b7cef2d3f70377f860426aea34dc)) + + + +# [1.11.0](https://github.com/algolia/autocomplete/compare/v1.10.0...v1.11.0) (2023-08-11) + + +### Features + +* **insights:** forward insights usertoken to algolia api calls ([#1179](https://github.com/algolia/autocomplete/issues/1179)) ([7a71eb4](https://github.com/algolia/autocomplete/commit/7a71eb41a71efa9aff17d38f34cada582917bb24)) + + + +# [1.10.0](https://github.com/algolia/autocomplete/compare/v1.9.4...v1.10.0) (2023-07-10) + + +### Features + +* **autocomplete-core:** add `enterKeyHint` option to manually set hint on virtual keyboards ([#1164](https://github.com/algolia/autocomplete/issues/1164)) ([e5aeea8](https://github.com/algolia/autocomplete/commit/e5aeea84e10f27a426d0a68dbc3c3cf3284e362d)) + + + +## [1.9.4](https://github.com/algolia/autocomplete/compare/v1.9.3...v1.9.4) (2023-06-20) + + +### Bug Fixes + +* **insights:** position gets computed per source ([#1159](https://github.com/algolia/autocomplete/issues/1159)) ([d053024](https://github.com/algolia/autocomplete/commit/d053024db03e956b15521e467f442d72cb5faa8a)) + + + +## [1.9.3](https://github.com/algolia/autocomplete/compare/v1.9.2...v1.9.3) (2023-06-07) + + +### Bug Fixes + +* **autocomplete-core:** don't update `enterKeyHint` on Samsung Browser ([#1153](https://github.com/algolia/autocomplete/issues/1153)) ([2971076](https://github.com/algolia/autocomplete/commit/2971076ea47a9c7d78ecb0ace0f1d16bb0155f98)) +* **fetchAlgoliaResults:** safely access searchClient credentials ([#1133](https://github.com/algolia/autocomplete/issues/1133)) ([f0f7a62](https://github.com/algolia/autocomplete/commit/f0f7a626f6b6caacc32051b8d3c198821d8c736a)) +* **insights:** retrieve index name from query if not returned by response ([#1138](https://github.com/algolia/autocomplete/issues/1138)) ([8406eb2](https://github.com/algolia/autocomplete/commit/8406eb234124651e81d09bf4985876c790215f2a)) + + + +## [1.9.2](https://github.com/algolia/autocomplete/compare/v1.9.1...v1.9.2) (2023-04-24) + +**Note:** Version bump only + + + +## [1.9.1](https://github.com/algolia/autocomplete/compare/v1.9.0...v1.9.1) (2023-04-24) + + +### Bug Fixes + +* **insights:** bump embedded search-insights version ([#1128](https://github.com/algolia/autocomplete/issues/1128)) ([984d9c5](https://github.com/algolia/autocomplete/commit/984d9c55de218da625d983e843e55d98f2c2c91a)) + + + +# [1.9.0](https://github.com/algolia/autocomplete/compare/v1.8.3...v1.9.0) (2023-04-24) + + +### Bug Fixes + +* **algolia:** throw error if searchClient is missing ([#1122](https://github.com/algolia/autocomplete/issues/1122)) ([8144cf3](https://github.com/algolia/autocomplete/commit/8144cf3bf37222e07f1e0b068596483e994d5ed8)) +* **autocomplete-js:** display warning when there are more than one instances of autocomplete ([#1108](https://github.com/algolia/autocomplete/issues/1108)) ([2926feb](https://github.com/algolia/autocomplete/commit/2926febdd787ed5fe47950cfe0ffb5b24225f883)) + + +### Features + +* **autocomplete-core:** add `insights` option to enable the Insights plugin ([#1121](https://github.com/algolia/autocomplete/issues/1121)) ([c9d06fd](https://github.com/algolia/autocomplete/commit/c9d06fd500531bcab67dbddee28221b1279ac285)) +* **createAlgoliaInsightsPlugin:** automatically load Insights when not passed ([#1106](https://github.com/algolia/autocomplete/issues/1106)) ([a02c2c1](https://github.com/algolia/autocomplete/commit/a02c2c1d272644ab539222e46bb98545f8d2c72b)) +* **createAlgoliaInsightsPlugin:** use `search-insights@2.6.0` ([#1109](https://github.com/algolia/autocomplete/issues/1109)) ([63dd995](https://github.com/algolia/autocomplete/commit/63dd99504760183759e75019d328b09edbd0cc10)) +* **insights:** annotate events with algoliaSource ([#1119](https://github.com/algolia/autocomplete/issues/1119)) ([43c5312](https://github.com/algolia/autocomplete/commit/43c5312a6b268e2efe7e4db317e73de8044f456f)) +* **insights:** re-export insights types from main packages ([#1124](https://github.com/algolia/autocomplete/issues/1124)) ([d61b385](https://github.com/algolia/autocomplete/commit/d61b385e69ade1930e773e7d2fef9579ebf5a875)) +* **insights:** set algolia credentials per event when supported ([#1120](https://github.com/algolia/autocomplete/issues/1120)) ([efb27ce](https://github.com/algolia/autocomplete/commit/efb27cee76ad3b8623f035ede38933312be1a926)) +* **preset-algolia:** attach algolia credentials on hits ([#1117](https://github.com/algolia/autocomplete/issues/1117)) ([8bcd680](https://github.com/algolia/autocomplete/commit/8bcd6806901fdb2a86382b59f3ee23ec3292ef63)) + + + +## [1.8.3](https://github.com/algolia/autocomplete/compare/v1.8.2...v1.8.3) (2023-03-02) + + +### Bug Fixes + +* **autocomplete-js:** `query` is reflected in the detached search `button` ([#1100](https://github.com/algolia/autocomplete/issues/1100)) ([a41ccc6](https://github.com/algolia/autocomplete/commit/a41ccc6fe6755f4b4cc7d6421ce830858a3f4616)) + + + +## [1.8.2](https://github.com/algolia/autocomplete/compare/v1.8.1...v1.8.2) (2023-02-21) + + +### Bug Fixes + +* **autocomplete-js:** correct peer dependency ([#1095](https://github.com/algolia/autocomplete/issues/1095)) ([c3824a9](https://github.com/algolia/autocomplete/commit/c3824a9e005a7cfbc8a8ea88816d4e9f79c7d4f0)), closes [#1094](https://github.com/algolia/autocomplete/issues/1094) +* duplicated IDs in panel ([#1078](https://github.com/algolia/autocomplete/issues/1078)) ([a732fc5](https://github.com/algolia/autocomplete/commit/a732fc5ae76ce7c8cbc5fd08aa33de5112d67d15)) + + + +## [1.8.1](https://github.com/algolia/autocomplete/compare/v1.8.0...v1.8.1) (2023-02-14) + + +### Bug Fixes + +* **insights:** split large view event payloads into multiple chunks ([#1087](https://github.com/algolia/autocomplete/issues/1087)) ([df58096](https://github.com/algolia/autocomplete/commit/df580968d1a479487905350c853ac89a0c86c4ff)) +* **querySuggestions:** allow categoryAttribute to be optional in hit ([#1086](https://github.com/algolia/autocomplete/issues/1086)) ([2dcbcd8](https://github.com/algolia/autocomplete/commit/2dcbcd8212c4a2852b0513767a0708c3da6e0092)) +* **redirect:** reopen menu when redirect detected ([#1091](https://github.com/algolia/autocomplete/issues/1091)) ([53b9ce5](https://github.com/algolia/autocomplete/commit/53b9ce5c88a16bd6b74dd991eeabb7917d79b542)) + + + +# [1.8.0](https://github.com/algolia/autocomplete/compare/v1.7.4...v1.8.0) (2023-02-09) + + +### Bug Fixes + +* **insights:** pass clickAnalytics automatically ([#1080](https://github.com/algolia/autocomplete/issues/1080)) ([8048442](https://github.com/algolia/autocomplete/commit/8048442b949b4230dea9aaafdfcd310a69cfa939)) + + +### Features + +* add redirect url plugin ([#1082](https://github.com/algolia/autocomplete/issues/1082)) ([a4f112d](https://github.com/algolia/autocomplete/commit/a4f112d274c433a96f68fbfc7dd219d0a994b6c5)) + + + +## [1.7.4](https://github.com/algolia/autocomplete/compare/v1.7.3...v1.7.4) (2022-12-20) + + +### Bug Fixes + +* **insights:** add Algolia agent on `subscribe` ([#1058](https://github.com/algolia/autocomplete/issues/1058)) ([60f8ae4](https://github.com/algolia/autocomplete/commit/60f8ae46ae230c40be832b52da3e44dcdd204c58)) + + +## [1.7.3](https://github.com/algolia/autocomplete.js/compare/v1.7.2...v1.7.3) (2022-11-02) + + +### Bug Fixes + +* **preset-algolia:** ensure "ts-ignore" is present in the output ([#1035](https://github.com/algolia/autocomplete.js/issues/1035)) ([c8a0f68](https://github.com/algolia/autocomplete.js/commit/c8a0f68e505c72a5088d1adca8e7ca0775f2a448)) + + + +## [1.7.2](https://github.com/algolia/autocomplete/compare/v1.7.1...v1.7.2) (2022-10-18) + + +### Bug Fixes + +* **algoliasearch:** support v5 via peerDependencies ([#1018](https://github.com/algolia/autocomplete/issues/1018)) ([5ba25f6](https://github.com/algolia/autocomplete/commit/5ba25f62213b2721218fb34ecc9472286cb9f926)) +* **preset-algolia:** support algoliasearch v5 ([#1002](https://github.com/algolia/autocomplete/issues/1002)) ([b1d93df](https://github.com/algolia/autocomplete/commit/b1d93dffad124e8bbef21b760d52e338c623cdfa)) + + + +## [1.7.1](https://github.com/algolia/autocomplete/compare/v1.7.0...v1.7.1) (2022-06-27) + + +### Bug Fixes + +* **metadata:** ensure safe user agent detection ([#993](https://github.com/algolia/autocomplete/issues/993)) ([fdf2b34](https://github.com/algolia/autocomplete/commit/fdf2b34673d4a9d7f56683eb3fa8e50d9fe5bc34)) + + + +# [1.7.0](https://github.com/algolia/autocomplete/compare/v1.6.3...v1.7.0) (2022-06-21) + + +### Bug Fixes + +* **autocomplete-js:** leave the modal open on reset on pointer devices ([#987](https://github.com/algolia/autocomplete/issues/987)) ([3e387e6](https://github.com/algolia/autocomplete/commit/3e387e6e7dea7de46acbaf4e220bbd1e568f4ea2)) + + + +## [1.6.3](https://github.com/algolia/autocomplete/compare/v1.6.2...v1.6.3) (2022-05-10) + + +### Bug Fixes + +* **react:** fix compatibility issues with React 18 ([#969](https://github.com/algolia/autocomplete/issues/969)) ([fb46298](https://github.com/algolia/autocomplete/commit/fb4629882a0b86468bae536fcdf4fc2159fcaa38)) + + + +## [1.6.2](https://github.com/algolia/autocomplete/compare/v1.6.1...v1.6.2) (2022-04-12) + + +### Bug Fixes + +* **autocomplete-js:** avoid warning when renderer is not specified at all ([#947](https://github.com/algolia/autocomplete/issues/947)) ([5fbae0d](https://github.com/algolia/autocomplete/commit/5fbae0d178e3a413df870630a017d530db30f1e7)) +* **autocomplete-js:** update components with new renderer ([#946](https://github.com/algolia/autocomplete/issues/946)) ([8fa038b](https://github.com/algolia/autocomplete/commit/8fa038b914a1b76270a106f5fe2b223aa657d6ae)) + + + +## [1.6.1](https://github.com/algolia/autocomplete/compare/v1.6.0...v1.6.1) (2022-04-08) + + +### Bug Fixes + +* **render:** pass `renderer.render` to default `render` function ([#940](https://github.com/algolia/autocomplete/issues/940)) ([55f53d1](https://github.com/algolia/autocomplete/commit/55f53d1c00bab3bbec8bc42f6ab12bbe8a407ff7)) + + + +# [1.6.0](https://github.com/algolia/autocomplete/compare/v1.5.7...v1.6.0) (2022-04-07) + + +### Features + +* **autocomplete-js:** enable HTML templating ([#920](https://github.com/algolia/autocomplete/issues/920)) ([f5bbf34](https://github.com/algolia/autocomplete/commit/f5bbf34f477a0d367d367f4f97db9768c4eb4781)) + + + +## [1.5.7](https://github.com/algolia/autocomplete/compare/v1.5.6...v1.5.7) (2022-04-05) + + +### Bug Fixes + +* **enterKeyHint:** remove check on only Chrome browser ([#933](https://github.com/algolia/autocomplete/issues/933)) ([93a1fc2](https://github.com/algolia/autocomplete/commit/93a1fc25c720eb3f4fb3900c8f71e0423bd9a0d5)) +* **plugin-insights:** allow search-insights v2 ([#930](https://github.com/algolia/autocomplete/issues/930)) ([c08189d](https://github.com/algolia/autocomplete/commit/c08189de96c35244617654815705ae008e0d1ec7)), closes [#929](https://github.com/algolia/autocomplete/issues/929) + + + +## [1.5.6](https://github.com/algolia/autocomplete/compare/v1.5.5...v1.5.6) (2022-03-31) + + +### Bug Fixes + +* **js:** stop touchstart event propagation if coming from cancel button in detached mode ([#924](https://github.com/algolia/autocomplete/issues/924)) ([24cf9d6](https://github.com/algolia/autocomplete/commit/24cf9d67c906378088bdf736bc0b70be49f270b4)) + + + +## [1.5.5](https://github.com/algolia/autocomplete/compare/v1.5.4...v1.5.5) (2022-03-30) + + +### Bug Fixes + +* **enterKeyHint:** use a fixed `enterKeyHint` value on Samsung devices ([#916](https://github.com/algolia/autocomplete/issues/916)) ([b4aa087](https://github.com/algolia/autocomplete/commit/b4aa08797236c0ff8cbdbbca88099c7be579711c)) + + + +## [1.5.4](https://github.com/algolia/autocomplete/compare/v1.5.3...v1.5.4) (2022-03-23) + + +### Bug Fixes + +* **js:** prevent event bubbling on cancel button click ([#922](https://github.com/algolia/autocomplete/issues/922)) ([ba17ccd](https://github.com/algolia/autocomplete/commit/ba17ccd578717c780d597733fa3d6dfd4b10dcf3)) + + + +## [1.5.3](https://github.com/algolia/autocomplete/compare/v1.5.2...v1.5.3) (2022-02-23) + + +### Bug Fixes + +* **umd:** batch requests when using plugins via UMD ([#902](https://github.com/algolia/autocomplete/issues/902)) ([1aa3dde](https://github.com/algolia/autocomplete/commit/1aa3ddee25b1df94de17b55c52e6fd06a7e1c5d3)) + + + +## [1.5.2](https://github.com/algolia/autocomplete/compare/v1.5.1...v1.5.2) (2022-01-26) + +### Bug Fixes + +* handle late resolving promises with promise cancelation ([#864](https://github.com/algolia/autocomplete/issues/864)) ([9640c2d](https://github.com/algolia/autocomplete/commit/9640c2d927301e88a4fa77b25d2dfeb7d25b8039)) + +# [1.5.1](https://github.com/algolia/autocomplete/compare/v1.5.0...v1.5.1) (2021-12-09) + +### Bug Fixes + +* **concurrency:** ensure panel stays closed after blur ([#829](https://github.com/algolia/autocomplete/issues/829)) ([2dd34e0](https://github.com/algolia/autocomplete/commit/2dd34e0ac1eae19d87105668bd13155b543ca336)) + +# [1.5.0](https://github.com/algolia/autocomplete/compare/v1.4.1...v1.5.0) (2021-11-02) + +### Bug Fixes + +- **getEnvironmentProps:** remove obsolete check causing tap not to close ([#803](https://github.com/algolia/autocomplete/issues/803)) ([51cfb94](https://github.com/algolia/autocomplete/commit/51cfb943d87a25eb863a48b9444637c49c22aa7c)) +- **js:** support updating Element options ([#777](https://github.com/algolia/autocomplete/issues/777)) ([fe684b3](https://github.com/algolia/autocomplete/commit/fe684b309dffd5b425db3430e5533a8eaac59d4b)) + +### Features + +- **core:** introduce metadata ([#774](https://github.com/algolia/autocomplete/issues/774)) ([79212d6](https://github.com/algolia/autocomplete/commit/79212d63c1b6062a22c771e71590709993e71a7a)) +- **plugins:** introduce plugin name ([#767](https://github.com/algolia/autocomplete/issues/767)) ([d50bd4b](https://github.com/algolia/autocomplete/commit/d50bd4b99b2521f0cba2102bd94c1c12d4693ced)) + +# [1.4.1](https://github.com/algolia/autocomplete/compare/v1.4.0...v1.4.1) (2021-10-11) + +### Bug Fixes + +- **concurrency:** ensure responses resolve in order ([#753](https://github.com/algolia/autocomplete/issues/753)) ([d15c404](https://github.com/algolia/autocomplete/commit/d15c404845a1446ad2cc8673c44be4dbfa68723f)) + +# [1.4.0](https://github.com/algolia/autocomplete/compare/v1.3.0...v1.4.0) (2021-09-13) + +### Features + +- **plugins:** introduce Tags plugin ([#644](https://github.com/algolia/autocomplete/issues/644)) ([d3cd9c3](https://github.com/algolia/autocomplete/commit/d3cd9c37ae4230227834ab4fabf7b00423b5cff2)) + +# [1.3.0](https://github.com/algolia/autocomplete/compare/v1.2.2...v1.3.0) (2021-08-26) + +### Bug Fixes + +- decycle potentially cyclic structures before serializing ([#634](https://github.com/algolia/autocomplete/issues/634)) ([99f7c84](https://github.com/algolia/autocomplete/commit/99f7c84695160e05c29dcf2f38ce6d916d5f21ee)) + +### Features + +- **core:** introduce Reshape API ([#647](https://github.com/algolia/autocomplete/issues/647)) ([d6180d2](https://github.com/algolia/autocomplete/commit/d6180d26921200378b9eaa55b26078d20c6ea480)) +- **theme:** provide non-minified theme ([#635](https://github.com/algolia/autocomplete/issues/635)) ([ca49d60](https://github.com/algolia/autocomplete/commit/ca49d60c1d29cf2673a35dc7bed290e0c5d2cdb6)) + +# [1.2.2](https://github.com/algolia/autocomplete/compare/v1.2.1...v1.2.2) (2021-07-19) + +### Bug Fixes + +- **js:** use user-provided "panelLayout" CSS class in `classNames` ([#628](https://github.com/algolia/autocomplete/issues/628)) ([c3aeb9f](https://github.com/algolia/autocomplete/commit/c3aeb9f817dda921f77a40a9b9c9010557cfd1c0)), closes [#627](https://github.com/algolia/autocomplete/issues/627) + +# [1.2.1](https://github.com/algolia/autocomplete/compare/v1.2.0...v1.2.1) (2021-07-08) + +### Bug Fixes + +- **completion:** prevent error when getting `activeItem` with an empty collection ([#623](https://github.com/algolia/autocomplete/issues/623)) ([0e0ce81](https://github.com/algolia/autocomplete/commit/0e0ce81405c4c71b31e5689820bc3909eaec5908)) + +# [1.2.0](https://github.com/algolia/autocomplete/compare/v1.1.0...v1.2.0) (2021-07-06) + +### Bug Fixes + +- **core:** open closed panel on `ArrowDown` and `ArrowUp` ([#599](https://github.com/algolia/autocomplete/issues/599)) ([37ebefe](https://github.com/algolia/autocomplete/commit/37ebefe637cd20c9e51c0242ef6126fd619cb53e)) +- **core:** trigger invariant when user doesn't return anything from `getItems` ([#607](https://github.com/algolia/autocomplete/issues/607)) ([e019b4d](https://github.com/algolia/autocomplete/commit/e019b4dd7968f23ba500235e866e74f05fbed9de)) +- **js:** provide fallback method for `addEventListener` on media queries ([#600](https://github.com/algolia/autocomplete/issues/600)) ([760f8e7](https://github.com/algolia/autocomplete/commit/760f8e79d71281c7176b7cd43917a77f89204b10)) + +### Features + +- **js:** provide setters and refresh to `render` API ([#598](https://github.com/algolia/autocomplete/issues/598)) ([3e78566](https://github.com/algolia/autocomplete/commit/3e785660d65d568e611542dec8de20eb87a001b0)) + +# [1.1.0](https://github.com/algolia/autocomplete/compare/v1.0.1...v1.1.0) (2021-05-27) + +### Bug Fixes + +- **css:** mark as side effects ([#587](https://github.com/algolia/autocomplete/issues/587)) ([67e7bbf](https://github.com/algolia/autocomplete/commit/67e7bbf3629936513852284e965979ecdc0f6404)), closes [#586](https://github.com/algolia/autocomplete/issues/586) +- **js:** do not render empty sections ([#594](https://github.com/algolia/autocomplete/issues/594)) ([527670e](https://github.com/algolia/autocomplete/commit/527670e6e71872e09fa98694f443b572847a89ae)) +- **js:** fix panel placement after scroll ([#593](https://github.com/algolia/autocomplete/issues/593)) ([ca396ad](https://github.com/algolia/autocomplete/commit/ca396adfbbca3db6d9c94e2076d3982925b57508)), closes [#591](https://github.com/algolia/autocomplete/issues/591) +- **js:** support panel scroll top position in all browsers ([#595](https://github.com/algolia/autocomplete/issues/595)) ([cce4b5f](https://github.com/algolia/autocomplete/commit/cce4b5f63f23410ffb3f14aa147f837835bbae4e)), closes [#591](https://github.com/algolia/autocomplete/issues/591) + +### Features + +- **js:** introduce Translations API ([#581](https://github.com/algolia/autocomplete/issues/581)) ([970ee6a](https://github.com/algolia/autocomplete/commit/970ee6acfcb0b7cbb699acbe274fec4e5e1c3a4e)) + +# [1.0.1](https://github.com/algolia/autocomplete/compare/v1.0.0...v1.0.1) (2021-05-07) + +### Bug Fixes + +- **js:** pass children as array in highlight components ([#575](https://github.com/algolia/autocomplete/issues/575)) ([7d3402e](https://github.com/algolia/autocomplete/commit/7d3402e0b157533aea74ad4b00115a1ae9ca09d1)), closes [#574](https://github.com/algolia/autocomplete/issues/574) +- **js:** rely on `environment` instead of global object ([#572](https://github.com/algolia/autocomplete/issues/572)) ([0a33b44](https://github.com/algolia/autocomplete/commit/0a33b442a48f5888412e2ae19326afd7f8ba3fb8)) +- **js:** render `noResults` template when `openOnFocus` is `true` ([#573](https://github.com/algolia/autocomplete/issues/573)) ([f2154c8](https://github.com/algolia/autocomplete/commit/f2154c80c7e54dec8107bc158c13ae21f01f8b5f)) + +# [1.0.0](https://github.com/algolia/autocomplete/compare/v1.0.0-alpha.49...v1.0.0) (2021-05-03) + +[![Banner](https://user-images.githubusercontent.com/6137112/116869321-ce911180-ac10-11eb-91fe-0965c9dbb52a.png)](http://alg.li/autocomplete) + +Read the [**Upgrading**](https://algolia.com/doc/ui-libraries/autocomplete/guides/upgrading/) guide to migrate from Autocomplete v0 to v1. + +# [1.0.0-alpha.49](https://github.com/algolia/autocomplete/compare/v1.0.0-alpha.48...v1.0.0-alpha.49) (2021-05-03) + +### Bug Fixes + +- **core:** support Insights in requesters ([#562](https://github.com/algolia/autocomplete/issues/562)) ([c305ab4](https://github.com/algolia/autocomplete/commit/c305ab4e974f18991cf4cbcf4189a4c81c47fa8a)) + +# [1.0.0-alpha.48](https://github.com/algolia/autocomplete/compare/v1.0.0-alpha.47...v1.0.0-alpha.48) (2021-04-30) + +### Features + +- **js:** sync detached mode open state ([#556](https://github.com/algolia/autocomplete/issues/556)) ([1239b63](https://github.com/algolia/autocomplete/commit/1239b63e80dd2351771d74e91b275eda19fb997f)) + +# [1.0.0-alpha.47](https://github.com/algolia/autocomplete/compare/v1.0.0-alpha.46...v1.0.0-alpha.47) (2021-04-29) + +### Bug Fixes + +- **theme-classic:** various fixes ([#546](https://github.com/algolia/autocomplete/issues/546)) ([6b4bc12](https://github.com/algolia/autocomplete/commit/6b4bc120f4eecfac3f7d2ab8b49067dacc833b55)) + +### Features + +- **js:** expose `GetSources` type ([#551](https://github.com/algolia/autocomplete/issues/551)) ([3d1bf26](https://github.com/algolia/autocomplete/commit/3d1bf269c02a7e35b100dda0b1d148805731a3ed)) +# [1.0.0-alpha.46](https://github.com/algolia/autocomplete/compare/v1.0.0-alpha.45...v1.0.0-alpha.46) (2021-04-22) + +This new version introduces the Requester API, which **transparently batches calls to the same Algolia application.** The `getAlgoliaHits` function is replaced by `getAlgoliaResults`, and `getAlgoliaFacetHits` by `getAlgoliaFacets`. Both functions no longer return a promise that resolves to the fetched records, but expose a `transformResponse` method that exposes the results, hits and facet hits for you to manipulate and return if need be. Learn more in the [documentation](https://autocomplete.algolia.com/docs/getAlgoliaResults). ### Bug Fixes -* **clear:** Avoid error when clear is called after destroy ([#287](https://github.com/algolia/autocomplete.js/issues/287)) ([244425d](https://github.com/algolia/autocomplete.js/commit/244425d)) +- **classic-theme:** fix modal display in Detached Mode ([#531](https://github.com/algolia/autocomplete/issues/531)) ([abf98ef](https://github.com/algolia/autocomplete/commit/abf98ef2c332c5f44988b521811e17a093adfd6a)) +- **classic-theme:** remove pointer cursor on no result item content ([#529](https://github.com/algolia/autocomplete/issues/529)) ([b241df4](https://github.com/algolia/autocomplete/commit/b241df426fce5e062139fcc48fbee26765aba0e3)) +- **examples:** update build command ([#539](https://github.com/algolia/autocomplete/issues/539)) ([f5254e9](https://github.com/algolia/autocomplete/commit/f5254e9a3e2c63962af87beea8e939319f12e619)) +- **js:** compute panel top position with `getBoundingClientRect` ([#536](https://github.com/algolia/autocomplete/issues/536)) ([492e058](https://github.com/algolia/autocomplete/commit/492e058c66f0c9972206ef4417ba7d0c8edf92a2)) +- **types:** fix collision between js/core and plugins ([#532](https://github.com/algolia/autocomplete/issues/532)) ([ac79f67](https://github.com/algolia/autocomplete/commit/ac79f6790f34cabe911492dbe24aad4633d9d949)) +- adjust examples ([#527](https://github.com/algolia/autocomplete/issues/527)) ([32bd2bc](https://github.com/algolia/autocomplete/commit/32bd2bc08c14c06faa3551f11253da6b14af5450)) +- **theme-classic:** wrap item content when there's no link ([#522](https://github.com/algolia/autocomplete/issues/522)) ([c6afe42](https://github.com/algolia/autocomplete/commit/c6afe4256047b234a5b4b0e45a1cfa61b1e82c6d)) +### Features +- **core:** introduce Requester API ([#540](https://github.com/algolia/autocomplete/issues/540)) ([be1cee7](https://github.com/algolia/autocomplete/commit/be1cee7003ef6e804a22a62415f0711ec9f583a4)) +- **qs:** append space to query on tap-ahead ([#525](https://github.com/algolia/autocomplete/issues/525)) ([06358bc](https://github.com/algolia/autocomplete/commit/06358bc8c295ffb7439d49c5959034dc5772f467)) - -# [0.36.0](https://github.com/algolia/autocomplete.js/compare/v0.35.0...v0.36.0) (2019-02-21) +# [1.0.0-alpha.45](https://github.com/algolia/autocomplete/compare/v1.0.0-alpha.44...v1.0.0-alpha.45) (2021-04-09) + +### Bug Fixes + +- **js:** change button class name to "aa-ClearButton" ([5991e77](https://github.com/algolia/autocomplete/commit/5991e77539f4cf2c7ab0a92c1ce626f3176348e7)) +- **js:** prevent id incrementation when toggling detached mode ([#489](https://github.com/algolia/autocomplete/issues/489)) ([fe2bf13](https://github.com/algolia/autocomplete/commit/fe2bf1326135ed6efa5b1434d7e23650a53442e5)) +- **js:** remove `window` references ([#501](https://github.com/algolia/autocomplete/issues/501)) ([7628d09](https://github.com/algolia/autocomplete/commit/7628d0944c3820d9227a1a4f4014efe78b7afeab)) +- **js:** run reactive values only once ([8356031](https://github.com/algolia/autocomplete/commit/83560313e0dfcc8c47e5078916f2f860ab347f5d)) +- **qs:** rename `categoriesLimit` to `itemsWithCategories` ([#491](https://github.com/algolia/autocomplete/issues/491)) ([4c97375](https://github.com/algolia/autocomplete/commit/4c9737563da9cb04b085bb1c2fb1c28e850d1b6d)) + +### Features + +- **autocomplete-theme-classic:** align search box properly ([#511](https://github.com/algolia/autocomplete/issues/511)) ([c807ed4](https://github.com/algolia/autocomplete/commit/c807ed46c29e594d2dff62f325545dc53bfda165)), closes [#513](https://github.com/algolia/autocomplete/issues/513) +- **js:** introduce Component API ([#505](https://github.com/algolia/autocomplete/issues/505)) ([74a908c](https://github.com/algolia/autocomplete/commit/74a908c9d2898e20da9451b4cf5f3575cd2f0151)) +- **js:** pass `elements` record to `render` ([#490](https://github.com/algolia/autocomplete/issues/490)) ([a50712e](https://github.com/algolia/autocomplete/commit/a50712e151266f13046d50ef7f8fccea27425bd4)) +- **plugins:** provide `state` to `transformSource` ([#516](https://github.com/algolia/autocomplete/issues/516)) ([eaa2026](https://github.com/algolia/autocomplete/commit/eaa2026dfe58603be48c5400fdadb6ec9ed539c4)) +- **recent-searches:** export storage and search APIs ([#473](https://github.com/algolia/autocomplete/issues/473)) ([09be485](https://github.com/algolia/autocomplete/commit/09be4855417840ae2428916d5291a49ab24e8532)) +- **theme:** patch theme ([#497](https://github.com/algolia/autocomplete/issues/497)) ([9bf41e2](https://github.com/algolia/autocomplete/commit/9bf41e2897624d7f69bb9dab4e7088f30247c73c)) +# [1.0.0-alpha.44](https://github.com/algolia/autocomplete/compare/v1.0.0-alpha.43...v1.0.0-alpha.44) (2021-03-01) ### Bug Fixes -* **standalone:** use aria label from input ([#276](https://github.com/algolia/autocomplete.js/issues/276)) ([4b94466](https://github.com/algolia/autocomplete.js/commit/4b94466)) +- **core:** compute open state on focus with `shouldPanelOpen` ([#456](https://github.com/algolia/autocomplete/issues/456)) ([dd28098](https://github.com/algolia/autocomplete/commit/dd28098accb75b5e76bc02df716a37b273b3e58a)) +- **js:** provide `title`s to submit and clear button ([45944e4](https://github.com/algolia/autocomplete/commit/45944e4c9f4e695039ab18e41284ff3d621774b7)), closes [algolia/algoliasearch-netlify#203](https://github.com/algolia/algoliasearch-netlify/issues/203) +- **js:** rename "Reset" button to "Clear" ([434c565](https://github.com/algolia/autocomplete/commit/434c56506f866c791553a79d2d3bbf325f8f11a6)) +- **theme:** keep item icon ratio ([b77921e](https://github.com/algolia/autocomplete/commit/b77921e857691dcda3d5d110a494425464899aab)) +### Features +- **js:** scroll to top when query changes ([#457](https://github.com/algolia/autocomplete/issues/457)) ([706939c](https://github.com/algolia/autocomplete/commit/706939c4f45e781b9cf9ae886521a7eb99f16755)) +- **qs:** rename `category` to `categoryAttribute` ([5d8c5d4](https://github.com/algolia/autocomplete/commit/5d8c5d4f5e2ce6d5621033fd04bd8431c0b7b915)) - -# [0.35.0](https://github.com/algolia/autocomplete.js/compare/v0.34.0...v0.35.0) (2018-12-17) +# [1.0.0-alpha.43](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.42...v1.0.0-alpha.43) (2021-02-19) + +### Bug Fixes + +- **js:** display `empty` template only with a query ([7c2f9a3](https://github.com/algolia/autocomplete.js/commit/7c2f9a35ae8935793f0e9838369225a4fedad37e)) +- **js:** rely on `environment` instead of `window` ([0bc15e9](https://github.com/algolia/autocomplete.js/commit/0bc15e9c354cacafe5f54fd91ece651dcc51cc58)) +- **theme:** update icons and Detached mode design ([#443](https://github.com/algolia/autocomplete.js/issues/443)) ([af43a37](https://github.com/algolia/autocomplete.js/commit/af43a379215376cab811adfd512bc7fb60965803)) +### Features + +- **algolia:** add `getAlgoliaFacetHits` preset ([#451](https://github.com/algolia/autocomplete.js/issues/451)) ([8876fd3](https://github.com/algolia/autocomplete.js/commit/8876fd3283e846be3bf9a6b8929efa5b09600d61)) +- **algolia:** fix highlighting hit type ([#452](https://github.com/algolia/autocomplete.js/issues/452)) ([0f92710](https://github.com/algolia/autocomplete.js/commit/0f927100e353adc0f6aac81bcad7c5fcc4c5862b)) +- **css:** support Detached mode ([#438](https://github.com/algolia/autocomplete.js/issues/438)) ([82747d5](https://github.com/algolia/autocomplete.js/commit/82747d58a9e53036f1cfb31efd79bb9a409d45f9)) +- **js:** add `aa-Detached` CSS class on Detached mode ([8a50e90](https://github.com/algolia/autocomplete.js/commit/8a50e90693ada6f876faf153e23fbc85300207a8)) +- **js:** always keep panel open on detached mode ([9014a41](https://github.com/algolia/autocomplete.js/commit/9014a4168c76dcc736c5895af614c970d4c0c2c0)) +- **js:** rename `detachedMediaQuery` ([46d30f5](https://github.com/algolia/autocomplete.js/commit/46d30f5178f7467b01a572cbea492ad63a5942ca)) +- **js:** rename `empty` to `noResults` ([#450](https://github.com/algolia/autocomplete.js/issues/450)) ([71ea2d0](https://github.com/algolia/autocomplete.js/commit/71ea2d010f7a370c03fbdcb01e155244f4ea8bb2)) +- **js:** rename classnames to Detached ([dadec26](https://github.com/algolia/autocomplete.js/commit/dadec26300b3f4bed3c8f704d2decf5211d6d3f0)) +- **plugins:** add categories to Query Suggestions and Recent Searches ([54ef36c](https://github.com/algolia/autocomplete.js/commit/54ef36c366e8328d99c6741cce839748f4914d89)) +- **recent-searches:** display tap-ahead button ([b3670c9](https://github.com/algolia/autocomplete.js/commit/b3670c95ebf5a8d6570e169351956360a18ac1f5)) +- **theme:** design search button focus state ([e284771](https://github.com/algolia/autocomplete.js/commit/e284771d6f2c5057a015912fbda0c50896a4c603)) +- **theme:** support modal design on Detached Mode ([#445](https://github.com/algolia/autocomplete.js/issues/445)) ([5043d27](https://github.com/algolia/autocomplete.js/commit/5043d27eb04ef550415d9235c60c40344abe8603)) +- **theme:** update search button design ([818a1d9](https://github.com/algolia/autocomplete.js/commit/818a1d9adc2df54ca289dd983bc8e77b5676029c)) + +# [1.0.0-alpha.42](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.41...v1.0.0-alpha.42) (2021-02-09) ### Bug Fixes -* **chrome-only:** Change autocomplete from 'nope' to 'off' ([#273](https://github.com/algolia/autocomplete.js/issues/273)) ([892a8f0](https://github.com/algolia/autocomplete.js/commit/892a8f0)) -* **utils:** correct _.every method ([#274](https://github.com/algolia/autocomplete.js/issues/274)) ([55af1e3](https://github.com/algolia/autocomplete.js/commit/55af1e3)) +- **core:** rename `shouldPanelShow` to `shouldPanelOpen` ([c442884](https://github.com/algolia/autocomplete.js/commit/c442884c99535939acef1309a6eff99d3358399b)) +- **js:** change highlighted element `key` to index ([d4d0348](https://github.com/algolia/autocomplete.js/commit/d4d03486fb72397644abd66e16dff879767f2a8d)) +### Features +- **core:** remove `onInput` option ([#437](https://github.com/algolia/autocomplete.js/issues/437)) ([3827605](https://github.com/algolia/autocomplete.js/commit/38276059f30c9e99a09be5764ba90cd79f9963da)) - -# [0.34.0](https://github.com/algolia/autocomplete.js/compare/v0.33.0...v0.34.0) (2018-12-04) +# [1.0.0-alpha.41](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.40...v1.0.0-alpha.41) (2021-02-09) + +### Bug Fixes + +- **core:** don't open panel with `openOnFocus` without items ([fde8b8a](https://github.com/algolia/autocomplete.js/commit/fde8b8abe7f459ac797e5a45b0b6e5f8b201fed0)) +- **core:** rename `OnHighlightParams` to `OnActiveParams` ([2d7762d](https://github.com/algolia/autocomplete.js/commit/2d7762df555782c0ff622e69faa86d5868e773a3)) +- **js:** add `footer` template ([b2223d5](https://github.com/algolia/autocomplete.js/commit/b2223d5a707846ac8efbe109d95cc97af0ad6b86)) +- **js:** add `key` props to highlight children ([11e5667](https://github.com/algolia/autocomplete.js/commit/11e566778bdc056f1ce048380ed16f04a75928d8)) +- **js:** forward `TData` type to `AutocompletePlugin` ([f62cb36](https://github.com/algolia/autocomplete.js/commit/f62cb36fcce6e2912269f9e456d0e68d73214a13)) +- **js:** use `AutocompletePlugin` type from JS ([ba3cda5](https://github.com/algolia/autocomplete.js/commit/ba3cda54b6d9088a938339f9d4d585915fe9a7bb)) +- **plugins:** pass `createElement` to highlight utils ([11f4cb7](https://github.com/algolia/autocomplete.js/commit/11f4cb7a5d47bce75ef830ff455bc9fe7f4b9f13)) +### Features + +- **emptyStates:** implements empty source template and renderEmpty method ([#395](https://github.com/algolia/autocomplete.js/issues/395)) ([fbfca35](https://github.com/algolia/autocomplete.js/commit/fbfca35e99002c27d3b79f79aa91a10fde2bdf27)) +- **js:** wrap item action buttons in container ([#434](https://github.com/algolia/autocomplete.js/issues/434)) ([0032f38](https://github.com/algolia/autocomplete.js/commit/0032f389eefdc752352d424ab427235608e50467)) +- **plugin:** add `transformSource` API ([fbd9e72](https://github.com/algolia/autocomplete.js/commit/fbd9e72ee20b225e574aa32e16e24e0252339f41)) +- **sourceId:** add `sourceId` to provide `data-autocomplete-source-id` on `section` source container ([#429](https://github.com/algolia/autocomplete.js/issues/429)) ([ce35fea](https://github.com/algolia/autocomplete.js/commit/ce35fea95a3d408064eba698d47ac0c57bd58349)) +- **theme:** add `aa-ItemWrapper` CSS class ([a56c9d4](https://github.com/algolia/autocomplete.js/commit/a56c9d4da87ca2b8b32c373da99f6f9fee0170fe)) + +# [1.0.0-alpha.40](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.39...v1.0.0-alpha.40) (2021-02-03) + +### Bug Fixes + +- **core:** forward props in `getEnvironmentProps` ([af49483](https://github.com/algolia/autocomplete.js/commit/af4948304f5b95fdc8c642882609b87d39562f83)) +- **core:** remove error handler when fetching results ([#416](https://github.com/algolia/autocomplete.js/issues/416)) ([eb98af6](https://github.com/algolia/autocomplete.js/commit/eb98af611ecd91b07d161d68ce1c81abf84bbf70)) +- **insights:** rename event to "Item Active" ([03059b6](https://github.com/algolia/autocomplete.js/commit/03059b6b2942f114ea48a304d964480cb43b02c5)) +- **js:** ignore empty template with no query and `openOnFocus` ([#407](https://github.com/algolia/autocomplete.js/issues/407)) ([92eeb3e](https://github.com/algolia/autocomplete.js/commit/92eeb3e523e4c1b1c87c4108d903029052472387)) +- **js:** update panel markup ([1eecc65](https://github.com/algolia/autocomplete.js/commit/1eecc65fa86cb490dc2987dedaedbbc7812fac10)) +- **mergeClassNames:** prevent classes with the same name being merged ([#413](https://github.com/algolia/autocomplete.js/issues/413)) ([9651481](https://github.com/algolia/autocomplete.js/commit/9651481a9e903605bbb26c28b1426fa7088416e5)) ### Features -* change autocomplete from 'off' to 'nope' ([#250](https://github.com/algolia/autocomplete.js/issues/250)) ([fbbed04](https://github.com/algolia/autocomplete.js/commit/fbbed04)) +- **core:** pass scoped API to lifecycle hooks ([#422](https://github.com/algolia/autocomplete.js/issues/422)) ([049b343](https://github.com/algolia/autocomplete.js/commit/049b3430f9bd6fe53536c346f287dab06652b7cf)) +- **highlighting:** support array syntax for nested attributes ([#418](https://github.com/algolia/autocomplete.js/issues/418)) ([4ad4e41](https://github.com/algolia/autocomplete.js/commit/4ad4e411d5c5ff6f6a8374b567fb9afa725bfd30)) +- **js:** add JS user agent to Algolia requests ([#420](https://github.com/algolia/autocomplete.js/issues/420)) ([fab2d57](https://github.com/algolia/autocomplete.js/commit/fab2d57c3d0c08ab2e46fad80224fe9f740988eb)) +- **js:** update reset icon ([2c953f6](https://github.com/algolia/autocomplete.js/commit/2c953f6ae8473b2fc5bd63b01b32470024e125bc)) +- **query-suggestions:** remove `getAlgoliaHits` param ([efa4c93](https://github.com/algolia/autocomplete.js/commit/efa4c938f8a1c885a38e8130be44c9ab17fba146)) +- **recent-searches:** update remove icon ([b828171](https://github.com/algolia/autocomplete.js/commit/b82817171718eab9041f41146d8accbd7d0cf909)) +- **theme:** introduce Autocomplete Classic Theme ([#409](https://github.com/algolia/autocomplete.js/issues/409)) ([226fc54](https://github.com/algolia/autocomplete.js/commit/226fc549e07e957b7b6177e0f3d592bc509b0089)) +# [1.0.0-alpha.39](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.38...v1.0.0-alpha.39) (2021-01-22) +This is a big release that changes the rendering implementation from plain HTML to Virtual DOM (see [#381](https://github.com/algolia/autocomplete.js/issues/381)). - -# [0.33.0](https://github.com/algolia/autocomplete.js/compare/v0.32.0...v0.33.0) (2018-11-19) +### Bug Fixes + +- **recent-searches:** escape highlighted query regex ([#387](https://github.com/algolia/autocomplete.js/issues/387)) ([d23f133](https://github.com/algolia/autocomplete.js/commit/d23f1336624680b72068fa93131448d251346a56)) + +### Features +- **core:** rename highlight to active ([1c1b951](https://github.com/algolia/autocomplete.js/commit/1c1b9512cd834c2bd2c48525df535d7039c14058)) +- **emptyStates:** add `empty` source template and `renderEmpty` method ([#395](https://github.com/algolia/autocomplete.js/issues/395)) ([8bd35e6](https://github.com/algolia/autocomplete.js/commit/8bd35e6c186f1a4398108abd76dbd006c2b734b2)) +- **js:** ([#381](https://github.com/algolia/autocomplete.js/issues/381)) ([5a1efc2](https://github.com/algolia/autocomplete.js/commit/5a1efc2cccd968b9f359ebd9ea26812743122d4c)) +- **reverseHighlight/reverseSnippet:** implement sibling strategy from InstantSearch.js ([#388](https://github.com/algolia/autocomplete.js/issues/388)) ([d86a33a](https://github.com/algolia/autocomplete.js/commit/d86a33a50adefa3b6996e80e1d3b407f0dab59be)) + +# [1.0.0-alpha.38](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.37...v1.0.0-alpha.38) (2020-12-12) ### Bug Fixes -* **release:** Update mversion to 1.12 ([#268](https://github.com/algolia/autocomplete.js/issues/268)) ([08b8e30](https://github.com/algolia/autocomplete.js/commit/08b8e30)) +- **core:** convert `AutocompleteContext` to interface ([0fbfe59](https://github.com/algolia/autocomplete.js/commit/0fbfe59a1897f64e6b95a45269ff710f72183925)) +- **js:** vertically offset panel based on `offsetTop` ([bb1af17](https://github.com/algolia/autocomplete.js/commit/bb1af17f92ffe97c9002fdddc8733356161fdf4e)) + +### Features + +- **core:** add `enterKeyHint` prop to input ([7ff2971](https://github.com/algolia/autocomplete.js/commit/7ff29719b48e08863f5d4eb62021b12e4e1472c0)) +- **core:** add invariant for `getItems` ([b57ccf3](https://github.com/algolia/autocomplete.js/commit/b57ccf3f915f5316c67af682427f7e6d8b07dc04)) +- **core:** add invariant for `getSources` ([309371c](https://github.com/algolia/autocomplete.js/commit/309371c5ec06b5c50fb3aef08de8f0b25b34d216)) +- **core:** add invariant for unknown reducer actions ([27d6281](https://github.com/algolia/autocomplete.js/commit/27d628194724bf04c381490c13d910fd368cd582)) +- **insights:** extends `AutocompleteContext` with Insights API ([f1e8de4](https://github.com/algolia/autocomplete.js/commit/f1e8de43fcbad20c48997a3d8dd17e11f6339fe7)) +- **js:** add non-input container invariant ([2e3a8ed](https://github.com/algolia/autocomplete.js/commit/2e3a8ed12a0167c78400ec0ae9c0c92041229a99)) +- **js:** introduce Autocomplete Touch ([#379](https://github.com/algolia/autocomplete.js/issues/379)) ([5cfbdf2](https://github.com/algolia/autocomplete.js/commit/5cfbdf266818fa6a2a026ec7ce62b0e37d2a1f8b)) +- **js:** introduce Update API ([921788c](https://github.com/algolia/autocomplete.js/commit/921788ce1067da9e8d42fd5dd2c688db659b9c88)) +- **js:** pass scope API to prop getters ([18c7474](https://github.com/algolia/autocomplete.js/commit/18c7474b74b4e023dd6bcaa6c05040b5680ba926)) +- **js:** schedule renders ([ef54af3](https://github.com/algolia/autocomplete.js/commit/ef54af3b85d13af511b7ae7edef5fbae00dd61e6)) +- **query-suggestions:** pass `state` to `getSearchParams` ([5b8de7f](https://github.com/algolia/autocomplete.js/commit/5b8de7f48165f4ff60a9250f9245ef7771e5eb16)) +- **shared:** add invariant util ([0e28f55](https://github.com/algolia/autocomplete.js/commit/0e28f550ca918d15023fe641ef2e893ce779cc95)) + +# [1.0.0-alpha.37](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.36...v1.0.0-alpha.37) (2020-12-06) +### Features + +- **core:** export `AutocompleteContext` type ([f6ce779](https://github.com/algolia/autocomplete.js/commit/f6ce779575aadcce4d7c711f950ebff7ae25dcc5)) +- **core:** pass `refresh` to all events ([bd45a77](https://github.com/algolia/autocomplete.js/commit/bd45a7793143425ba84f6f0bd2b66811b525f984)) +- **core:** support `onReset` prop ([b7a66a8](https://github.com/algolia/autocomplete.js/commit/b7a66a85216c8a54330c087127da1ae45a59c1e6)) +- **js:** introduce Props API ([04bef73](https://github.com/algolia/autocomplete.js/commit/04bef73d7b8b23fa2d0d1a0256dc794dab5a8422)) + +# [1.0.0-alpha.36](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.35...v1.0.0-alpha.36) (2020-12-03) + +### Bug Fixes + +- **core:** allow null `inputElement` in `getInputProps` ([75b990a](https://github.com/algolia/autocomplete.js/commit/75b990a866399af4b1f51aa45cd44206ce03a99d)) +- **core:** disable `getSources` concurrent fix ([a558b5e](https://github.com/algolia/autocomplete.js/commit/a558b5e238a5f1897bf61e1b1ae6e5742685e4ea)) +- **core:** extend `TItem` to object ([fcf94ff](https://github.com/algolia/autocomplete.js/commit/fcf94fff4788f3f132095a2a6d359b51824a4d51)) +- **examples:** update ref types ([1d0cb20](https://github.com/algolia/autocomplete.js/commit/1d0cb208a6b3ce3da24bbfab3557831481e96032)) +- **js:** add `templates` type to `getSources` ([69b9718](https://github.com/algolia/autocomplete.js/commit/69b97188dc570ef1082287576eae01ef4c505947)) +- **js:** calculate panel position before opening ([#375](https://github.com/algolia/autocomplete.js/issues/375)) ([307a7ac](https://github.com/algolia/autocomplete.js/commit/307a7acc4283e10a19cb7d067f04f1bea79dc56f)) +- **js:** clean environment effects ([eec80d2](https://github.com/algolia/autocomplete.js/commit/eec80d2cd590234dc653517adc78b2c6c6423716)) +- **js:** fix debounce function ([82e6f4e](https://github.com/algolia/autocomplete.js/commit/82e6f4e0c017dbab675b53a107a8842f529a73fc)) +- **js:** fix internal state type ([6def041](https://github.com/algolia/autocomplete.js/commit/6def041fdb0487184d571639a912e70d61a9576d)) +- **js:** make `panelContainer` optional ([fe5db0c](https://github.com/algolia/autocomplete.js/commit/fe5db0c16aae404af6f1a8e34ad4fa8c259a676d)) +- **js:** pass submit button to render ([ca119f3](https://github.com/algolia/autocomplete.js/commit/ca119f33f1ebb00b22bfb21423ffbaa8ac576e81)) +- update icons `stroke-width` ([23e321b](https://github.com/algolia/autocomplete.js/commit/23e321ba22e535a184d8a000f8ee16b887b1ac2d)) ### Features -* **selected:** Adding context.selectionMethod to selected event ([#267](https://github.com/algolia/autocomplete.js/issues/267)) ([36028a6](https://github.com/algolia/autocomplete.js/commit/36028a6)) +- **core:** rename `searchBoxElement` to `formElement` ([#374](https://github.com/algolia/autocomplete.js/issues/374)) ([79c4985](https://github.com/algolia/autocomplete.js/commit/79c49854b0ff18a2c28b47e0173e819af7a6112c)) +- **js:** add loading indicator ([59dc31b](https://github.com/algolia/autocomplete.js/commit/59dc31bec1b4ba0f199359bfaddb2d37074ae8c8)) +- **js:** add PanelLayout element ([371fae0](https://github.com/algolia/autocomplete.js/commit/371fae04c43c1d7bfec2b125873a8ee4bb075aad)) +- **js:** batch DOM updates ([#372](https://github.com/algolia/autocomplete.js/issues/372)) ([d06873e](https://github.com/algolia/autocomplete.js/commit/d06873e1fff737f853ac813df21d2b7b365c5446)) +- **js:** display reset button only when query ([1656530](https://github.com/algolia/autocomplete.js/commit/16565304a50724d898e68686ef5301797cfac7ad)) +- **js:** don't append panel on initial loading ([84ce729](https://github.com/algolia/autocomplete.js/commit/84ce729d24b7cf027daa0f61e7816ba5892cf6c5)) +- **js:** introduce `panelContainer` option ([98dfe4b](https://github.com/algolia/autocomplete.js/commit/98dfe4b95b9085bb2a9e51d03d466582aeffee55)) +- **js:** pass source to header and footer templates ([9983c64](https://github.com/algolia/autocomplete.js/commit/9983c64477408b52b9449cdb46faa2dc3e24ef0d)) +- **js:** turn search label into submit button ([27e61cb](https://github.com/algolia/autocomplete.js/commit/27e61cbc4e6f3b039764f665225bd385c82941c4)) +- **plugins:** introduce Insights plugin ([#373](https://github.com/algolia/autocomplete.js/issues/373)) ([2e967be](https://github.com/algolia/autocomplete.js/commit/2e967be6a33c532c472f9ca76295d605ed4f6f99)) +- **theme:** prepare Autocomplete Classic Theme ([#361](https://github.com/algolia/autocomplete.js/issues/361)) ([8638a98](https://github.com/algolia/autocomplete.js/commit/8638a98c76c28f7ecdd51775888f52b514432405)) +- **website:** update GitHub logo ([20a9b48](https://github.com/algolia/autocomplete.js/commit/20a9b482b267f8866d1b9795e4024a0cd084fd4e)) +# [v1.0.0-alpha.35](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.34...v1.0.0-alpha.35) (2020-11-12) +This new version introduces breaking changes. - -# [0.32.0](https://github.com/algolia/autocomplete.js/compare/v0.31.0...v0.32.0) (2018-11-06) +### Bug Fixes + +- **core:** don't complete query when closed ([86adc65](https://github.com/algolia/autocomplete.js/commit/86adc659b65b2808f0eafe2ca0419c126bf1336c)) +- **getSuggestions:** allow nested arrays to be returned ([#331](https://github.com/algolia/autocomplete.js/issues/331)) ([753c8ca](https://github.com/algolia/autocomplete.js/commit/753c8ca1358d1bd26c818ddb704caa0265d5aeae)) +- **js:** correct `item` class name ([475e88f](https://github.com/algolia/autocomplete.js/commit/475e88f6184d2b01a9ab9639c0e23601e157fb88)) +- **js:** correct `panel` class name ([a2be862](https://github.com/algolia/autocomplete.js/commit/a2be8626bc22f47f265ee5713754fc422a21af42)) +- **js:** fix highlight `hit` param ([6c03d8d](https://github.com/algolia/autocomplete.js/commit/6c03d8d148877c914e43ad75952156ac1f014803)) +- **js:** make `getSources` optional ([b12cc26](https://github.com/algolia/autocomplete.js/commit/b12cc268f900de9b47b9150156434c2ca5b8b3e9)) +- **js:** resize panel also when hidden ([9007fe0](https://github.com/algolia/autocomplete.js/commit/9007fe0e3378e4e56c6039614dcf700d46dbb6fc)) +- **qs:** ignore `storage` param ([5279dba](https://github.com/algolia/autocomplete.js/commit/5279dba5360286eba5125a24bfc2ed8b1d0aa690)) +- **recent-searches:** type `getTemplates` internal function ([1ff6a0d](https://github.com/algolia/autocomplete.js/commit/1ff6a0d0dea7a1f0fa70b15945f4b3ded097558f)) +- fix `setCollections` type ([cb967a4](https://github.com/algolia/autocomplete.js/commit/cb967a49fa07a6d67b792f24b015289ab4c44afb)) + +### Features +- **core:** warn when using `debug` option ([#364](https://github.com/algolia/autocomplete.js/issues/364)) ([2a2e3dd](https://github.com/algolia/autocomplete.js/commit/2a2e3dd72b2ba4e1856a7772f7e95a3ddad82812)) +- **recent-searches:** add search highlighting ([#370](https://github.com/algolia/autocomplete.js/issues/370)) ([3cb1d39](https://github.com/algolia/autocomplete.js/commit/3cb1d39fde6c5e0199bd9912c5fb448f5d002959)) +- introduce development and production modes ([#363](https://github.com/algolia/autocomplete.js/issues/363)) ([eed934f](https://github.com/algolia/autocomplete.js/commit/eed934f1d7d632c934c37593f555d8258c0084e3)) +- rename getters ([#362](https://github.com/algolia/autocomplete.js/issues/362)) ([b7e86d5](https://github.com/algolia/autocomplete.js/commit/b7e86d551aa29f8c372b2f560f3a9dc3c44548ca)) +- **core:** introduce new completion system ([#354](https://github.com/algolia/autocomplete.js/issues/354)) ([25099e8](https://github.com/algolia/autocomplete.js/commit/25099e8ad37004b1522364716275eb4f90f01c51)) +- **core:** remove `statusContext` API ([#350](https://github.com/algolia/autocomplete.js/issues/350)) ([a916aea](https://github.com/algolia/autocomplete.js/commit/a916aea48743eaa3e97e1e421aa1ac6986fa0e83)) +- **core:** rename private and public methods and properties ([#349](https://github.com/algolia/autocomplete.js/issues/349)) ([aeebe6d](https://github.com/algolia/autocomplete.js/commit/aeebe6de5b71c72fa4ac52b0cc5bd2b71965b973)) +- **js:** rename class names ([#351](https://github.com/algolia/autocomplete.js/issues/351)) ([8c53d2d](https://github.com/algolia/autocomplete.js/commit/8c53d2da3cf2c1669300549aadba93d486b7bf5e)) +- **plugins:** introduce Query Suggestions plugin ([#360](https://github.com/algolia/autocomplete.js/issues/360)) ([7d19396](https://github.com/algolia/autocomplete.js/commit/7d19396efbbe9c03225bb7b51540438a5ecd9ba0)) +- **recent-searches:** support search and templating ([#353](https://github.com/algolia/autocomplete.js/issues/353)) ([b8ff178](https://github.com/algolia/autocomplete.js/commit/b8ff178f48438d5e5feaf2d10d7cfe6d54d4b7de)) +- **shared:** introduce autocomplete-shared package ([#359](https://github.com/algolia/autocomplete.js/issues/359)) ([af04ae1](https://github.com/algolia/autocomplete.js/commit/af04ae1a53a89fe853c73ffc450998ef3898c38d)) +- **shared:** prefix warnings ([586f0f1](https://github.com/algolia/autocomplete.js/commit/586f0f14af9647433c8d1afa4e9dc2ecc75226c8)) + +# [v1.0.0-alpha.34](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.33...v1.0.0-alpha.34) (2020-10-20) ### Bug Fixes -* **zepto:** apply patch to prevent an error ([#263](https://github.com/algolia/autocomplete.js/issues/263)) ([917d5a7](https://github.com/algolia/autocomplete.js/commit/917d5a7)) +- **core:** keep cursor position on state changes ([#343](https://github.com/algolia/autocomplete.js/issues/343)) ([bae4d62](https://github.com/algolia/autocomplete.js/commit/bae4d621e5e8577d3394292e357e767d26d26742)) +- **algolia:** fix `getAlgoliaResults` typings ([#336](https://github.com/algolia/autocomplete.js/issues/336)) ([0559624](https://github.com/algolia/autocomplete.js/commit/0559624affe43307d0f5b47ddeed6bdbc30a0961)) +- **core:** turn public `navigator` type to partial ([#339](https://github.com/algolia/autocomplete.js/issues/339)) ([056daeb](https://github.com/algolia/autocomplete.js/commit/056daeba21764c12e2933999d0ae243ce94c956d)) +- **core:** keep last `isOpen` value on refresh ([#334](https://github.com/algolia/autocomplete.js/issues/334)) ([637d23e](https://github.com/algolia/autocomplete.js/commit/637d23ecb82f88b6de686b76657fe53a8acf119d)) + +### Features + +- **core:** provide prevState in `onStateChange` ([#335](https://github.com/algolia/autocomplete.js/issues/335)) ([21739b4](https://github.com/algolia/autocomplete.js/commit/21739b446dd7d5105dabb67adfff0937cbe06162)) + +# [1.0.0-alpha.33](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.32...v1.0.0-alpha.33) (2020-10-05) +### Bug Fixes + +- **js:** support `onStateChange` ([e4c1488](https://github.com/algolia/autocomplete.js/commit/e4c14886361c7914ba29bf6da4b2db773ed84fc6)) ### Features -* **source:** add cache disabling for datasets ([#254](https://github.com/algolia/autocomplete.js/issues/254)) ([0e65fee](https://github.com/algolia/autocomplete.js/commit/0e65fee)) -* add flag for toggling tab autocompletion ([#260](https://github.com/algolia/autocomplete.js/issues/260)) ([4dc7c52](https://github.com/algolia/autocomplete.js/commit/4dc7c52)) -* Throw err on update if suggestions are invalid type ([#256](https://github.com/algolia/autocomplete.js/issues/256)) ([179febf](https://github.com/algolia/autocomplete.js/commit/179febf)), closes [#131](https://github.com/algolia/autocomplete.js/issues/131) +- **recentSearches:** add remove button ([#326](https://github.com/algolia/autocomplete.js/issues/326)) ([648f1e8](https://github.com/algolia/autocomplete.js/commit/648f1e8de82ddfdf385da091e7402a2e4742f7d0)) +# [1.0.0-alpha.32](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.31...v1.0.0-alpha.32) (2020-09-28) +### Features - -# [0.31.0](https://github.com/algolia/autocomplete.js/compare/v0.30.0...v0.31.0) (2018-08-08) +- **core:** run `onSelect` on item click ([079a4c1](https://github.com/algolia/autocomplete.js/commit/079a4c15e78cf5e560aa34276cf2d2ac95812944)) +# [1.0.0-alpha.31](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.30...v1.0.0-alpha.31) (2020-09-28) ### Bug Fixes -* **dataset:** avoid to call the source when upadte is canceled ([a47696d](https://github.com/algolia/autocomplete.js/commit/a47696d)) -* **dataset:** avoid usage of callNow for debounce ([1a0ce74](https://github.com/algolia/autocomplete.js/commit/1a0ce74)) -* Handle an odd case with the user agent ([#242](https://github.com/algolia/autocomplete.js/issues/242)) ([c194736](https://github.com/algolia/autocomplete.js/commit/c194736)) +- **algolia:** fallback non-existant highlighted to default value ([7783bc6](https://github.com/algolia/autocomplete.js/commit/7783bc666607c9e08720e11f934945f68629b257)) +- **algolia:** warn when an attribute cannot be highlighted ([ce43e83](https://github.com/algolia/autocomplete.js/commit/ce43e83fb2eeea6da494acbba87f192c9490699a)) + +### Features + +- **core:** skip `onInput` on keyboard select with URL ([399be2b](https://github.com/algolia/autocomplete.js/commit/399be2b3e4eb316c65d2a054cded6d23df71cd62)) +- **core:** trigger `onSelect` on meta keyboard select ([b10fbe1](https://github.com/algolia/autocomplete.js/commit/b10fbe102a6e28386b41bc4c2fbdd239bc5aa886)) +- **core:** use `scrollIntoViewIfNeeded` if exists ([c409f11](https://github.com/algolia/autocomplete.js/commit/c409f11dfd0511bcfcdd60d3ba0c28cf3a61bd26)) +- **recent-searches**: add recent-searches plugin ([#316](https://github.com/algolia/autocomplete.js/issues/316)) ([858637e](https://github.com/algolia/autocomplete.js/commit/858637e34ba5bfcdfa8bf66e8785296afd436971)) +# [1.0.0-alpha.30](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.29...v1.0.0-alpha.30) (2020-09-17) + +### Bug Fixes + +- **algolia:** don't flatten `getAlgoliaHits` ([8da805f](https://github.com/algolia/autocomplete.js/commit/8da805f7b6061b73387ed937152980f6b76f42ac)) +- **algolia:** type Algolia presets ([7c34ada](https://github.com/algolia/autocomplete.js/commit/7c34adacc71fb8da3a75c66edacce6489170efba)) +- **core:** rename public types without prefix ([77487a6](https://github.com/algolia/autocomplete.js/commit/77487a6392e23c0c1a8ac674fbb8f80cc11e6c6d)) +- **js:** forward event to all `onInput` calls ([c5492aa](https://github.com/algolia/autocomplete.js/commit/c5492aa536f8189bc37f1c603ae7a8c26bcbb444)) +- **website:** ignore lint rule for Docusaurus packages ([042f4dc](https://github.com/algolia/autocomplete.js/commit/042f4dc4cf5e22518194b67c7e898c61f451282c)) ### Features -* update dist files ([9babf2e](https://github.com/algolia/autocomplete.js/commit/9babf2e)) -* **clearOnSelected:** allow users to clear the input instead of filling ([#244](https://github.com/algolia/autocomplete.js/issues/244)) ([aa2edbb](https://github.com/algolia/autocomplete.js/commit/aa2edbb)), closes [#241](https://github.com/algolia/autocomplete.js/issues/241) +- **algolia:** type highlight presets ([9f4b6bd](https://github.com/algolia/autocomplete.js/commit/9f4b6bdc58d7e1d9b4d22692f4aeaf0b0309e008)) +- **core:** support `onHighlight` on input ([e463933](https://github.com/algolia/autocomplete.js/commit/e4639332bff017925814f21857ade1c7c270222d)) +- **js:** type highlight utils ([23cff13](https://github.com/algolia/autocomplete.js/commit/23cff13800a223239b54074087bf8127f9b314fa)) +# [1.0.0-alpha.29](https://github.com/algolia/autocomplete.js/compare/v1.0.0-alpha.28...v1.0.0-alpha.29) (2020-09-15) +### Bug Fixes - -# [0.30.0](https://github.com/algolia/autocomplete.js/compare/v0.29.0...v0.30.0) (2018-04-30) +- **algolia:** import version from file ([d880d8a](https://github.com/algolia/autocomplete.js/commit/d880d8a304cb79f095fcbb1bf12d5f1e9e805a3f)) +- **algolia:** import version from package ([c314375](https://github.com/algolia/autocomplete.js/commit/c31437529724fe15a599a7356e0b9fbf78948305)) +- **algolia:** proxy highlighting tags ([baabc3a](https://github.com/algolia/autocomplete.js/commit/baabc3abc74cff260025b17f18e8b6900f7ebe9c)) +- **core:** fix type for `getSources` ([18e88ae](https://github.com/algolia/autocomplete.js/commit/18e88aee249f02769d55599dd209e2ca73c0600d)) +- **js:** call debounced function ([0662e1b](https://github.com/algolia/autocomplete.js/commit/0662e1be711a53c4d9ca4c82c771739a6eceffa7)) +- **js:** convert header and footer templates to `div`s ([1435aad](https://github.com/algolia/autocomplete.js/commit/1435aad459b814b435651cffe83cb5b759a914a2)) +- **js:** fix source types ([9913fe1](https://github.com/algolia/autocomplete.js/commit/9913fe1403830008eea7f61df59049d5dad34b72)) +- **js:** fix type for `getSources` ([4a29700](https://github.com/algolia/autocomplete.js/commit/4a29700f0f18fe35d60e3172932950a211cfecfe)) +- **js:** make options types optional ([569f738](https://github.com/algolia/autocomplete.js/commit/569f7381ff5bb84bd0d46fc4b2aa74ad1a7f0142)) +- **js:** update HTML elements properties at every render ([b00878c](https://github.com/algolia/autocomplete.js/commit/b00878cf2e32e50f7faee13a3521c6b6a30bb539)) +### Features +- **algolia:** escape values in highlighting utils ([50a9a73](https://github.com/algolia/autocomplete.js/commit/50a9a73df440565c5a9cbff84e12f3ce5c455e17)) +- **algolia:** introduce `parseReverseSnippetedAttribute` ([0f7a912](https://github.com/algolia/autocomplete.js/commit/0f7a9126a72468ac63513391c7f460e4c86bcc05)) +- **algolia:** type highlighting utils ([fa4b959](https://github.com/algolia/autocomplete.js/commit/fa4b959c575aeb6f36720c6e67ce6bfef2dacb06)) +- **core:** introduce debug mode ([#315](https://github.com/algolia/autocomplete.js/issues/315)) ([7a7b612](https://github.com/algolia/autocomplete.js/commit/7a7b61211209ebffacd2ccc61725df426397f2f0)) +- **js:** add `destroy` API ([4e32138](https://github.com/algolia/autocomplete.js/commit/4e3213860958aaedce52c719bd5dc263671d4a02)) +- **js:** add magnifier glass as label ([0cc8a44](https://github.com/algolia/autocomplete.js/commit/0cc8a44c3d27933bb581a2c258b2c238e8009904)) +- **js:** add reset icon ([7d2dccc](https://github.com/algolia/autocomplete.js/commit/7d2dccccaf5fb8292808bf2291f82b205a99554a)) +- **js:** allow escaping in highlighting utils ([a70f80d](https://github.com/algolia/autocomplete.js/commit/a70f80df9e0281d547ec0fe1a0c4e5102bfbc1ed)) +- **js:** export snippeting utils ([e276c5e](https://github.com/algolia/autocomplete.js/commit/e276c5e91ee8028d8b7ac87a238375ec97caf730)) +- **js:** introduce `classNames` API ([#317](https://github.com/algolia/autocomplete.js/issues/317)) ([28bb422](https://github.com/algolia/autocomplete.js/commit/28bb4220067b690145738a894c95ba8bf32cb49d)) +- **js:** introduce `dropdownPlacement` API ([#314](https://github.com/algolia/autocomplete.js/issues/314)) ([4a52ce5](https://github.com/algolia/autocomplete.js/commit/4a52ce5a27efa8b9b230bce85fb953531b8e07b8)) +- **js:** type highlighting utils ([b178487](https://github.com/algolia/autocomplete.js/commit/b17848753a2794a76edf545d649be6107c712f31)) + +# [1.0.0-alpha.28](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.27...v1.0.0-alpha.28) (2020-08-26) - -# [0.29.0](https://github.com/algolia/autocomplete.js/compare/v0.28.3...v0.29.0) (2017-10-12) +### Bug Fixes +- **core:** add type `search` to `getInputProps` ([92d95cc](https://github.com/francoischalifour/autocomplete.js/commit/92d95ccbd6683a1d8cd3ce53786f7fffc192cd00)) +- **core:** add type to `GetDropdownProps` ([6bd21fc](https://github.com/francoischalifour/autocomplete.js/commit/6bd21fc67451058f32460b1afaf65ae3a73ce71c)), closes [#70](https://github.com/francoischalifour/autocomplete.js/issues/70) +- **core:** allow calling `getDropdownProps` without argument ([c44e494](https://github.com/francoischalifour/autocomplete.js/commit/c44e49439684e8577e3341a84381acf8dba463aa)) +- **core:** prevent `mousedown` event on dropdown to keep it open ([ec9733b](https://github.com/francoischalifour/autocomplete.js/commit/ec9733bfcce0be0e94a8f4d402d1bae9c3090549)) +- **core:** rename `showCompletion` to `enableCompletion` ([07b46af](https://github.com/francoischalifour/autocomplete.js/commit/07b46afb6c382a3f8be5bb711b477b4cbc0c1382)) +- **core:** type form props ([1c2551b](https://github.com/francoischalifour/autocomplete.js/commit/1c2551b838933c095543c7abd807c0de1ac5aeb1)) +- **docsearch:** add type to `GetDropdownProps` ([50b4879](https://github.com/francoischalifour/autocomplete.js/commit/50b487969c0a1b355499b7526315eb9e4967c47a)) +- **docsearch:** allow a single instance to open ([90bfaaa](https://github.com/francoischalifour/autocomplete.js/commit/90bfaaa81740be5dac4abac23c9180718527b55e)) +- **docsearch:** capture `mousedown` event to close modal ([b802621](https://github.com/francoischalifour/autocomplete.js/commit/b802621c1e999d11296f1d1b711e454f980fb314)), closes [/github.com/facebook/react-native-website/pull/2139#issuecomment-678330203](https://github.com//github.com/facebook/react-native-website/pull/2139/issues/issuecomment-678330203) +- **docsearch:** remove `data-cy` attributes ([6bedbb7](https://github.com/francoischalifour/autocomplete.js/commit/6bedbb7d0fd4a086a5af974eb814e0ffaa355d1f)) +- **docsearch:** remove Docusaurus style ([a52cc44](https://github.com/francoischalifour/autocomplete.js/commit/a52cc448843cba0e4a04f8cb0e180c316870abb4)) +- **docsearch:** use `"false"` value for `spellCheck` in vanilla version ([d22bea7](https://github.com/francoischalifour/autocomplete.js/commit/d22bea77d7e098b6e849bd57302244c0fe15dc0a)) +- **js:** return setters and `refresh` only ([758565e](https://github.com/francoischalifour/autocomplete.js/commit/758565e71f73415e9b0d9f0f454e8c1d43a43f51)) +- **js:** revert highlighting conditions ([8fb33b1](https://github.com/francoischalifour/autocomplete.js/commit/8fb33b1e07eb5e18461c2290cbafaea61fc5c65f)) +- **js:** update types ([607ea45](https://github.com/francoischalifour/autocomplete.js/commit/607ea4547067ca994ed8e3b5e855cb3a6f85b81c)) +- **types:** allow arbitrary keys in sources ([6ed9e4a](https://github.com/francoischalifour/autocomplete.js/commit/6ed9e4ae14c6e8534f8904676b1c6d5c2755e759)) ### Features -* **a11y:** Add ariaLabel option. ([6db8e1b](https://github.com/algolia/autocomplete.js/commit/6db8e1b)) -* **a11y:** Add option to control `aria-labelledby` attribute. ([0491c43](https://github.com/algolia/autocomplete.js/commit/0491c43)) +- **autocomplete:** introduce JavaScript API ([fd9d2b7](https://github.com/francoischalifour/autocomplete.js/commit/fd9d2b7d62ad5ad4ad8d641eb5bda12d02cc7931)) +- **core:** add default form props ([2264f2b](https://github.com/francoischalifour/autocomplete.js/commit/2264f2bbd6b470d758a98ae445fb7efa945d34fa)) +- **docsearch:** add `enterkeyhint` to `go` ([d652514](https://github.com/francoischalifour/autocomplete.js/commit/d652514bc35c16f4d406a7b89ac7b479ed316c54)) +- **js:** pass state to `render` ([7f7da3d](https://github.com/francoischalifour/autocomplete.js/commit/7f7da3db27ab34c841692d034c2135d3c4e0a7a8)) +# [1.0.0-alpha.27](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.26...vv1.0.0-alpha.27) (2020-08-07) +### Bug Fixes - -## [0.28.3](https://github.com/algolia/autocomplete.js/compare/v0.28.2...v0.28.3) (2017-07-31) +- **docsearch:** fix vanilla DocSearch types ([2b5e7aa](https://github.com/francoischalifour/autocomplete.js/commit/2b5e7aad3cc02c4021970bc1971a719843416474)) +### Features +- **docsearch:** export DocSearch types ([b499fee](https://github.com/francoischalifour/autocomplete.js/commit/b499fee3e88a496b89c62f327f25e114c8e8f486)) +- **docsearch:** update missing results issue link ([fb8d735](https://github.com/francoischalifour/autocomplete.js/commit/fb8d735354d5b06752abd1008f14cef8bd986b42)) - -## [0.28.2](https://github.com/algolia/autocomplete.js/compare/v0.28.1...v0.28.2) (2017-06-22) +# [1.0.0-alpha.26](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.25...v1.0.0-alpha.26) (2020-08-04) +### Bug Fixes + +- **docsearch:** don't open modal on `/` when editing text ([6118725](https://github.com/francoischalifour/autocomplete.js/commit/6118725dda6a3c0fc92ec478923c6b3187a43ad7)) + +# [1.0.0-alpha.25](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.24...v1.0.0-alpha.25) (2020-07-30) ### Bug Fixes -* **empty template:** hide main empty template as long as we have results ([344e225](https://github.com/algolia/autocomplete.js/commit/344e225)), closes [#185](https://github.com/algolia/autocomplete.js/issues/185) +- **docsearch:** pass `autoFocus` prop to autocomplete for mobiles ([8f4d3fb](https://github.com/francoischalifour/autocomplete.js/commit/8f4d3fb8d9926e74d44da2b7b1eb388e5283b0db)) + +### Features +- **docsearch:** add `aria-label` to search button ([5bc08ca](https://github.com/francoischalifour/autocomplete.js/commit/5bc08cabc87c876fb26bf7608509cfcd000fac89)) +# [1.0.0-alpha.24](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.23...v1.0.0-alpha.24) (2020-07-23) - -## [0.28.1](https://github.com/algolia/autocomplete.js/compare/v0.28.0...v0.28.1) (2017-03-29) +### Bug Fixes +- **docsearch:** don't blur input on submit ([86da0fc](https://github.com/francoischalifour/autocomplete.js/commit/86da0fc3c66f8bb0757ce7b3a760ea752184de82)) +- **docsearch:** focus input on Selection Search ([9f1fa52](https://github.com/francoischalifour/autocomplete.js/commit/9f1fa52c68b765a36060aef8ce25728cda37affa)) + +# [1.0.0-alpha.23](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.22...v1.0.0-alpha.23) (2020-07-22) ### Bug Fixes -* **iOS:** remove double tap bug on hrefs in suggestions ([e532bd8](https://github.com/algolia/autocomplete.js/commit/e532bd8)) +- **docsearch:** add padding to dropdown when no recent searches ([0e3d0f5](https://github.com/francoischalifour/autocomplete.js/commit/0e3d0f570abee1361651e07de47b93c1b990a8b0)) +- **docsearch:** rename `DocSearch-Button` CSS class ([f3a5449](https://github.com/francoischalifour/autocomplete.js/commit/f3a5449a7ddfdf987422e213db3b2baa52d54d0e)) +- **docsearch:** use Preact alias in Babel config ([31b3bd4](https://github.com/francoischalifour/autocomplete.js/commit/31b3bd42d6677c2dbd40ee7012059bcd1202c781)) +- **search:** hide content when `disableUserPersonalization` ([4940538](https://github.com/francoischalifour/autocomplete.js/commit/4940538563b89545612fbe8f19acbd0f89d1219d)) +- **website:** memoize `onInput` callback ([9fa7d30](https://github.com/francoischalifour/autocomplete.js/commit/9fa7d30629578774d318989a7200385034e5ff3c)) +### Features +- **docsearch:** introduce `disableUserPersonalization` API ([de31121](https://github.com/francoischalifour/autocomplete.js/commit/de311210ae2bc63d7907abfbf75b61d3b624e976)) +- **docsearch:** support `initialQuery` ([11aa27b](https://github.com/francoischalifour/autocomplete.js/commit/11aa27bf332b91542cf9c0f6f7e88b0412172fb6)), closes [#51](https://github.com/francoischalifour/autocomplete.js/issues/51) +- **DocSearch:** add `DocSearch` CSS class to DocSearch elements ([0e93615](https://github.com/francoischalifour/autocomplete.js/commit/0e9361568440281f5c632d7a086cda523bf4948e)) +- **website:** forward Docusaurus props to DocSearch ([abfb06d](https://github.com/francoischalifour/autocomplete.js/commit/abfb06d6ebb6973ac40c890fa9d91dbe05459c13)) - -# [0.28.0](https://github.com/algolia/autocomplete.js/compare/v0.27.0...v0.28.0) (2017-03-24) +# [1.0.0-alpha.22](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.21...v1.0.0-alpha.22) (2020-07-09) +### Bug Fixes +- **docsearch:** support initial query ([dc476d3](https://github.com/francoischalifour/autocomplete.js/commit/dc476d322de3a8d4d589c638076767e009dd59e4)) +- **website:** import DS variables and button styles dynamically ([bef75be](https://github.com/francoischalifour/autocomplete.js/commit/bef75be039479c6b45c0f12ec12c21d1af42520f)) - -# [0.27.0](https://github.com/algolia/autocomplete.js/compare/v0.26.0...v0.27.0) (2017-03-06) +### Features +- **docsearch:** attach `docsearch.js` user agent to vanilla renderer ([e1bd8d3](https://github.com/francoischalifour/autocomplete.js/commit/e1bd8d3a94147b325adacafd5f609d5184d4aeb2)) +- **docsearch:** introduce `transformSearchClient` API ([edf6b9b](https://github.com/francoischalifour/autocomplete.js/commit/edf6b9b77b187d6d32e43c335593eb8b1a3daacf)) +- **docsearch:** introduce DocSearch.js v3 ([#56](https://github.com/francoischalifour/autocomplete.js/issues/56)) ([0ff2462](https://github.com/francoischalifour/autocomplete.js/commit/0ff2462b44eb6b42f1e4d8f53361315b0247a17b)) +- **docsearch:** track `docsearch-react` UA ([2c280e2](https://github.com/francoischalifour/autocomplete.js/commit/2c280e2ad9ca6a8d99c7e60ac6da48dd06991d30)) +- **website:** lazy load DocSearch styles ([e3bc021](https://github.com/francoischalifour/autocomplete.js/commit/e3bc021b52e282c5e516e84c2988e4c8c8355837)) +- **website:** track `docsearch-docusaurus` UA ([eb400f2](https://github.com/francoischalifour/autocomplete.js/commit/eb400f2e4ac4c4fe2eb08bb84581ea07dd20665a)) + +# [1.0.0-alpha.21](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.20...v1.0.0-alpha.21) (2020-07-07) ### Bug Fixes -* **UA:** add failsafe if params not provided ([30df97a](https://github.com/algolia/autocomplete.js/commit/30df97a)), closes [#166](https://github.com/algolia/autocomplete.js/issues/166) +- **css:** don't display key shortcuts on mobile ([1adc418](https://github.com/francoischalifour/autocomplete.js/commit/1adc418722f0017b1d504e7d3f7dda8e8104a352)) +- **css:** Firefox placeholder opacity ([49f7ac3](https://github.com/francoischalifour/autocomplete.js/commit/49f7ac3c9a7680a4e49593f278cb815d52d8d48b)) +- **docsearch:** remove theme media query ([a1030e4](https://github.com/francoischalifour/autocomplete.js/commit/a1030e493c22c5c615fa9c49e385030452b18729)) +- **test:** removed extra percy snapshot ([24e38b7](https://github.com/francoischalifour/autocomplete.js/commit/24e38b7771471609e190c5c1a61c57627126551a)) +### Features +- **docsearch:** support keyboard on focus on default integration ([7600f2a](https://github.com/francoischalifour/autocomplete.js/commit/7600f2a385b193fe5f60b67e135c1810e496052c)) +- **docsearch:** support typing query when search button is focused ([#54](https://github.com/francoischalifour/autocomplete.js/issues/54)) ([dcf2247](https://github.com/francoischalifour/autocomplete.js/commit/dcf22474d93ab261d59d12a44b3d677b7271e86e)) - -# [0.26.0](https://github.com/algolia/autocomplete.js/compare/v0.25.0...v0.26.0) (2017-02-28) +# [1.0.0-alpha.20](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.19...v1.0.0-alpha.20) (2020-07-01) +### Features + +- **docsearch:** add `/` keyboard shortcut ([d3a7275](https://github.com/francoischalifour/autocomplete.js/commit/d3a7275f03c8d397d797c9375a42bf977fc824ed)) + +# [1.0.0-alpha.19](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.18...v1.0.0-alpha.19) (2020-06-24) ### Bug Fixes -* **test:** bad handling of no actual inner mechanics of client ([622aec5](https://github.com/algolia/autocomplete.js/commit/622aec5)) +- **ci:** fix orbs declaration ([db902a1](https://github.com/francoischalifour/autocomplete.js/commit/db902a12972a206f2b75646f40625376a80cad82)) +- **ci:** Install cypress ([fb32788](https://github.com/francoischalifour/autocomplete.js/commit/fb327886b6d22d3c59cab2f8e6a32b24c5bd8eaf)) +- **ci:** install cypress with Yarn ([5f7dc27](https://github.com/francoischalifour/autocomplete.js/commit/5f7dc27547bcf195586d1ddb43f5a981bc1c7f42)) +- **ci:** npm script + percy ([949a24a](https://github.com/francoischalifour/autocomplete.js/commit/949a24a9da50a6f1ca2c7b0a6891d0a09f63ae20)) +- **ci:** remove test cypress job args ([c1bf37b](https://github.com/francoischalifour/autocomplete.js/commit/c1bf37b7966eb468281410e055a107f0e9ac3d0d)) +- **ci:** rerun ([04fb6f6](https://github.com/francoischalifour/autocomplete.js/commit/04fb6f6f93da0fdd14949cb79176a1b678e83dbd)) +- **ci:** use cypress docker image ([fa5521b](https://github.com/francoischalifour/autocomplete.js/commit/fa5521bce55e029e8510e41903024c4252ac2567)) +- **ci:** use latest cypress browsers image with node 13 ([bdab390](https://github.com/francoischalifour/autocomplete.js/commit/bdab3901876668e7cbc5b3b15498ca82af90077a)) +- **css:** fixed Modal height undefined on Gecko ([85753c5](https://github.com/francoischalifour/autocomplete.js/commit/85753c546da2465b731f70c4f7b6c6e06206feea)) +- **cypress:** Added Verify and Info npm scripts ([5f4ae05](https://github.com/francoischalifour/autocomplete.js/commit/5f4ae05c58fb76d92517fab07f622444c3d9a5b3)) +- **cypress:** changed env var name for cypress key ([28307a8](https://github.com/francoischalifour/autocomplete.js/commit/28307a84c92b2fe79939cfa4e0755a44326de54c)) +- **docsearch:** hoist `transformItems` default value ([1e0ae9e](https://github.com/francoischalifour/autocomplete.js/commit/1e0ae9eefb5fc185cbf41e6ac5c876ed8be24075)) +- **lint:** Disable import/no-common for percy ([8af940d](https://github.com/francoischalifour/autocomplete.js/commit/8af940d69bf9eb35bcc70fbc533abdcf721ea209)) +- **lint:** set carriage return in prettier config ([3016601](https://github.com/francoischalifour/autocomplete.js/commit/3016601a47699a37e9fa30620e71eef3f30f8c6d)) +- **lint:** set eol to auto ([e6db26e](https://github.com/francoischalifour/autocomplete.js/commit/e6db26e57aaf433b149dc16d9a846cdfa19c0314)) +- **test:** lint ([896ef59](https://github.com/francoischalifour/autocomplete.js/commit/896ef5959f06576f03d6ecd4ff7ee6ce96333a6d)) +- **Cypress:** Record results ([7dac93e](https://github.com/francoischalifour/autocomplete.js/commit/7dac93e6a538f69bfe44bfdfe0fbb939b0e241e1)) + +# [1.0.0-alpha.18](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.17...v1.0.0-alpha.18) (2020-06-11) + +### Bug Fixes +- **css:** overflow overlay not supported on gecko ([9e5b764](https://github.com/francoischalifour/autocomplete.js/commit/9e5b764794b5de5e2169ed13f48e024f5f1df812)) ### Features -* **algolia agent:** provide an algolia agent when searching ([6ca7ac2](https://github.com/algolia/autocomplete.js/commit/6ca7ac2)) -* **algolia agent:** provide an algolia agent when searching ([ef604e1](https://github.com/algolia/autocomplete.js/commit/ef604e1)) +- **docsearch:** introduce `initialScrollY` option ([2d5b216](https://github.com/francoischalifour/autocomplete.js/commit/2d5b21684174f0940c405d4da6839b0e94412f61)) +# [1.0.0-alpha.17](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.16...v1.0.0-alpha.17) (2020-06-08) +### Bug Fixes - -# [0.25.0](https://github.com/algolia/autocomplete.js/compare/v0.24.2...v0.25.0) (2017-02-07) +- **docsearch:** use `scrollTo` when unmounting modal ([aae0a14](https://github.com/francoischalifour/autocomplete.js/commit/aae0a1420caf17bb87249c988c735be5d5ae5c8a)) +# [1.0.0-alpha.16](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.15...v1.0.0-alpha.16) (2020-06-08) ### Bug Fixes -* **zepto:** .is() only accepts selectors, reworked code to use pure DOM ([a47a4d4](https://github.com/algolia/autocomplete.js/commit/a47a4d4)), closes [#144](https://github.com/algolia/autocomplete.js/issues/144) +- **docsearch:** always use `aria-expanded` to `true` ([b89aeb5](https://github.com/francoischalifour/autocomplete.js/commit/b89aeb5c2eccb43b3657239c519caa34ed62299e)) +- **docsearch:** don't add `distinct` search parameter ([1c11457](https://github.com/francoischalifour/autocomplete.js/commit/1c1145768a74f372c683fbee9d0aaaf0dd3fb62a)) +- **website:** don't pass default `appId` ([62e0609](https://github.com/francoischalifour/autocomplete.js/commit/62e060917b864e2a86328ea9816252d50c189654)) +- **website:** support missing `algolia` config ([4b30cdd](https://github.com/francoischalifour/autocomplete.js/commit/4b30cdd90bfb2226cfd5f34642ea1593a64f833b)) +- **website:** update netlify.com to netlify.app ([9cbb80b](https://github.com/francoischalifour/autocomplete.js/commit/9cbb80bb9078a7a19750e8f2bbef6e69ecce9cda)) +### Features +- **docsearch:** display 5 hits per category maximum ([7e6582c](https://github.com/francoischalifour/autocomplete.js/commit/7e6582cf0be5df74e8f239432db6309929caf1a6)) +- **docsearch:** introduce `resultsFooterComponent` option ([b613bb2](https://github.com/francoischalifour/autocomplete.js/commit/b613bb2a63da604cbd44e5d3dff32a4e60c63723)) +- **website:** add "Creating a renderer" guide ([71a94ea](https://github.com/francoischalifour/autocomplete.js/commit/71a94eab8cbe6e76219730b7eda3c60460dfc35b)) +- **website:** add link to search page in DocSearch modal ([d610ce9](https://github.com/francoischalifour/autocomplete.js/commit/d610ce9cd717253b33a4596ee855268e38c82ffa)) - -## [0.24.2](https://github.com/algolia/autocomplete.js/compare/v0.24.1...v0.24.2) (2017-01-20) +# [1.0.0-alpha.15](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.14...v1.0.0-alpha.15) (2020-05-20) + +### Bug Fixes + +- **css:** scroll windows ([a966e74](https://github.com/francoischalifour/autocomplete.js/commit/a966e74aee86cf4b7b25818c3f2b0e2b463636ef)) +- **css:** separate docusaurus css variables ([f41c31d](https://github.com/francoischalifour/autocomplete.js/commit/f41c31dd4af5bf5178982ed725f03dc67d0d4a6e)) +- **docsearch:** use `scrollTop` on body ([129c1d1](https://github.com/francoischalifour/autocomplete.js/commit/129c1d13aa77e1f6c38d09ee3b9cfa2eb9df8f57)) +- **website:** update DocSearch integration ([d41605d](https://github.com/francoischalifour/autocomplete.js/commit/d41605dd0d1451c1760ad78ab361fa5a52e35820)) +# [1.0.0-alpha.14](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.13...v1.0.0-alpha.14) (2020-05-15) ### Bug Fixes -* **dep:** immediate is a dependency, not a devDependency ([22164ad](https://github.com/algolia/autocomplete.js/commit/22164ad)) +- **docsearch:** remove blur effect to avoid performance issues ([978229f](https://github.com/francoischalifour/autocomplete.js/commit/978229f2b838f71effe04b77876cbcf36f17e0a4)) +- **docsearch:** use `scrollTop` for IE support ([b51e81d](https://github.com/francoischalifour/autocomplete.js/commit/b51e81d94b473ed3602b02073911899cc5dc6a4a)) +- **docsearch:** use absolute URLs ([e1ed4e8](https://github.com/francoischalifour/autocomplete.js/commit/e1ed4e887a9e57af37e384fb08423d293502423c)) +### Features +- **docsearch:** add `DocSearch` component ([218944e](https://github.com/francoischalifour/autocomplete.js/commit/218944e4412dc0afee54521895247736a86f16ca)) +- **docsearch:** add `useDocSearchKeyboardEvents` API ([5697895](https://github.com/francoischalifour/autocomplete.js/commit/5697895167e7bdc61429b9794c92ef57cd0315bc)) +- **docusaurus:** import DocSearch modal on hover ([e680f24](https://github.com/francoischalifour/autocomplete.js/commit/e680f2453a9339467f3e502586d3bccc23edb911)) - -## [0.24.1](https://github.com/algolia/autocomplete.js/compare/v0.24.0...v0.24.1) (2017-01-20) +# [1.0.0-alpha.13](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.12...v1.0.0-alpha.13) (2020-04-24) + +### Bug Fixes + +- **fix**: update workspace dependencies when releasing ([076b7be](076b7be69f89fa677a66cb0a91c51d02d440ac0a)) +# [1.0.0-alpha.12](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.11...v1.0.0-alpha.12) (2020-04-24) ### Bug Fixes -* **postMessage:** avoid using postMessage when feasible ([a99f664](https://github.com/algolia/autocomplete.js/commit/a99f664)), closes [#142](https://github.com/algolia/autocomplete.js/issues/142) +- **docsearch:** add index name to localStorage key ([f5fbaa3](https://github.com/francoischalifour/autocomplete.js/commit/f5fbaa3b544038caa76cd8382af2a9c990f80a4f)) +# [](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.11...v) (2020-04-24) +### Bug Fixes - -# [0.24.0](https://github.com/algolia/autocomplete.js/compare/0.23.0...v0.24.0) (2017-01-10) +- **docsearch:** add index name to localStorage key ([f5fbaa3](https://github.com/francoischalifour/autocomplete.js/commit/f5fbaa3b544038caa76cd8382af2a9c990f80a4f)) + +# [1.0.0-alpha.11](https://github.com/francoischalifour/autocomplete.js/compare/v1.0.0-alpha.10...v1.0.0-alpha.11) (2020-04-24) + +### Features + +- **docsearch**: create clean exports ([d0f8ff3](https://github.com/francoischalifour/autocomplete.js/commit/d0f8ff3ab4f89c9dce1f2bdc923d94aed7515dc1)) +- **design:** icon actions ([056d333](https://github.com/francoischalifour/autocomplete.js/commit/056d333780d6c5f48cf86236b443916b75b073b4)) +- **design:** new error icons + update icons + update light shadows / searchbox ([2e77e70](https://github.com/francoischalifour/autocomplete.js/commit/2e77e70e792ccb52d7c6300f149697fad441fd2e)) +- **design:** new icons ([5bd3cbc](https://github.com/francoischalifour/autocomplete.js/commit/5bd3cbc10693d3b65f7908e3523fa9bcc187f0ea)) +- **docsearch:** add `hitComponent` and `transformItems` options ([daaafe5](https://github.com/francoischalifour/autocomplete.js/commit/daaafe5178cd43e258e389f579bc7517a3935b09)) +- **docsearch:** add DocSearch for Docusaurus ([#39](https://github.com/francoischalifour/autocomplete.js/issues/39)) ([ad63053](https://github.com/francoischalifour/autocomplete.js/commit/ad630539c444417f414e0e8bcf74fd20f7cd73c8)) +- **docsearch:** add recent searches ([#40](https://github.com/francoischalifour/autocomplete.js/issues/40)) ([36e7fab](https://github.com/francoischalifour/autocomplete.js/commit/36e7fabe43582fe358cb15f92e5afddecd5f1a7d)) +- **docsearch:** add search suggestions ([d1fe8b2](https://github.com/francoischalifour/autocomplete.js/commit/d1fe8b2be3d30f067892ac9f04f6f802b6b40826)) +- **docsearch:** allow placeholder customization ([3a4f13b](https://github.com/francoischalifour/autocomplete.js/commit/3a4f13b35198a0372f6d4cab7b661e774e424c6f)) +- **docsearch:** animate cards on action ([8c7bdc1](https://github.com/francoischalifour/autocomplete.js/commit/8c7bdc117f6a76a53c9245a2089cda5dd02b71e6)) +- **docsearch:** append modal to body ([73a7f0e](https://github.com/francoischalifour/autocomplete.js/commit/73a7f0ed491407d9c80ef9d14ddd704c0ac8f7c4)) +- **docsearch:** catch retry errors in the search client ([750c4b5](https://github.com/francoischalifour/autocomplete.js/commit/750c4b51e2159a787613aa959ac62238c1f1a65b)) +- **docsearch:** display more recent searches when no favorites ([a4c7082](https://github.com/francoischalifour/autocomplete.js/commit/a4c70825cc5d204b0ee44bcb8965a325f7fa5471)) +- **docsearch:** forward props to autocomplete-core ([7cbcb12](https://github.com/francoischalifour/autocomplete.js/commit/7cbcb128bd59fe5c550ffb534f399b2b1022e5a3)) +- **docsearch:** introduce favorite searches ([61bd0aa](https://github.com/francoischalifour/autocomplete.js/commit/61bd0aa5f768658c70f7cb0b7bb465c9b9579da7)) +- **docsearch:** introduce Selection Search ([d5fd4d6](https://github.com/francoischalifour/autocomplete.js/commit/d5fd4d66a08a1b6d7f990757261c5f9e32e95c1c)) +- **docsearch:** save content record hit parent in recent searches ([3fe547f](https://github.com/francoischalifour/autocomplete.js/commit/3fe547f2cc17f2c5a2f1c526bca4e98b42093e1f)) +- **docsearch:** trap focus in modal ([0ca92ca](https://github.com/francoischalifour/autocomplete.js/commit/0ca92ca18b60d3949f4150a7afb5eb1f6984612d)) +- **docsearch:** use `preconnect` link in Docusaurus integration ([33e2e8b](https://github.com/francoischalifour/autocomplete.js/commit/33e2e8bd9436222933e7bd949082c3a446cb9f6e)) +- **docsearch:** use relative URLs ([f434ca1](https://github.com/francoischalifour/autocomplete.js/commit/f434ca1f92c9638ddfa42f3cd8b7d0093490830f)) + +# [1.0.0-alpha.10](https://github.com/francoischalifour/autocomplete.js/compare/v0.37.0...v1.0.0-alpha.10) (2020-03-31) + +### Bug Fixes + +- remove unused prop getters ([074c92d](https://github.com/francoischalifour/autocomplete.js/commit/074c92d3601cd0208759a211b2d22ca2430a2340)) +- **core:** call `generateAutocompleteId` only if necessary ([ce4d496](https://github.com/francoischalifour/autocomplete.js/commit/ce4d496d1f074d02051c1cbe1f296d2a5d6e1c1c)) +- **getters:** compute `aria-autocomplete` based on the props ([9ea5042](https://github.com/francoischalifour/autocomplete.js/commit/9ea5042c3a78126c09b03daff2b682db4535aba1)) +- **getters:** don't forward data prop getters ([0deb9a1](https://github.com/francoischalifour/autocomplete.js/commit/0deb9a14a14e7730c2b54c63a5138a4bfcd2d1e7)) +- **react:** fix options types ([fdde35f](https://github.com/francoischalifour/autocomplete.js/commit/fdde35ff26da7a097c073816e1da76d6f0e6ed49)) +- **react:** remove dropdown from DOM when closed ([c647224](https://github.com/francoischalifour/autocomplete.js/commit/c64722467f7ab77babc6e53b4f46386efd335d95)) + +### Features + +- **core:** allow input pause in keyboard navigation ([0000499](https://github.com/francoischalifour/autocomplete.js/commit/000049971884e958e92cbfd14331ab88ff2b5e1f)) +- **core:** introduce `getDropdownProps` ([9b758ee](https://github.com/francoischalifour/autocomplete.js/commit/9b758eee271954eb7228916bf822b09a1a715e61)) +- **react:** attach Algolia agents in React renderer ([c6c4da5](https://github.com/francoischalifour/autocomplete.js/commit/c6c4da580afec6dbf69b55c55809b1e1f9b8e9fc)) +- **react:** create highlighting components ([fb49161](https://github.com/francoischalifour/autocomplete.js/commit/fb49161ea59f3ff925bcffe3e74435acb6e47c18)) +- add openOnFocus and remove minLength ([#31](https://github.com/francoischalifour/autocomplete.js/issues/31)) ([553ea68](https://github.com/francoischalifour/autocomplete.js/commit/553ea68950bfc94eb8588a71dd5580db4682931c)) +- swap Preact with React ([#34](https://github.com/francoischalifour/autocomplete.js/issues/34)) ([e0f2568](https://github.com/francoischalifour/autocomplete.js/commit/e0f25689440f7177e663ac6306e49f8f89a0727a)) +- **autoFocus:** add support for `autoFocus` option ([4d3f792](https://github.com/francoischalifour/autocomplete.js/commit/4d3f7921307ef9417a8dd1147e71309350de77fe)) +- **core:** filter out falsy sources ([f771522](https://github.com/francoischalifour/autocomplete.js/commit/f771522df77f3297644aec5214b459fc960f0b3f)) +- **core:** introduce `getEnvironmentProps` for mobile experience ([#27](https://github.com/francoischalifour/autocomplete.js/issues/27)) ([f9d7eed](https://github.com/francoischalifour/autocomplete.js/commit/f9d7eed75514911ee45ed3aaee47c30373fdbd8a)) +- **core:** process completion as a state enhancer ([#29](https://github.com/francoischalifour/autocomplete.js/issues/29)) ([53c2ef7](https://github.com/francoischalifour/autocomplete.js/commit/53c2ef7b1b985486199ae2dc54069a7bcfe3b41a)) +- **core:** rename `shouldDropdownOpen` to `shouldDropdownShow` ([f2c3eb2](https://github.com/francoischalifour/autocomplete.js/commit/f2c3eb2d5ec6e5338df5b685af8edfb6cc477659)), closes [/github.com/francoischalifour/autocomplete.js/pull/16#pullrequestreview-355978230](https://github.com//github.com/francoischalifour/autocomplete.js/pull/16/issues/pullrequestreview-355978230) +- **core:** support `onHighlight` on sources ([0f4101b](https://github.com/francoischalifour/autocomplete.js/commit/0f4101bbf82e15afcc6f02ae1075a05dee7f261c)) +- **core:** support `onSelect` on sources ([0cf0a93](https://github.com/francoischalifour/autocomplete.js/commit/0cf0a93bf3e5c04972e70c716867ce82e220c640)) +- **onInput:** support `onInput` prop for controlled mode ([7345eb9](https://github.com/francoischalifour/autocomplete.js/commit/7345eb9c279b28a00bc833912fa9697654becab0)) +- **onSubmit:** introduce `onSubmit` option ([#24](https://github.com/francoischalifour/autocomplete.js/issues/24)) ([ca0891c](https://github.com/francoischalifour/autocomplete.js/commit/ca0891c87256f0eb6a04f28fbf92b42826346c67)) +- **react:** introduce `inputRef` for focus management ([#32](https://github.com/francoischalifour/autocomplete.js/issues/32)) ([4d804fe](https://github.com/francoischalifour/autocomplete.js/commit/4d804fe62ee7d67fb335866aeeb87f070255319e)) +- **react:** place dropdown with Popper ([#25](https://github.com/francoischalifour/autocomplete.js/issues/25)) ([ca38070](https://github.com/francoischalifour/autocomplete.js/commit/ca380704f6506dc6c9c82b564701da3ce5772109)) +- **website:** add Docusaurus 2 website ([#33](https://github.com/francoischalifour/autocomplete.js/issues/33)) ([3ee0ab5](https://github.com/francoischalifour/autocomplete.js/commit/3ee0ab53bd3d78ac3943fd35ec54f14d016dbd5a)) + + + +# [0.37.0](https://github.com/algolia/autocomplete.js/compare/v0.36.0...v0.37.0) (2019-08-30) + +### Bug Fixes + +- **clear:** Avoid error when clear is called after destroy ([#287](https://github.com/algolia/autocomplete.js/issues/287)) ([244425d](https://github.com/algolia/autocomplete.js/commit/244425d)) + + + +# [0.36.0](https://github.com/algolia/autocomplete.js/compare/v0.35.0...v0.36.0) (2019-02-21) + +### Bug Fixes + +- **standalone:** use aria label from input ([#276](https://github.com/algolia/autocomplete.js/issues/276)) ([4b94466](https://github.com/algolia/autocomplete.js/commit/4b94466)) + + + +# [0.35.0](https://github.com/algolia/autocomplete.js/compare/v0.34.0...v0.35.0) (2018-12-17) + +### Bug Fixes + +- **chrome-only:** Change autocomplete from 'nope' to 'off' ([#273](https://github.com/algolia/autocomplete.js/issues/273)) ([892a8f0](https://github.com/algolia/autocomplete.js/commit/892a8f0)) +- **utils:** correct \_.every method ([#274](https://github.com/algolia/autocomplete.js/issues/274)) ([55af1e3](https://github.com/algolia/autocomplete.js/commit/55af1e3)) + + + +# [0.34.0](https://github.com/algolia/autocomplete.js/compare/v0.33.0...v0.34.0) (2018-12-04) + +### Features + +- change autocomplete from 'off' to 'nope' ([#250](https://github.com/algolia/autocomplete.js/issues/250)) ([fbbed04](https://github.com/algolia/autocomplete.js/commit/fbbed04)) + + + +# [0.33.0](https://github.com/algolia/autocomplete.js/compare/v0.32.0...v0.33.0) (2018-11-19) + +### Bug Fixes + +- **release:** Update mversion to 1.12 ([#268](https://github.com/algolia/autocomplete.js/issues/268)) ([08b8e30](https://github.com/algolia/autocomplete.js/commit/08b8e30)) + +### Features + +- **selected:** Adding context.selectionMethod to selected event ([#267](https://github.com/algolia/autocomplete.js/issues/267)) ([36028a6](https://github.com/algolia/autocomplete.js/commit/36028a6)) + + + +# [0.32.0](https://github.com/algolia/autocomplete.js/compare/v0.31.0...v0.32.0) (2018-11-06) + +### Bug Fixes + +- **zepto:** apply patch to prevent an error ([#263](https://github.com/algolia/autocomplete.js/issues/263)) ([917d5a7](https://github.com/algolia/autocomplete.js/commit/917d5a7)) + +### Features + +- **source:** add cache disabling for datasets ([#254](https://github.com/algolia/autocomplete.js/issues/254)) ([0e65fee](https://github.com/algolia/autocomplete.js/commit/0e65fee)) +- add flag for toggling tab autocompletion ([#260](https://github.com/algolia/autocomplete.js/issues/260)) ([4dc7c52](https://github.com/algolia/autocomplete.js/commit/4dc7c52)) +- Throw err on update if suggestions are invalid type ([#256](https://github.com/algolia/autocomplete.js/issues/256)) ([179febf](https://github.com/algolia/autocomplete.js/commit/179febf)), closes [#131](https://github.com/algolia/autocomplete.js/issues/131) + + + +# [0.31.0](https://github.com/algolia/autocomplete.js/compare/v0.30.0...v0.31.0) (2018-08-08) + +### Bug Fixes + +- **dataset:** avoid to call the source when update is canceled ([a47696d](https://github.com/algolia/autocomplete.js/commit/a47696d)) +- **dataset:** avoid usage of callNow for debounce ([1a0ce74](https://github.com/algolia/autocomplete.js/commit/1a0ce74)) +- Handle an odd case with the user agent ([#242](https://github.com/algolia/autocomplete.js/issues/242)) ([c194736](https://github.com/algolia/autocomplete.js/commit/c194736)) + +### Features + +- update dist files ([9babf2e](https://github.com/algolia/autocomplete.js/commit/9babf2e)) +- **clearOnSelected:** allow users to clear the input instead of filling ([#244](https://github.com/algolia/autocomplete.js/issues/244)) ([aa2edbb](https://github.com/algolia/autocomplete.js/commit/aa2edbb)), closes [#241](https://github.com/algolia/autocomplete.js/issues/241) + + + +# [0.30.0](https://github.com/algolia/autocomplete.js/compare/v0.29.0...v0.30.0) (2018-04-30) + + + +# [0.29.0](https://github.com/algolia/autocomplete.js/compare/v0.28.3...v0.29.0) (2017-10-12) + +### Features + +- **a11y:** Add ariaLabel option. ([6db8e1b](https://github.com/algolia/autocomplete.js/commit/6db8e1b)) +- **a11y:** Add option to control `aria-labelledby` attribute. ([0491c43](https://github.com/algolia/autocomplete.js/commit/0491c43)) + + + +## [0.28.3](https://github.com/algolia/autocomplete.js/compare/v0.28.2...v0.28.3) (2017-07-31) + + + +## [0.28.2](https://github.com/algolia/autocomplete.js/compare/v0.28.1...v0.28.2) (2017-06-22) + +### Bug Fixes + +- **empty template:** hide main empty template as long as we have results ([344e225](https://github.com/algolia/autocomplete.js/commit/344e225)), closes [#185](https://github.com/algolia/autocomplete.js/issues/185) + + + +## [0.28.1](https://github.com/algolia/autocomplete.js/compare/v0.28.0...v0.28.1) (2017-03-29) + +### Bug Fixes + +- **iOS:** remove double tap bug on hrefs in suggestions ([e532bd8](https://github.com/algolia/autocomplete.js/commit/e532bd8)) + + + +# [0.28.0](https://github.com/algolia/autocomplete.js/compare/v0.27.0...v0.28.0) (2017-03-24) + + +# [0.27.0](https://github.com/algolia/autocomplete.js/compare/v0.26.0...v0.27.0) (2017-03-06) ### Bug Fixes -* **angular:** do not launch the directive if autocomplete has a value ([f96a1ba](https://github.com/algolia/autocomplete.js/commit/f96a1ba)), closes [#136](https://github.com/algolia/autocomplete.js/issues/136) -* **typeahead:** propagate redrawn ([82293e4](https://github.com/algolia/autocomplete.js/commit/82293e4)) +- **UA:** add failsafe if params not provided ([30df97a](https://github.com/algolia/autocomplete.js/commit/30df97a)), closes [#166](https://github.com/algolia/autocomplete.js/issues/166) + + + +# [0.26.0](https://github.com/algolia/autocomplete.js/compare/v0.25.0...v0.26.0) (2017-02-28) + +### Bug Fixes +- **test:** bad handling of no actual inner mechanics of client ([622aec5](https://github.com/algolia/autocomplete.js/commit/622aec5)) ### Features -* **appendTo:** new parameter ([e40cbd0](https://github.com/algolia/autocomplete.js/commit/e40cbd0)) +- **algolia agent:** provide an algolia agent when searching ([6ca7ac2](https://github.com/algolia/autocomplete.js/commit/6ca7ac2)) +- **algolia agent:** provide an algolia agent when searching ([ef604e1](https://github.com/algolia/autocomplete.js/commit/ef604e1)) + + + +# [0.25.0](https://github.com/algolia/autocomplete.js/compare/v0.24.2...v0.25.0) (2017-02-07) + +### Bug Fixes + +- **zepto:** .is() only accepts selectors, reworked code to use pure DOM ([a47a4d4](https://github.com/algolia/autocomplete.js/commit/a47a4d4)), closes [#144](https://github.com/algolia/autocomplete.js/issues/144) + + +## [0.24.2](https://github.com/algolia/autocomplete.js/compare/v0.24.1...v0.24.2) (2017-01-20) + +### Bug Fixes + +- **dep:** immediate is a dependency, not a devDependency ([22164ad](https://github.com/algolia/autocomplete.js/commit/22164ad)) + + +## [0.24.1](https://github.com/algolia/autocomplete.js/compare/v0.24.0...v0.24.1) (2017-01-20) + +### Bug Fixes + +- **postMessage:** avoid using postMessage when feasible ([a99f664](https://github.com/algolia/autocomplete.js/commit/a99f664)), closes [#142](https://github.com/algolia/autocomplete.js/issues/142) + +# [0.24.0](https://github.com/algolia/autocomplete.js/compare/0.23.0...v0.24.0) (2017-01-10) +### Bug Fixes +- **angular:** do not launch the directive if autocomplete has a value ([f96a1ba](https://github.com/algolia/autocomplete.js/commit/f96a1ba)), closes [#136](https://github.com/algolia/autocomplete.js/issues/136) +- **typeahead:** propagate redrawn ([82293e4](https://github.com/algolia/autocomplete.js/commit/82293e4)) +### Features +- **appendTo:** new parameter ([e40cbd0](https://github.com/algolia/autocomplete.js/commit/e40cbd0)) ### 0.23.0 Dec 14, 2016 -* feat(build): add noConflict() for standalone build, fixes #133 +- feat(build): add noConflict() for standalone build, fixes #133 ### 0.22.1 Nov 07, 2016 -* Fixes bad behavior when `autoselectOnBlur` used, fixes #113 +- Fixes bad behavior when `autoselectOnBlur` used, fixes #113 ### 0.22.0 Oct 25, 2016 -* Add `autocomplete:cursorremoved` event, see #105 -* Add `autoselectOnBlur` option, fixes #113 +- Add `autocomplete:cursorremoved` event, see #105 +- Add `autoselectOnBlur` option, fixes #113 ### 0.21.8 Oct 3, 2016 -* Do not allow Zepto to leak to window. Never. +- Do not allow Zepto to leak to window. Never. ### 0.21.7 Sep 21, 2016 -* Ensure the `empty` templates get displayed before the `footer`. -* Ensure the dataset `empty` templates are displayed when all datasets are empty. +- Ensure the `empty` templates get displayed before the `footer`. +- Ensure the dataset `empty` templates are displayed when all datasets are empty. ### 0.21.6 Sep 20, 2016 -* Make sure we don't leak/override `window.Zepto`. +- Make sure we don't leak/override `window.Zepto`. ### 0.21.5 Sep 15, 2016 -* While selecting the top suggestion (autoselect=true), do not update the input. +- While selecting the top suggestion (autoselect=true), do not update the input. ### 0.21.4 Sep 2, 2016 -* Ensure the cursor selects the first suggestion when the dropdown is shown + send the `cursorchanged` event. +- Ensure the cursor selects the first suggestion when the dropdown is shown + send the `cursorchanged` event. ### 0.21.3 Aug 1, 2016 -* Ensure empty template displays from first keystroke (#104) +- Ensure empty template displays from first keystroke (#104) ### 0.21.2 July 26, 2016 -* fix(empty): fix the empty even handling, fixes #95 +- fix(empty): fix the empty even handling, fixes #95 ### 0.21.1 July 19, 2016 -* fix(getVal): fix getVal on standalone build +- fix(getVal): fix getVal on standalone build ### 0.21.0 July 15, 2016 -* Upgrade to zepto 1.2.0 +- Upgrade to zepto 1.2.0 ### 0.20.1 June 14, 2016 -* Ensure the dropdown menu is hidden when there is an `$empty` block and blank query. +- Ensure the dropdown menu is hidden when there is an `$empty` block and blank query. ### 0.20.0 June 04, 2016 -* Ensure we don't update the input value on mouseenter (#76) -* Render an `empty` template if no results (#80) +- Ensure we don't update the input value on mouseenter (#76) +- Render an `empty` template if no results (#80) ### 0.19.1 May 04, 2016 -* Fixed the angular build (_.Event was undefined) +- Fixed the angular build (\_.Event was undefined) ### 0.19.0 Apr 25, 2016 -* Allow select handler to prevent menu from being closed (#72) -* Do not trigger the cursorchanged event while entering/leaving nested divs (#71) +- Allow select handler to prevent menu from being closed (#72) +- Do not trigger the cursorchanged event while entering/leaving nested divs (#71) ### 0.18.0 Apr 07, 2016 -* Ability to customize the CSS classes used to render the DOM -* Ensure the `autocomplete:cursorchanged` event is called on `mouseover` as well +- Ability to customize the CSS classes used to render the DOM +- Ensure the `autocomplete:cursorchanged` event is called on `mouseover` as well ### 0.17.3 Apr 04, 2016 -* Standalone: ensure we actually use the Zepto object and not whatever is in `window.$` +- Standalone: ensure we actually use the Zepto object and not whatever is in `window.$` ### 0.17.2 Mar 21, 2016 -* Ability to setup the autocomplete on a multi-inputs Zepto selector -* Propagate the `shown` event to the top-level +- Ability to setup the autocomplete on a multi-inputs Zepto selector +- Propagate the `shown` event to the top-level ### 0.17.1 Mar 19, 2016 -* REVERT [Ability to setup the autocomplete on a multi-inputs Zepto selector] Fix #59 +- REVERT [Ability to setup the autocomplete on a multi-inputs Zepto selector] Fix #59 ### 0.17.0 Mar 18, 2016 -* Ability to setup the autocomplete on a multi-inputs Zepto selector -* Add a new `shown` event triggered when the dropdown menu is opened and non-empty +- Ability to setup the autocomplete on a multi-inputs Zepto selector +- Add a new `shown` event triggered when the dropdown menu is opened and non-empty BREAKING CHANGE: the standalone object returned by the `autocomplete()` method is now a Zepto object. ### 0.16.2 Jan 22, 2016 -* stop using weird zepto package. Stop using chained .data calls - it seems that chaining them ended up in an `undefined` return value when passing `undefined` as a value +- stop using weird zepto package. Stop using chained .data calls it seems that chaining them ended up in an `undefined` return value when passing `undefined` as a value ### 0.16.1 Jan 22, 2016 -* remove npm-zepto, use zepto original package (now on npm) fixes #48 +- remove npm-zepto, use zepto original package (now on npm) fixes #48 ### 0.16.0 Dec 11, 2015 -* Emit a new `autocomplete:updated` event as soon as a dataset is rendered +- Emit a new `autocomplete:updated` event as soon as a dataset is rendered ### 0.15.0 Dec 10, 2015 -* Ability to configure the dropdown menu container +- Ability to configure the dropdown menu container ### 0.14.1 Dec 2, 2015 -* Move Zepto as a dependency (not a peer dep) -* Really use the `query` instead of the `displayKey` (was supposed to be fixed in 0.11.0) +- Move Zepto as a dependency (not a peer dep) +- Really use the `query` instead of the `displayKey` (was supposed to be fixed in 0.11.0) ### 0.14.0 Nov 28, 2015 -* Move npm-zepto & angular to peerDependencies -* Fixed custom dropdownMenu's footer & header not being displayed properly -* Allow dataset with name=0 +- Move npm-zepto & angular to peerDependencies +- Fixed custom dropdownMenu's footer & header not being displayed properly +- Allow dataset with name=0 ### 0.13.1 Nov 25, 2015 -* Move the bower release name to `algolia-autocomplete.js` since `autocomplete.js` is already used +- Move the bower release name to `algolia-autocomplete.js` since `autocomplete.js` is already used ### 0.13.0 Nov 25, 2015 -* Add Bower release +- Add Bower release ### 0.12.0 Oct 15, 2015 -* Expose the underlying `close`, `open`, ... functions in the standalone build. +- Expose the underlying `close`, `open`, ... functions in the standalone build. ### 0.11.1 Oct 13, 2015 -* Zepto doesn't work like jQuery regarding the `data` API, it doesn't support serializing objects. +- Zepto doesn't work like jQuery regarding the `data` API, it doesn't support serializing objects. ### 0.11.0 Oct 07, 2015 -* If the `displayKey` is not specified and the `value` attribute missing, don't update the input value with `undefined`. -* Expose the `sources` object in the Angular.js build as well. +- If the `displayKey` is not specified and the `value` attribute missing, don't update the input value with `undefined`. +- Expose the `sources` object in the Angular.js build as well. ### 0.10.0 Oct 06, 2015 -* Add a new `includeAll` option to the `popularIn` source to add an extra suggestion. +- Add a new `includeAll` option to the `popularIn` source to add an extra suggestion. ### 0.9.0 Oct 01, 2015 -* Full CommonJS compliance (moved from browserify to webpack) +- Full CommonJS compliance (moved from browserify to webpack) ### 0.8.0 Sep 24, 2015 -* UMD compliance +- UMD compliance ### 0.7.0 Sep 16, 2015 -* New standalone build (including Zepto.js) -* Get rid of lodash-compat and use jQuery, Zepto or Angular.js's helper functions +- New standalone build (including Zepto.js) +- Get rid of lodash-compat and use jQuery, Zepto or Angular.js's helper functions ### 0.6.0 Sep 11, 2015 -* Add Zepto.js support. +- Add Zepto.js support. ### 0.5.0 Sep 9, 2015 -* The wrapper span will now have a `table-cell` display if the original input was a `block` inside a `table`. +- The wrapper span will now have a `table-cell` display if the original input was a `block` inside a `table`. ### 0.4.0 Aug 12, 2015 -* Add a new `openOnFocus` option to open the dropdown menu when the input is focused +- Add a new `openOnFocus` option to open the dropdown menu when the input is focused ### 0.3.0 July 27, 2015 -* Add Angular.js support [#7] +- Add Angular.js support [#7] ### 0.2.0 July 16, 2015 -* Ability to change the layout based on the matching datasets [#11] +- Ability to change the layout based on the matching datasets [#11] ### 0.1.0 July 13, 2015 -* Start using semantic versioning +- Start using semantic versioning ### 0.0.2 July 13, 2015 -* Ability to keep the dropdown menu opened when the input if blurred [#1] -* Ability to use a custom dropdown menu template [#2] -* Ability to configure a custom header/footer on the dropdown menu [#3] +- Ability to keep the dropdown menu opened when the input if blurred [#1] +- Ability to use a custom dropdown menu template [#2] +- Ability to configure a custom header/footer on the dropdown menu [#3] ### 0.0.1 July 12, 2015 -* First release based on Twitter's typeahead.js library -* Travis-ci.org, Coveralls.io, Saucelabs.com integration -* CommonJS compatibility +- First release based on Twitter's typeahead.js library +- Travis-ci.org, Coveralls.io, Saucelabs.com integration +- CommonJS compatibility diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9cc1ac28f2..a35f5b9e17 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,73 +1,83 @@ -# Contributing to Autocomplete.js +# Contributing to Autocomplete -First of all, thanks for taking a look at contributing here 🎉 If you have any questions while contributing, feel free to open an issue or to send an email to mentioning the PR or issue you're working on. +Welcome to the contributing guide for Autocomplete! -## Development +If this guide does not contain what you are looking for and thus prevents you from contributing, don't hesitate to [open an discussion](https://github.com/algolia/autocomplete/discussions). -To start developing, you can use the following commands: +## Reporting an issue -```sh -yarn -yarn dev -open http://localhost:8888/test/playground.html -``` +Opening an issue is very effective way to contribute because many users might also be impacted. We'll make sure to fix it quickly if it's technically feasible and doesn't have important side effects for other users. -Linting is done with [eslint](http://eslint.org/) and [Algolia's configuration](https://github.com/algolia/eslint-config-algolia) and can be run with: +Before reporting an issue, first check that there is not an already open issue for the same topic using the [issues page](https://github.com/algolia/autocomplete/issues). Don't hesitate to thumb up an issue that corresponds to the problem you have. -```sh -yarn lint -``` +Another element that will help us go faster at solving the issue is to provide a reproducible test case. We recommend to [use this CodeSandbox template](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/playground?file=/app.tsx). -## Tests +## Code contribution -Unit tests are written using [Jasmine](http://jasmine.github.io/) and ran with [Karma](http://karma-runner.github.io/). Integration tests are using [Mocha](http://mochajs.org/) and [Saucelabs](https://saucelabs.com/). +For any code contribution, you need to: -To run the unit tests suite run: +- Make sure that the code change has been discussed in [Issues](https://github.com/algolia/autocomplete/issues) or [Discussions](https://github.com/algolia/autocomplete/discussions) +- Fork and clone the project +- Create a new branch for what you want to solve ("fix/issue-number", "feat/name-of-the-feature") +- Make your changes +- Open a pull request -```sh -yarn test -``` +Then: -To run the integration tests suite run: +- Automatic checks will be run +- A team member will review the pull request + +When every check is green and a team member approves, your contribution is merged! 🚀 + +Before contributing, make sure you have the following dependencies set up on your machine: + +- [Yarn v1](https://classic.yarnpkg.com/) +- [Node.js](https://nodejs.org/) (version available in [.nvmrc](https://github.com/algolia/autocomplete/blob/next/.nvmrc)) +- setup for [node-gyp](https://github.com/nodejs/node-gyp) (on macOS, install the XCode Command Line Tools by running `xcode-select --install` in the terminal if you haven't already) +- a version of Chrome/Chromium (for Puppeteer) + +## Commit conventions + +This project follows the [conventional changelog](https://conventionalcommits.org/) approach. This means that all commit messages should be formatted using the following scheme: -```sh -yarn build -yarn server -ngrok 8888 -TEST_HOST=http://YOUR_NGROK_ID.ngrok.com SAUCE_ACCESS_KEY=YOUR_KEY SAUCE_USERNAME=YOUR_USERNAME./node_modules/mocha/bin/mocha --harmony -R spec ./test/integration/test.js +``` +type(scope): description ``` -### Testing accessibility +This convention is used to generate the [changelog](https://github.com/algolia/autocomplete/tree/next/CHANGELOG.md). -Autocomplete.js is accessible to screen readers, and here's how to test how most blind users will experience it: +In most cases, we use the following types: -#### Steps +- `fix`: for any resolution of an issue (identified or not) +- `feat`: for any new feature +- `refactor`: for any code change that neither adds a feature nor fixes an issue +- `docs`: for any documentation change or addition +- `chore`: for anything that is not related to the library itself (e.g., doc, tooling) -1. Run `yarn dev` on your development machine -1. Start the screen reader -1. Open a browser to http://YOUR_IP:8888/test/playground.html -1. Tab to the field -1. Type a search query -1. Use the arrow keys to navigate through the results +Even though the scope is optional, we try to fill it in as it helps us better understand the impact of a change. -✔ SUCCESS: results are read (not necessarily in sync with the visually selected cursor) -𐄂 FAIL: no text is read or the screen reader keeps reading the typed query +Finally, if your work is based on an issue on GitHub, please add in the body of the commit message "Closes #1234" if it solves the issue #1234 (read "[Closing issues using keywords](https://help.github.com/en/articles/closing-issues-using-keywords)"). -#### Recommended testing platforms +Some examples of valid commit messages (used as first lines): -- VoiceOver (CMD+F5 in macOS): Safari, Chrome -- [JAWS](http://www.freedomscientific.com/Products/Blindness/JAWS): IE11, Chrome (Windows 7 VM available at [modern.ie](https://modern.ie)) -- [NVDA](http://www.nvaccess.org/): IE11, Chrome (Windows 8.1 VM available at [modern.ie](https://modern.ie)) +> - fix(js): increase magnifying glass size +> - feat(core): add `enterKeyHint` input prop +> - refactor(core): inline `navigator` default prop +> - docs(state): add `query` state property +> - docs(readme): add "Showcase" section +> - chore(deps): update dependency rollup-plugin-babel to v3.0.7 -#### Tips +## Requirements -- All screen readers work slightly differently - which makes making accessible pages tricky. -- Don't worry if the usability isn't 100% perfect, but make sure the functionality is there. +To run this project, you will need: -## Release +- Node.js ([nvm](https://github.com/creationix/nvm#install-script) is recommended) +- [Yarn](https://yarnpkg.com) -Decide if this is a patch, minor or major release, have a look at [semver.org](http://semver.org/). +## Release ```sh -npm run release [major|minor|patch|x.x.x] +yarn run release ``` + +It will create a pull request for the next release. When it's reviewed, approved and merged, then CircleCI will automatically publish the packages to npm. diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 3609dd42bd..0000000000 --- a/Gruntfile.js +++ /dev/null @@ -1,144 +0,0 @@ -'use strict'; - -module.exports = function(grunt) { - grunt.initConfig({ - version: grunt.file.readJSON('package.json').version, - - buildDir: 'dist', - - banner: [ - '/*!', - ' * autocomplete.js <%= version %>', - ' * https://github.com/algolia/autocomplete.js', - ' * Copyright <%= grunt.template.today("yyyy") %> Algolia, Inc. and other contributors; Licensed MIT', - ' */' - ].join('\n'), - - usebanner: { - all: { - options: { - position: 'top', - banner: '<%= banner %>', - linebreak: true - }, - files: { - src: ['dist/*.js'] - } - } - }, - - uglify: { - jquery: { - src: '<%= buildDir %>/autocomplete.jquery.js', - dest: '<%= buildDir %>/autocomplete.jquery.min.js' - }, - angular: { - src: '<%= buildDir %>/autocomplete.angular.js', - dest: '<%= buildDir %>/autocomplete.angular.min.js' - }, - standalone: { - src: '<%= buildDir %>/autocomplete.js', - dest: '<%= buildDir %>/autocomplete.min.js' - } - }, - - webpack: { - jquery: { - entry: './index_jquery.js', - output: { - path: '<%= buildDir %>', - filename: 'autocomplete.jquery.js' - }, - externals: [{ - jquery: 'jQuery' - }] - }, - angular: { - entry: './index_angular.js', - output: { - path: '<%= buildDir %>', - filename: 'autocomplete.angular.js' - }, - externals: ['angular'] - }, - standalone: { - entry: './index.js', - output: { - path: '<%= buildDir %>', - filename: 'autocomplete.js', - library: 'autocomplete', - libraryTarget: 'umd' - } - } - }, - - sed: { - version: { - pattern: '%VERSION%', - replacement: '<%= version %>', - recursive: true, - path: '<%= buildDir %>' - } - }, - - eslint: { - options: { - config: '.eslintrc' - }, - src: ['src/**/*.js', 'Gruntfile.js'] - }, - - watch: { - js: { - files: 'src/**/*.js', - tasks: 'build' - } - }, - - clean: { - dist: 'dist' - }, - - connect: { - server: { - options: {port: 8888, keepalive: true} - } - }, - - concurrent: { - options: {logConcurrentOutput: true}, - dev: ['server', 'watch'] - }, - - step: { - options: { - option: false - } - } - }); - - // aliases - // ------- - - grunt.registerTask('default', 'build'); - grunt.registerTask('build', ['webpack', 'sed:version', 'uglify', 'usebanner']); - grunt.registerTask('server', 'connect:server'); - grunt.registerTask('lint', 'eslint'); - grunt.registerTask('dev', 'concurrent:dev'); - - // load tasks - // ---------- - - grunt.loadNpmTasks('grunt-sed'); - grunt.loadNpmTasks('grunt-exec'); - grunt.loadNpmTasks('grunt-step'); - grunt.loadNpmTasks('grunt-banner'); - grunt.loadNpmTasks('grunt-concurrent'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks('grunt-eslint'); - grunt.loadNpmTasks('grunt-webpack'); -}; diff --git a/LICENSE b/LICENSE index 8c315f450b..78f811663b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 Algolia -http://www.algolia.com/ +Copyright (c) 2015-present Algolia, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -10,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 956d02e5f7..0020459ccc 100644 --- a/README.md +++ b/README.md @@ -1,790 +1,126 @@ -# Autocomplete.js - - -This JavaScript library adds a fast and fully-featured auto-completion menu to your search box displaying results "as you type". It can easily be combined with Algolia's realtime search engine. The library is available as a jQuery plugin, an Angular.js directive or a standalone library. - -[![build status](https://travis-ci.org/algolia/algoliasearch-client-node.svg?branch=master)](http://travis-ci.org/algolia/autocomplete.js) -[![NPM version](https://badge.fury.io/js/autocomplete.js.svg)](http://badge.fury.io/js/autocomplete.js) -[![Coverage Status](https://coveralls.io/repos/algolia/autocomplete.js/badge.svg?branch=master)](https://coveralls.io/r/algolia/autocomplete.js?branch=master) -[![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/autocomplete.js/badge?style=rounded)](https://www.jsdelivr.com/package/npm/autocomplete.js) -![jQuery](https://img.shields.io/badge/jQuery-OK-blue.svg) -![Zepto.js](https://img.shields.io/badge/Zepto.js-OK-blue.svg) -![Angular.js](https://img.shields.io/badge/Angular.js-OK-blue.svg) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) - - -[![Browser tests](https://saucelabs.com/browser-matrix/opensauce-algolia.svg)](https://saucelabs.com/u/opensauce-algolia) - -## Table of Contents - - - - - -- [Features](#features) -- [Installation](#installation) - - [jsDelivr](#jsdelivr) - - [cdnjs](#cdnjs) - - [npm](#npm) - - [Bower](#bower) - - [Source dist/](#source-dist) - - [Browserify](#browserify) -- [Usage](#usage) - - [Standalone](#standalone) - - [jQuery](#jquery) - - [Angular.JS](#angularjs) -- [Look and Feel](#look-and-feel) -- [Global Options](#global-options) -- [Datasets](#datasets) -- [Sources](#sources) - - [Hits](#hits) - - [PopularIn (aka "xxxxx in yyyyy")](#popularin-aka-xxxxx-in-yyyyy) - - [Custom source](#custom-source) -- [Security](#security) - - [User-generated data: protecting against XSS](#user-generated-data-protecting-against-xss) -- [FAQ](#faq) - - [How can I `Control`-click on results and have them open in a new tab?](#how-can-i-control-click-on-results-and-have-them-open-in-a-new-tab) -- [Events](#events) -- [API](#api) - - [jQuery](#jquery-1) - - [Standalone](#standalone-1) -- [Contributing & releasing](#contributing--releasing) -- [Credits](#credits) - - - -## Features - -* Displays suggestions to end-users as they type -* Shows top suggestion as a hint (i.e. background text) -* Supports custom templates to allow for UI flexibility -* Works well with RTL languages and input method editors -* Triggers custom events +
+ Autocomplete +

+ A JavaScript library that lets you quickly build autocomplete experiences +

+[![Version](https://img.shields.io/npm/v/@algolia/autocomplete-js.svg?style=flat-square)](https://www.npmjs.com/package/@algolia/autocomplete-js) [![MIT License](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](LICENSE) -## Installation - -The `autocomplete.js` library must be included **after** jQuery, Zepto or Angular.js (with jQuery). - -### jsDelivr - -```html - - - - - -``` - -### cdnjs - -```html - - - - - -``` - -### npm - -```sh -npm install --save autocomplete.js -``` - -### Bower - -```sh -bower install algolia-autocomplete.js -S -``` - -### Source dist/ - -You can find the built version in [dist/](https://github.com/algolia/autocomplete.js/tree/master/dist). - -### Browserify - -You can require it and use [Browserify](http://browserify.org/): - -```js -var autocomplete = require('autocomplete.js'); -``` - -## Usage - -### Standalone - - 1. Include `autocomplete.min.js` - 1. Initialize the auto-completion menu calling the `autocomplete` function - -```html - - - - - - -``` - -### jQuery - - 1. Include `autocomplete.jquery.min.js` after including `jQuery` - 1. Initialize the auto-completion menu calling the `autocomplete` jQuery plugin - -```html - - - - - - -``` - -### Angular.JS - - 1. Include `autocomplete.angular.min.js` after including `jQuery` & `Angular.js` - 1. Inject the `algolia.autocomplete` module - 1. Add the `autocomplete`, `aa-datasets` and the optional `aa-options` attribute to your search bar - -```html -
-
- - - - -``` - -**Note:** You need to rely on `jQuery`, the lite version embedded in Angular.js won't work. - -## Look and Feel - -Below is a faux mustache template describing the DOM structure of an autocomplete -dropdown menu. Keep in mind that `header`, `footer`, `suggestion`, and `empty` -come from the provided templates detailed [here](#datasets). - -```html - - {{#datasets}} -
- {{{header}}} - - {{#suggestions}} -
{{{suggestion}}}
- {{/suggestions}} - {{^suggestions}} - {{{empty}}} - {{/suggestions}} -
- {{{footer}}} -
- {{/datasets}} - {{empty}} -
-``` - -When an end-user mouses or keys over a `.aa-suggestion`, the class `aa-cursor` will be added to it. You can use this class as a hook for styling the "under cursor" state of suggestions. - - -Add the following CSS rules to add a default style: - -```css -.algolia-autocomplete { - width: 100%; -} -.algolia-autocomplete .aa-input, .algolia-autocomplete .aa-hint { - width: 100%; -} -.algolia-autocomplete .aa-hint { - color: #999; -} -.algolia-autocomplete .aa-dropdown-menu { - width: 100%; - background-color: #fff; - border: 1px solid #999; - border-top: none; -} -.algolia-autocomplete .aa-dropdown-menu .aa-suggestion { - cursor: pointer; - padding: 5px 4px; -} -.algolia-autocomplete .aa-dropdown-menu .aa-suggestion.aa-cursor { - background-color: #B2D7FF; -} -.algolia-autocomplete .aa-dropdown-menu .aa-suggestion em { - font-weight: bold; - font-style: normal; -} -``` - -Here is what the [basic example](https://github.com/algolia/autocomplete.js/tree/master/examples) looks like: - -![Basic example](./examples/basic.gif) - -## Global Options +All you need to get started is: -When initializing an autocomplete, there are a number of global options you can configure. +- A container to inject the experience into +- Data to fill the autocomplete with +- Any Virtual DOM solution (JavaScript, Preact, React, Vue, etc.) -* `autoselect` – If `true`, the first rendered suggestion in the dropdown will automatically have the `cursor` class, and pressing `` will select it. +The data that populates the autocomplete results are called [sources](https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/sources). You can use whatever you want in your sources: a static set of searches terms, search results from an external source like an [Algolia](https://www.algolia.com/doc/guides/getting-started/what-is-algolia/) index, recent searches, and more. -* `autoselectOnBlur` – If `true`, when the input is blurred, the first rendered suggestion in the dropdown will automatically have the `cursor` class, and pressing `` will select it. This option should be used on mobile, see [#113](https://github.com/algolia/autocomplete.js/issues/113) +By configuring just those two required parameters ([`container`](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/#param-container) and [`getSources`](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/#param-getsources)) you can have an interactive autocomplete experience. **The library creates an input and provides the interactivity and accessibility attributes, but you're in full control of the DOM elements to output**. -* `tabAutocomplete` – If `true`, pressing tab will select the first rendered suggestion in the dropdown. Defaults to `true`. +

+ + Screenshot + +
+
+ + Documentation • + API • + Playground + +

-* `hint` – If `false`, the autocomplete will not show a hint. Defaults to `true`. - -* `debug` – If `true`, the autocomplete will not close on `blur`. Defaults to `false`. - -* `clearOnSelected` – If `true`, the autocomplete will empty the search box when a suggestion is selected. This is useful if you want to use this as a way to input tags using the `selected` event. - -* `openOnFocus` – If `true`, the dropdown menu will open when the input is focused. Defaults to `false`. - -* `appendTo` – If set with a DOM selector, doesn't wrap the input and appends the wrapper and dropdown menu to the first DOM element matching the selector. It automatically positions the wrapper under the input, and sets it to the same width as the input. Can't be used with `hint: true`, because `hint` requires the wrapper around the input. +## Installation -* `dropdownMenuContainer` – If set with a DOM selector, it overrides the container of the dropdown menu. +The recommended way to get started is with the [`autocomplete-js`](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) package. It includes everything you need to render a JavaScript autocomplete experience. -* `templates` – An optional hash overriding the default templates. - * `dropdownMenu` – the dropdown menu template. The template should include all *dataset* placeholders. - * `header` – the header to prepend to the dropdown menu - * `footer` – the footer to append to the dropdown menu - * `empty` – the template to display when none of the datasets are returning results. The templating function - is called with a context containing the underlying `query`. +Otherwise, you can install the [`autocomplete-core`](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-core) package if you want to [build a renderer](https://www.algolia.com/doc/ui-libraries/autocomplete/guides/creating-a-renderer) from scratch. -* `cssClasses` – An optional hash overriding the default css classes. - * `root` – the root classes. Defaults to `algolia-autocomplete`. - * `prefix` – the CSS class prefix of all nested elements. Defaults to `aa`. - * `noPrefix` - set this to true if you wish to not use any prefix. Without this option, all nested elements classes will have a leading dash. Defaults to `false`. - * `dropdownMenu` – the dropdown menu CSS class. Defaults to `dropdown-menu`. - * `input` – the input CSS class. Defaults to `input`. - * `hint` – the hint CSS class. Defaults to `hint`. - * `suggestions` – the suggestions list CSS class. Defaults to `suggestions`. - * `suggestion` – the suggestion wrapper CSS class. Defaults to `suggestion`. - * `cursor` – the cursor CSS class. Defaults to `cursor`. - * `dataset` – the dataset CSS class. Defaults to `dataset`. - * `empty` – the empty CSS class. Defaults to `empty`. +All Autocomplete packages are available on the [npm](https://www.npmjs.com) registry. -* `keyboardShortcuts` - Array of shortcut that will focus the input. For example if you want to bind `s` and `/` - you can specify: `keyboardShortcuts: ['s', '/']` +```bash +yarn add @algolia/autocomplete-js +# or +npm install @algolia/autocomplete-js +``` -* `ariaLabel` - An optional string that will populate the `aria-label` attribute. +If you don't use a package manager, you can use the HTML `script` element: ```html - - + ``` -* `minLength` – The minimum character length needed before suggestions start - getting rendered. Defaults to `1`. - -* `autoWidth` – This option allow you to control the width of autocomplete wrapper. When `false` the autocomplete wrapper will not have the width style attribute and you are be able to put your specific width property in your css to control the wrapper. Default value is `true`. - -One scenario for use this option. e.g. You have a `max-width` css attribute in your `autocomplete-dropdown-menu` and you need to width grows until fill the `max-width`. In this scenario you put a `width: auto` in your autocomplete wrapper css class and the `max-width` in your autocomplete dropdown class and all done. - -## Datasets - -An autocomplete is composed of one or more datasets. When an end-user modifies the -value of the underlying input, each dataset will attempt to render suggestions for the -new value. - -Datasets can be configured using the following options. - -* `source` – The backing data source for suggestions. Expected to be a function - with the signature `(query, cb)`. It is expected that the function will - compute the suggestion set (i.e. an array of JavaScript objects) for `query` - and then invoke `cb` with said set. `cb` can be invoked synchronously or - asynchronously. - -* `name` – The name of the dataset. This will be appended to `tt-dataset-` to - form the class name of the containing DOM element. Must only consist of - underscores, dashes, letters (`a-z`), and numbers. Defaults to a random - number. - -* `displayKey` – For a given suggestion object, determines the string - representation of it. This will be used when setting the value of the input - control after a suggestion is selected. Can be either a key string or a - function that transforms a suggestion object into a string. Defaults to - `value`. - Example function usage: `displayKey: function(suggestion) { return suggestion.nickname || suggestion.firstName }` - -* `templates` – A hash of templates to be used when rendering the dataset. Note - a precompiled template is a function that takes a JavaScript object as its - first argument and returns a HTML string. - - * `empty` – Rendered when `0` suggestions are available for the given query. - Can be either a HTML string or a precompiled template. The templating function - is called with a context containing `query`, `isEmpty`, and any optional - arguments that may have been forwarded by the source: - `function emptyTemplate({ query, isEmpty }, [forwarded args])`. - - * `footer` – Rendered at the bottom of the dataset. Can be either a HTML - string or a precompiled template. The templating function - is called with a context containing `query`, `isEmpty`, and any optional - arguments that may have been forwarded by the source: - `function footerTemplate({ query, isEmpty }, [forwarded args])`. - - * `header` – Rendered at the top of the dataset. Can be either a HTML string - or a precompiled template. The templating function - is called with a context containing `query`, `isEmpty`, and any optional - arguments that may have been forwarded by the source: - `function headerTemplate({ query, isEmpty }, [forwarded args])`. - - * `suggestion` – Used to render a single suggestion. The templating function - is called with the `suggestion`, and any optional arguments that may have - been forwarded by the source: `function suggestionTemplate(suggestion, [forwarded args])`. - Defaults to the value of `displayKey` wrapped in a `p` tag i.e. `

{{value}}

`. - -* `debounce` – If set, will postpone the source execution until after `debounce` milliseconds -have elapsed since the last time it was invoked. - -* `cache` - If set to `false`, subsequent identical queries will always execute the source function for suggestions. Defaults to `true`. - -## Sources - -A few helpers are provided by default to ease the creation of Algolia-based sources. - -### Hits - -To build a source based on Algolia's `hits` array, just use: - -```js -{ - source: autocomplete.sources.hits(indexObj, { hitsPerPage: 2 }), - templates: { - suggestion: function(suggestion, answer) { - // FIXME - } - } -} -``` - -### PopularIn (aka "xxxxx in yyyyy") - -To build an Amazon-like autocomplete menu, suggesting popular queries and for the most popular one displaying the associated categories, you can use the `popularIn` source: - -```js -{ - source: autocomplete.sources.popularIn(popularQueriesIndexObj, { hitsPerPage: 3 }, { - source: 'sourceAttribute', // attribute of the `popularQueries` index use to query the `index` index - index: productsIndexObj, // targeted index - facets: 'facetedCategoryAttribute', // facet used to enrich the most popular query - maxValuesPerFacet: 3 // maximum number of facets returned - }, { - includeAll: true, // should it include an extra "All department" suggestion - allTitle: 'All departments' // the included category label - }), - templates: { - suggestion: function(suggestion, answer) { - var value = suggestion.sourceAttribute; - if (suggestion.facet) { - // this is the first suggestion - // and it has been enriched with the matching facet - value += ' in ' + suggestion.facet.value + ' (' + suggestion.facet.count + ')'; - } - return value; - } - } -} -``` - -### Custom source - -The `source` options can also take a function. It enables you to have more control of the results returned by Algolia search. The function `function(query, callback)` takes 2 parameters - * `query: String`: the text typed in the autocomplete - * `callback: Function`: the callback to call at the end of your processing with the array of suggestions - -```js -source: function(query, callback) { - var index = client.initIndex('myindex'); - index.search(query, { hitsPerPage: 1, facetFilters: 'category:mycat' }).then(function(answer) { - callback(answer.hits); - }, function() { - callback([]); - }); -} -``` - -Or by reusing an existing source: - -```js -var hitsSource = autocomplete.sources.hits(index, { hitsPerPage: 5 }); - -source: function(query, callback) { - hitsSource(query, function(suggestions) { - // FIXME: Do stuff with the array of returned suggestions - callback(suggestions); - }); -} -``` - -## Security - -### User-generated data: protecting against XSS - -Malicious users may attempt to engineer XSS attacks by storing HTML/JS in their data. It is important that user-generated data be properly escaped before using it in an *autocomplete.js* template. - -In order to easily do that, *autocomplete.js* provides you with a helper function escaping all HTML code but the highlighting tags: - -```js - templates: { - suggestion: function(suggestion) { - var val = suggestion._highlightResult.name.value; - return autocomplete.escapeHighlightedString(val); - } - } -``` - -If you did specify custom highlighting pre/post tags, you can specify them as 2nd and 3rd parameter: - -```js - templates: { - suggestion: function(suggestion) { - var val = suggestion._highlightResult.name.value; - return autocomplete.escapeHighlightedString(val, '', ''); - } - } -``` - -## FAQ - -### How can I `Control`-click on results and have them open in a new tab? - -You'll need to update your suggestion templates to make them as `` links -and not simple divs. `Control`-clicking on them will trigger the default browser -behavior and open suggestions in a new tab. - -To also support keyboard navigation, you'll need to listen to the -`autocomplete:selected` event and change `window.location` to the destination -URL. - -Note that you might need to check the value of `context.selectionMethod` in -`autocomplete:selected` first. If it's equal to `click`, you should `return` -early, otherwise your main window will **also** follow the link. +## Usage -Here is an example of how it would look like: +To get started, you need a container for your autocomplete to go in. If you don't have one already, you can insert one into your markup: -```javascript -autocomplete(…).on('autocomplete:selected', function(event, suggestion, dataset, context) { - // Do nothing on click, as the browser will already do it - if (context.selectionMethod === 'click') { - return; - } - // Change the page, for example, on other events - window.location.assign(suggestion.url); -}); +```js title="HTML" +
``` -## Events - -The autocomplete component triggers the following custom events. - -* `autocomplete:opened` – Triggered when the dropdown menu of the autocomplete is - opened. - -* `autocomplete:shown` – Triggered when the dropdown menu of the autocomplete is - shown (opened and non-empty). - -* `autocomplete:empty` – Triggered when all datasets are empty. - -* `autocomplete:closed` – Triggered when the dropdown menu of the autocomplete is - closed. +Then, insert your autocomplete into it by calling the [`autocomplete`](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/) function and providing the [`container`](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/#param-container). It can be a [CSS selector](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) or an [Element](https://developer.mozilla.org/docs/Web/API/HTMLElement). -* `autocomplete:updated` – Triggered when a dataset is rendered. +Make sure to provide a container (e.g., a `div`), not an `input`. Autocomplete generates a fully accessible search box for you. -* `autocomplete:cursorchanged` – Triggered when the dropdown menu cursor is moved - to a different suggestion. The event handler will be invoked with 3 - arguments: the jQuery event object, the suggestion object, and the name of - the dataset the suggestion belongs to. +```js title="JavaScript" +import { autocomplete } from '@algolia/autocomplete-js'; -* `autocomplete:selected` – Triggered when a suggestion from the dropdown menu is - selected. The event handler will be invoked with the following arguments: the jQuery - event object, the suggestion object, the name of the dataset the - suggestion belongs to and a `context` object. The `context` contains - a `.selectionMethod` key that can be either `click`, `enterKey`, `tabKey` or - `blur`, depending how the suggestion was selected. - -* `autocomplete:cursorremoved` – Triggered when the cursor leaves the selections - or its current index is lower than 0 - -* `autocomplete:autocompleted` – Triggered when the query is autocompleted. - Autocompleted means the query was changed to the hint. The event handler will - be invoked with 3 arguments: the jQuery event object, the suggestion object, - and the name of the dataset the suggestion belongs to. - -* `autocomplete:redrawn` – Triggered when `appendTo` is used and the wrapper is resized/repositionned. - -All custom events are triggered on the element initialized as the autocomplete. - -## API - -### jQuery - -Turns any `input[type="text"]` element into an auto-completion menu. `globalOptions` is an -options hash that's used to configure the autocomplete to your liking. Refer to -[Global Options](#global-options) for more info regarding the available configs. Subsequent -arguments (`*datasets`), are individual option hashes for datasets. For more -details regarding datasets, refer to [Datasets](#datasets). - -``` -$(selector).autocomplete(globalOptions, datasets) -``` - -Example: - -```js -$('.search-input').autocomplete({ - minLength: 3 -}, -{ - name: 'my-dataset', - source: mySource +autocomplete({ + container: '#autocomplete', + // ... }); ``` -#### jQuery#autocomplete('destroy') - -Removes the autocomplete functionality and reverts the `input` element back to its -original state. - -```js -$('.search-input').autocomplete('destroy'); -``` - -#### jQuery#autocomplete('open') - -Opens the dropdown menu of the autocomplete. Note that being open does not mean that -the menu is visible. The menu is only visible when it is open and has content. - -```js -$('.search-input').autocomplete('open'); -``` - -#### jQuery#autocomplete('close') - -Closes the dropdown menu of the autocomplete. - -```js -$('.search-input').autocomplete('close'); -``` - -#### jQuery#autocomplete('val') +Continue reading our [**Getting Started**](https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/getting-started/#defining-where-to-put-your-autocomplete) guide. -Returns the current value of the autocomplete. The value is the text the user has -entered into the `input` element. +## Documentation -```js -var myVal = $('.search-input').autocomplete('val'); -``` +The [documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete) offers a few ways to learn about the Autocomplete library: -#### jQuery#autocomplete('val', val) +- Read the [**Core Concepts**](https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/basic-configuration-options/) to learn more about underlying principles, like [**Sources**](https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/sources/) and [**State**](https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/state/). +- Follow the [**Guides**](https://www.algolia.com/doc/ui-libraries/autocomplete/guides/adding-suggested-searches) to understand how to build common UX patterns. +- Refer to [**API reference**](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) for a comprehensive list of parameters and options. +- Try out the [**Playground**](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/playground?file=/app.tsx) where you can fork a basic implementation and play around. -Sets the value of the autocomplete. This should be used in place of `jQuery#val`. +You can find more on the [documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete). -```js -$('.search-input').autocomplete('val', myVal); -``` +## Support -#### jQuery.fn.autocomplete.noConflict() +- [GitHub Discussions](https://github.com/algolia/autocomplete/discussions) +- See [Algolia Careers](https://www.algolia.com/careers) for Paris-based opportunities and job openings if you want to help build search experiences. -Returns a reference to the autocomplete plugin and reverts `jQuery.fn.autocomplete` -to its previous value. Can be used to avoid naming collisions. +## Packages -```js -var autocomplete = jQuery.fn.autocomplete.noConflict(); -jQuery.fn._autocomplete = autocomplete; -``` +| Package | Description | Documentation | +| --- | --- | --- | +| [`autocomplete-js`](packages/autocomplete-js) | JavaScript package for Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js) | +| [`autocomplete-core`](packages/autocomplete-core) | JavaScript core primitives to build an autocomplete experience | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-core) | +| [`autocomplete-plugin-recent-searches`](packages/autocomplete-plugin-recent-searches) | A plugin to add recent searches to Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches) | +| [`autocomplete-plugin-query-suggestions`](packages/autocomplete-plugin-query-suggestions) | A plugin to add query suggestions to Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions) | +| [`autocomplete-plugin-algolia-insights`](packages/autocomplete-plugin-algolia-insights) | A plugin to add Algolia Insights to Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-algolia-insights) | +| [`autocomplete-plugin-redirect-url`](packages/autocomplete-plugin-redirect-url) | A plugin to enable redirect URLs | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-redirect-url) | +| [`autocomplete-plugin-tags`](packages/autocomplete-plugin-tags) | A plugin to manage and display a list of tags in Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags) | +| [`autocomplete-preset-algolia`](packages/autocomplete-preset-algolia) | Presets to use Algolia features with Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-preset-algolia) | +| [`autocomplete-theme-classic`](packages/autocomplete-theme-classic) | Classic theme for Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-theme-classic) | -### Standalone +## Showcase -The standalone version API is similiar to jQuery's: - -```js -var search = autocomplete(containerSelector, globalOptions, datasets); -``` +See the awesome experiences people built with Autocomplete: -Example: +| [![DocSearch](./media/showcase/docsearch.png)](https://docsearch.algolia.com) | [![Algolia Documentation](./media/showcase/algolia-documentation.png)](https://algolia.com/doc) | +| --- | --- | +|
DocSearch
|
Algolia Documentation
| -```js -var search = autocomplete('#search', { hint: false }, [{ - source: autocomplete.sources.hits(index, { hitsPerPage: 5 }) -}]); - -search.autocomplete.open(); -search.autocomplete.close(); -search.autocomplete.getVal(); -search.autocomplete.setVal('Hey Jude'); -search.autocomplete.destroy(); -search.autocomplete.getWrapper(); // since autocomplete.js wraps your input into another div, you can access that div -``` - -You can also pass a custom Typeahead instance in Autocomplete.js constructor: - -```js -var search = autocomplete('#search', { hint: false }, [{ ... }], new Typeahead({ ... })); -``` - -#### autocomplete.noConflict() - -Returns a reference to the autocomplete plugin and reverts `window.autocomplete` -to its previous value. Can be used to avoid naming collisions. - -```js -var algoliaAutocomplete = autocomplete.noConflict(); -``` +Find more in our [**Showcase**](https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/showcase/). -## Contributing & releasing +## Sandboxes -see [CONTRIBUTING.md](./CONTRIBUTING.md) +Check out [sandboxes using Autocomplete](https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/sandboxes). -## Credits +## License -This library has originally been forked from [Twitter's typeahead.js](https://github.com/twitter/typeahead.js) library. +[MIT](LICENSE) diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000000..58f0471a28 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,50 @@ +const wrapWarningWithDevCheck = require('./scripts/babel/wrap-warning-with-dev-check'); + +module.exports = (api) => { + const isTest = api.env('test'); + const modules = isTest ? 'commonjs' : false; + const targets = {}; + + if (isTest) { + targets.node = true; + } else { + targets.browsers = ['last 2 versions', 'ie >= 9']; + } + + return { + presets: [ + '@babel/preset-typescript', + [ + '@babel/preset-react', + { pragma: 'createElement', pragmaFrag: 'Fragment' }, + ], + [ + '@babel/preset-env', + { + modules, + targets, + }, + ], + ], + plugins: clean([ + wrapWarningWithDevCheck, + [ + 'inline-replace-variables', + { + __DEV__: { + type: 'node', + replacement: "process.env.NODE_ENV !== 'production'", + }, + __TEST__: { + type: 'node', + replacement: "process.env.NODE_ENV === 'test'", + }, + }, + ], + ]), + }; +}; + +function clean(config) { + return config.filter(Boolean); +} diff --git a/bower.json b/bower.json deleted file mode 100644 index c12ab25786..0000000000 --- a/bower.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "algolia-autocomplete.js", - "main": "dist/autocomplete.js", - "version": "0.37.0", - "homepage": "https://github.com/algolia/autocomplete.js", - "authors": [ - "Algolia Team " - ], - "description": "Fast and fully-featured autocomplete library", - "keywords": [ - "autocomplete", - "typeahead" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test" - ] -} diff --git a/bundlesize.config.json b/bundlesize.config.json new file mode 100644 index 0000000000..90ff41819d --- /dev/null +++ b/bundlesize.config.json @@ -0,0 +1,40 @@ +{ + "files": [ + { + "path": "packages/autocomplete-core/dist/umd/index.production.js", + "maxSize": "9.5 kB" + }, + { + "path": "packages/autocomplete-js/dist/umd/index.production.js", + "maxSize": "21.75 kB" + }, + { + "path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js", + "maxSize": "2.5 kB" + }, + { + "path": "packages/autocomplete-plugin-algolia-insights/dist/umd/index.production.js", + "maxSize": "3.5 kB" + }, + { + "path": "packages/autocomplete-plugin-redirect-url/dist/umd/index.production.js", + "maxSize": "2.25 kB" + }, + { + "path": "packages/autocomplete-plugin-recent-searches/dist/umd/index.production.js", + "maxSize": "3.25 kB" + }, + { + "path": "packages/autocomplete-plugin-query-suggestions/dist/umd/index.production.js", + "maxSize": "3.5 kB" + }, + { + "path": "packages/autocomplete-plugin-tags/dist/umd/index.production.js", + "maxSize": "2 kB" + }, + { + "path": "packages/autocomplete-theme-classic/dist/theme.min.css", + "maxSize": "4.5 kB" + } + ] +} diff --git a/dist/autocomplete.angular.js b/dist/autocomplete.angular.js deleted file mode 100644 index 62c6971404..0000000000 --- a/dist/autocomplete.angular.js +++ /dev/null @@ -1,2872 +0,0 @@ -/*! - * autocomplete.js 0.37.0 - * https://github.com/algolia/autocomplete.js - * Copyright 2019 Algolia, Inc. and other contributors; Licensed MIT - */ -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; - -/******/ // The require function -/******/ function __webpack_require__(moduleId) { - -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) -/******/ return installedModules[moduleId].exports; - -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ exports: {}, -/******/ id: moduleId, -/******/ loaded: false -/******/ }; - -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); - -/******/ // Flag the module as loaded -/******/ module.loaded = true; - -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } - - -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; - -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; - -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; - -/******/ // Load entry module and return exports -/******/ return __webpack_require__(0); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - module.exports = __webpack_require__(1); - - -/***/ }, -/* 1 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var angular = __webpack_require__(2); - - // setup DOM element - var DOM = __webpack_require__(3); - DOM.element = angular.element; - - // setup utils functions - var _ = __webpack_require__(4); - _.isArray = angular.isArray; - _.isFunction = angular.isFunction; - _.isObject = angular.isObject; - _.bind = angular.element.proxy; - _.each = angular.forEach; - _.map = angular.element.map; - _.mixin = angular.extend; - _.Event = angular.element.Event; - - var EventBus = __webpack_require__(5); - var Typeahead = __webpack_require__(6); - - angular.module('algolia.autocomplete', []) - .directive('autocomplete', ['$parse', '$injector', function($parse, $injector) { - // inject the sources in the algolia namespace if available - try { - $injector.get('algolia').sources = Typeahead.sources; - $injector.get('algolia').escapeHighlightedString = _.escapeHighlightedString; - } catch (e) { - // not fatal - } - - return { - restrict: 'AC', // Only apply on an attribute or class - scope: { - options: '&aaOptions', - datasets: '&aaDatasets' - }, - link: function(scope, element, attrs) { - if (!element.hasClass('autocomplete') && attrs.autocomplete !== '') return; - attrs = attrs; // no-unused-vars - scope.options = $parse(scope.options)(scope); - if (!scope.options) { - scope.options = {}; - } - scope.datasets = $parse(scope.datasets)(scope); - if (scope.datasets && !angular.isArray(scope.datasets)) { - scope.datasets = [scope.datasets]; - } - - var eventBus = new EventBus({el: element}); - var autocomplete = null; - - // reinitialization watchers - scope.$watch('options', initialize); - if (angular.isArray(scope.datasets)) { - scope.$watchCollection('datasets', initialize); - } else { - scope.$watch('datasets', initialize); - } - - // init function - function initialize() { - if (autocomplete) { - autocomplete.destroy(); - } - autocomplete = new Typeahead({ - input: element, - dropdownMenuContainer: scope.options.dropdownMenuContainer, - eventBus: eventBus, - hint: scope.options.hint, - minLength: scope.options.minLength, - autoselect: scope.options.autoselect, - autoselectOnBlur: scope.options.autoselectOnBlur, - tabAutocomplete: scope.options.tabAutocomplete, - openOnFocus: scope.options.openOnFocus, - templates: scope.options.templates, - debug: scope.options.debug, - clearOnSelected: scope.options.clearOnSelected, - cssClasses: scope.options.cssClasses, - datasets: scope.datasets, - keyboardShortcuts: scope.options.keyboardShortcuts, - appendTo: scope.options.appendTo, - autoWidth: scope.options.autoWidth - }); - } - - // Propagate the selected event - element.bind('autocomplete:selected', function(object, suggestion, dataset) { - scope.$emit('autocomplete:selected', suggestion, dataset); - }); - - // Propagate the autocompleted event - element.bind('autocomplete:autocompleted', function(object, suggestion, dataset) { - scope.$emit('autocomplete:autocompleted', suggestion, dataset); - }); - - // Propagate the opened event - element.bind('autocomplete:opened', function() { - scope.$emit('autocomplete:opened'); - }); - - // Propagate the closed event - element.bind('autocomplete:closed', function() { - scope.$emit('autocomplete:closed'); - }); - - // Propagate the cursorchanged event - element.bind('autocomplete:cursorchanged', function(event, suggestion, dataset) { - scope.$emit('autocomplete:cursorchanged', event, suggestion, dataset); - }); - } - }; - }]); - - -/***/ }, -/* 2 */ -/***/ function(module, exports) { - - module.exports = angular; - -/***/ }, -/* 3 */ -/***/ function(module, exports) { - - 'use strict'; - - module.exports = { - element: null - }; - - -/***/ }, -/* 4 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var DOM = __webpack_require__(3); - - function escapeRegExp(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); - } - - module.exports = { - // those methods are implemented differently - // depending on which build it is, using - // $... or angular... or Zepto... or require(...) - isArray: null, - isFunction: null, - isObject: null, - bind: null, - each: null, - map: null, - mixin: null, - - isMsie: function(agentString) { - if (agentString === undefined) { agentString = navigator.userAgent; } - // from https://github.com/ded/bowser/blob/master/bowser.js - if ((/(msie|trident)/i).test(agentString)) { - var match = agentString.match(/(msie |rv:)(\d+(.\d+)?)/i); - if (match) { return match[2]; } - } - return false; - }, - - // http://stackoverflow.com/a/6969486 - escapeRegExChars: function(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); - }, - - isNumber: function(obj) { return typeof obj === 'number'; }, - - toStr: function toStr(s) { - return s === undefined || s === null ? '' : s + ''; - }, - - cloneDeep: function cloneDeep(obj) { - var clone = this.mixin({}, obj); - var self = this; - this.each(clone, function(value, key) { - if (value) { - if (self.isArray(value)) { - clone[key] = [].concat(value); - } else if (self.isObject(value)) { - clone[key] = self.cloneDeep(value); - } - } - }); - return clone; - }, - - error: function(msg) { - throw new Error(msg); - }, - - every: function(obj, test) { - var result = true; - if (!obj) { - return result; - } - this.each(obj, function(val, key) { - if (result) { - result = test.call(null, val, key, obj) && result; - } - }); - return !!result; - }, - - any: function(obj, test) { - var found = false; - if (!obj) { - return found; - } - this.each(obj, function(val, key) { - if (test.call(null, val, key, obj)) { - found = true; - return false; - } - }); - return found; - }, - - getUniqueId: (function() { - var counter = 0; - return function() { return counter++; }; - })(), - - templatify: function templatify(obj) { - if (this.isFunction(obj)) { - return obj; - } - var $template = DOM.element(obj); - if ($template.prop('tagName') === 'SCRIPT') { - return function template() { return $template.text(); }; - } - return function template() { return String(obj); }; - }, - - defer: function(fn) { setTimeout(fn, 0); }, - - noop: function() {}, - - formatPrefix: function(prefix, noPrefix) { - return noPrefix ? '' : prefix + '-'; - }, - - className: function(prefix, clazz, skipDot) { - return (skipDot ? '' : '.') + prefix + clazz; - }, - - escapeHighlightedString: function(str, highlightPreTag, highlightPostTag) { - highlightPreTag = highlightPreTag || ''; - var pre = document.createElement('div'); - pre.appendChild(document.createTextNode(highlightPreTag)); - - highlightPostTag = highlightPostTag || ''; - var post = document.createElement('div'); - post.appendChild(document.createTextNode(highlightPostTag)); - - var div = document.createElement('div'); - div.appendChild(document.createTextNode(str)); - return div.innerHTML - .replace(RegExp(escapeRegExp(pre.innerHTML), 'g'), highlightPreTag) - .replace(RegExp(escapeRegExp(post.innerHTML), 'g'), highlightPostTag); - } - }; - - -/***/ }, -/* 5 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var namespace = 'autocomplete:'; - - var _ = __webpack_require__(4); - var DOM = __webpack_require__(3); - - // constructor - // ----------- - - function EventBus(o) { - if (!o || !o.el) { - _.error('EventBus initialized without el'); - } - - this.$el = DOM.element(o.el); - } - - // instance methods - // ---------------- - - _.mixin(EventBus.prototype, { - - // ### public - - trigger: function(type, suggestion, dataset, context) { - var event = _.Event(namespace + type); - this.$el.trigger(event, [suggestion, dataset, context]); - return event; - } - }); - - module.exports = EventBus; - - -/***/ }, -/* 6 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var attrsKey = 'aaAttrs'; - - var _ = __webpack_require__(4); - var DOM = __webpack_require__(3); - var EventBus = __webpack_require__(5); - var Input = __webpack_require__(7); - var Dropdown = __webpack_require__(16); - var html = __webpack_require__(18); - var css = __webpack_require__(19); - - // constructor - // ----------- - - // THOUGHT: what if datasets could dynamically be added/removed? - function Typeahead(o) { - var $menu; - var $hint; - - o = o || {}; - - if (!o.input) { - _.error('missing input'); - } - - this.isActivated = false; - this.debug = !!o.debug; - this.autoselect = !!o.autoselect; - this.autoselectOnBlur = !!o.autoselectOnBlur; - this.openOnFocus = !!o.openOnFocus; - this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; - this.autoWidth = (o.autoWidth === undefined) ? true : !!o.autoWidth; - this.clearOnSelected = !!o.clearOnSelected; - this.tabAutocomplete = (o.tabAutocomplete === undefined) ? true : !!o.tabAutocomplete; - - o.hint = !!o.hint; - - if (o.hint && o.appendTo) { - throw new Error('[autocomplete.js] hint and appendTo options can\'t be used at the same time'); - } - - this.css = o.css = _.mixin({}, css, o.appendTo ? css.appendTo : {}); - this.cssClasses = o.cssClasses = _.mixin({}, css.defaultClasses, o.cssClasses || {}); - this.cssClasses.prefix = - o.cssClasses.formattedPrefix = _.formatPrefix(this.cssClasses.prefix, this.cssClasses.noPrefix); - this.listboxId = o.listboxId = [this.cssClasses.root, 'listbox', _.getUniqueId()].join('-'); - - var domElts = buildDom(o); - - this.$node = domElts.wrapper; - var $input = this.$input = domElts.input; - $menu = domElts.menu; - $hint = domElts.hint; - - if (o.dropdownMenuContainer) { - DOM.element(o.dropdownMenuContainer) - .css('position', 'relative') // ensure the container has a relative position - .append($menu.css('top', '0')); // override the top: 100% - } - - // #705: if there's scrollable overflow, ie doesn't support - // blur cancellations when the scrollbar is clicked - // - // #351: preventDefault won't cancel blurs in ie <= 8 - $input.on('blur.aa', function($e) { - var active = document.activeElement; - if (_.isMsie() && ($menu[0] === active || $menu[0].contains(active))) { - $e.preventDefault(); - // stop immediate in order to prevent Input#_onBlur from - // getting exectued - $e.stopImmediatePropagation(); - _.defer(function() { $input.focus(); }); - } - }); - - // #351: prevents input blur due to clicks within dropdown menu - $menu.on('mousedown.aa', function($e) { $e.preventDefault(); }); - - this.eventBus = o.eventBus || new EventBus({el: $input}); - - this.dropdown = new Typeahead.Dropdown({ - appendTo: o.appendTo, - wrapper: this.$node, - menu: $menu, - datasets: o.datasets, - templates: o.templates, - cssClasses: o.cssClasses, - minLength: this.minLength - }) - .onSync('suggestionClicked', this._onSuggestionClicked, this) - .onSync('cursorMoved', this._onCursorMoved, this) - .onSync('cursorRemoved', this._onCursorRemoved, this) - .onSync('opened', this._onOpened, this) - .onSync('closed', this._onClosed, this) - .onSync('shown', this._onShown, this) - .onSync('empty', this._onEmpty, this) - .onSync('redrawn', this._onRedrawn, this) - .onAsync('datasetRendered', this._onDatasetRendered, this); - - this.input = new Typeahead.Input({input: $input, hint: $hint}) - .onSync('focused', this._onFocused, this) - .onSync('blurred', this._onBlurred, this) - .onSync('enterKeyed', this._onEnterKeyed, this) - .onSync('tabKeyed', this._onTabKeyed, this) - .onSync('escKeyed', this._onEscKeyed, this) - .onSync('upKeyed', this._onUpKeyed, this) - .onSync('downKeyed', this._onDownKeyed, this) - .onSync('leftKeyed', this._onLeftKeyed, this) - .onSync('rightKeyed', this._onRightKeyed, this) - .onSync('queryChanged', this._onQueryChanged, this) - .onSync('whitespaceChanged', this._onWhitespaceChanged, this); - - this._bindKeyboardShortcuts(o); - - this._setLanguageDirection(); - } - - // instance methods - // ---------------- - - _.mixin(Typeahead.prototype, { - // ### private - - _bindKeyboardShortcuts: function(options) { - if (!options.keyboardShortcuts) { - return; - } - var $input = this.$input; - var keyboardShortcuts = []; - _.each(options.keyboardShortcuts, function(key) { - if (typeof key === 'string') { - key = key.toUpperCase().charCodeAt(0); - } - keyboardShortcuts.push(key); - }); - DOM.element(document).keydown(function(event) { - var elt = (event.target || event.srcElement); - var tagName = elt.tagName; - if (elt.isContentEditable || tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA') { - // already in an input - return; - } - - var which = event.which || event.keyCode; - if (keyboardShortcuts.indexOf(which) === -1) { - // not the right shortcut - return; - } - - $input.focus(); - event.stopPropagation(); - event.preventDefault(); - }); - }, - - _onSuggestionClicked: function onSuggestionClicked(type, $el) { - var datum; - var context = {selectionMethod: 'click'}; - - if (datum = this.dropdown.getDatumForSuggestion($el)) { - this._select(datum, context); - } - }, - - _onCursorMoved: function onCursorMoved(event, updateInput) { - var datum = this.dropdown.getDatumForCursor(); - var currentCursorId = this.dropdown.getCurrentCursor().attr('id'); - this.input.setActiveDescendant(currentCursorId); - - if (datum) { - if (updateInput) { - this.input.setInputValue(datum.value, true); - } - - this.eventBus.trigger('cursorchanged', datum.raw, datum.datasetName); - } - }, - - _onCursorRemoved: function onCursorRemoved() { - this.input.resetInputValue(); - this._updateHint(); - this.eventBus.trigger('cursorremoved'); - }, - - _onDatasetRendered: function onDatasetRendered() { - this._updateHint(); - - this.eventBus.trigger('updated'); - }, - - _onOpened: function onOpened() { - this._updateHint(); - this.input.expand(); - - this.eventBus.trigger('opened'); - }, - - _onEmpty: function onEmpty() { - this.eventBus.trigger('empty'); - }, - - _onRedrawn: function onRedrawn() { - this.$node.css('top', 0 + 'px'); - this.$node.css('left', 0 + 'px'); - - var inputRect = this.$input[0].getBoundingClientRect(); - - if (this.autoWidth) { - this.$node.css('width', inputRect.width + 'px'); - } - - var wrapperRect = this.$node[0].getBoundingClientRect(); - - var top = inputRect.bottom - wrapperRect.top; - this.$node.css('top', top + 'px'); - var left = inputRect.left - wrapperRect.left; - this.$node.css('left', left + 'px'); - - this.eventBus.trigger('redrawn'); - }, - - _onShown: function onShown() { - this.eventBus.trigger('shown'); - if (this.autoselect) { - this.dropdown.cursorTopSuggestion(); - } - }, - - _onClosed: function onClosed() { - this.input.clearHint(); - this.input.removeActiveDescendant(); - this.input.collapse(); - - this.eventBus.trigger('closed'); - }, - - _onFocused: function onFocused() { - this.isActivated = true; - - if (this.openOnFocus) { - var query = this.input.getQuery(); - if (query.length >= this.minLength) { - this.dropdown.update(query); - } else { - this.dropdown.empty(); - } - - this.dropdown.open(); - } - }, - - _onBlurred: function onBlurred() { - var cursorDatum; - var topSuggestionDatum; - - cursorDatum = this.dropdown.getDatumForCursor(); - topSuggestionDatum = this.dropdown.getDatumForTopSuggestion(); - var context = {selectionMethod: 'blur'}; - - if (!this.debug) { - if (this.autoselectOnBlur && cursorDatum) { - this._select(cursorDatum, context); - } else if (this.autoselectOnBlur && topSuggestionDatum) { - this._select(topSuggestionDatum, context); - } else { - this.isActivated = false; - this.dropdown.empty(); - this.dropdown.close(); - } - } - }, - - _onEnterKeyed: function onEnterKeyed(type, $e) { - var cursorDatum; - var topSuggestionDatum; - - cursorDatum = this.dropdown.getDatumForCursor(); - topSuggestionDatum = this.dropdown.getDatumForTopSuggestion(); - var context = {selectionMethod: 'enterKey'}; - - if (cursorDatum) { - this._select(cursorDatum, context); - $e.preventDefault(); - } else if (this.autoselect && topSuggestionDatum) { - this._select(topSuggestionDatum, context); - $e.preventDefault(); - } - }, - - _onTabKeyed: function onTabKeyed(type, $e) { - if (!this.tabAutocomplete) { - // Closing the dropdown enables further tabbing - this.dropdown.close(); - return; - } - - var datum; - var context = {selectionMethod: 'tabKey'}; - - if (datum = this.dropdown.getDatumForCursor()) { - this._select(datum, context); - $e.preventDefault(); - } else { - this._autocomplete(true); - } - }, - - _onEscKeyed: function onEscKeyed() { - this.dropdown.close(); - this.input.resetInputValue(); - }, - - _onUpKeyed: function onUpKeyed() { - var query = this.input.getQuery(); - - if (this.dropdown.isEmpty && query.length >= this.minLength) { - this.dropdown.update(query); - } else { - this.dropdown.moveCursorUp(); - } - - this.dropdown.open(); - }, - - _onDownKeyed: function onDownKeyed() { - var query = this.input.getQuery(); - - if (this.dropdown.isEmpty && query.length >= this.minLength) { - this.dropdown.update(query); - } else { - this.dropdown.moveCursorDown(); - } - - this.dropdown.open(); - }, - - _onLeftKeyed: function onLeftKeyed() { - if (this.dir === 'rtl') { - this._autocomplete(); - } - }, - - _onRightKeyed: function onRightKeyed() { - if (this.dir === 'ltr') { - this._autocomplete(); - } - }, - - _onQueryChanged: function onQueryChanged(e, query) { - this.input.clearHintIfInvalid(); - - if (query.length >= this.minLength) { - this.dropdown.update(query); - } else { - this.dropdown.empty(); - } - - this.dropdown.open(); - this._setLanguageDirection(); - }, - - _onWhitespaceChanged: function onWhitespaceChanged() { - this._updateHint(); - this.dropdown.open(); - }, - - _setLanguageDirection: function setLanguageDirection() { - var dir = this.input.getLanguageDirection(); - - if (this.dir !== dir) { - this.dir = dir; - this.$node.css('direction', dir); - this.dropdown.setLanguageDirection(dir); - } - }, - - _updateHint: function updateHint() { - var datum; - var val; - var query; - var escapedQuery; - var frontMatchRegEx; - var match; - - datum = this.dropdown.getDatumForTopSuggestion(); - - if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) { - val = this.input.getInputValue(); - query = Input.normalizeQuery(val); - escapedQuery = _.escapeRegExChars(query); - - // match input value, then capture trailing text - frontMatchRegEx = new RegExp('^(?:' + escapedQuery + ')(.+$)', 'i'); - match = frontMatchRegEx.exec(datum.value); - - // clear hint if there's no trailing text - if (match) { - this.input.setHint(val + match[1]); - } else { - this.input.clearHint(); - } - } else { - this.input.clearHint(); - } - }, - - _autocomplete: function autocomplete(laxCursor) { - var hint; - var query; - var isCursorAtEnd; - var datum; - - hint = this.input.getHint(); - query = this.input.getQuery(); - isCursorAtEnd = laxCursor || this.input.isCursorAtEnd(); - - if (hint && query !== hint && isCursorAtEnd) { - datum = this.dropdown.getDatumForTopSuggestion(); - if (datum) { - this.input.setInputValue(datum.value); - } - - this.eventBus.trigger('autocompleted', datum.raw, datum.datasetName); - } - }, - - _select: function select(datum, context) { - if (typeof datum.value !== 'undefined') { - this.input.setQuery(datum.value); - } - if (this.clearOnSelected) { - this.setVal(''); - } else { - this.input.setInputValue(datum.value, true); - } - - this._setLanguageDirection(); - - var event = this.eventBus.trigger('selected', datum.raw, datum.datasetName, context); - if (event.isDefaultPrevented() === false) { - this.dropdown.close(); - - // #118: allow click event to bubble up to the body before removing - // the suggestions otherwise we break event delegation - _.defer(_.bind(this.dropdown.empty, this.dropdown)); - } - }, - - // ### public - - open: function open() { - // if the menu is not activated yet, we need to update - // the underlying dropdown menu to trigger the search - // otherwise we're not gonna see anything - if (!this.isActivated) { - var query = this.input.getInputValue(); - if (query.length >= this.minLength) { - this.dropdown.update(query); - } else { - this.dropdown.empty(); - } - } - this.dropdown.open(); - }, - - close: function close() { - this.dropdown.close(); - }, - - setVal: function setVal(val) { - // expect val to be a string, so be safe, and coerce - val = _.toStr(val); - - if (this.isActivated) { - this.input.setInputValue(val); - } else { - this.input.setQuery(val); - this.input.setInputValue(val, true); - } - - this._setLanguageDirection(); - }, - - getVal: function getVal() { - return this.input.getQuery(); - }, - - destroy: function destroy() { - this.input.destroy(); - this.dropdown.destroy(); - - destroyDomStructure(this.$node, this.cssClasses); - - this.$node = null; - }, - - getWrapper: function getWrapper() { - return this.dropdown.$container[0]; - } - }); - - function buildDom(options) { - var $input; - var $wrapper; - var $dropdown; - var $hint; - - $input = DOM.element(options.input); - $wrapper = DOM - .element(html.wrapper.replace('%ROOT%', options.cssClasses.root)) - .css(options.css.wrapper); - - // override the display property with the table-cell value - // if the parent element is a table and the original input was a block - // -> https://github.com/algolia/autocomplete.js/issues/16 - if (!options.appendTo && $input.css('display') === 'block' && $input.parent().css('display') === 'table') { - $wrapper.css('display', 'table-cell'); - } - var dropdownHtml = html.dropdown. - replace('%PREFIX%', options.cssClasses.prefix). - replace('%DROPDOWN_MENU%', options.cssClasses.dropdownMenu); - $dropdown = DOM.element(dropdownHtml) - .css(options.css.dropdown) - .attr({ - role: 'listbox', - id: options.listboxId - }); - if (options.templates && options.templates.dropdownMenu) { - $dropdown.html(_.templatify(options.templates.dropdownMenu)()); - } - $hint = $input.clone().css(options.css.hint).css(getBackgroundStyles($input)); - - $hint - .val('') - .addClass(_.className(options.cssClasses.prefix, options.cssClasses.hint, true)) - .removeAttr('id name placeholder required') - .prop('readonly', true) - .attr({ - 'aria-hidden': 'true', - autocomplete: 'off', - spellcheck: 'false', - tabindex: -1 - }); - if ($hint.removeData) { - $hint.removeData(); - } - - // store the original values of the attrs that get modified - // so modifications can be reverted on destroy - $input.data(attrsKey, { - 'aria-autocomplete': $input.attr('aria-autocomplete'), - 'aria-expanded': $input.attr('aria-expanded'), - 'aria-owns': $input.attr('aria-owns'), - autocomplete: $input.attr('autocomplete'), - dir: $input.attr('dir'), - role: $input.attr('role'), - spellcheck: $input.attr('spellcheck'), - style: $input.attr('style'), - type: $input.attr('type') - }); - - $input - .addClass(_.className(options.cssClasses.prefix, options.cssClasses.input, true)) - .attr({ - autocomplete: 'off', - spellcheck: false, - - // Accessibility features - // Give the field a presentation of a "select". - // Combobox is the combined presentation of a single line textfield - // with a listbox popup. - // https://www.w3.org/WAI/PF/aria/roles#combobox - role: 'combobox', - // Let the screen reader know the field has an autocomplete - // feature to it. - 'aria-autocomplete': (options.datasets && - options.datasets[0] && options.datasets[0].displayKey ? 'both' : 'list'), - // Indicates whether the dropdown it controls is currently expanded or collapsed - 'aria-expanded': 'false', - 'aria-label': options.ariaLabel, - // Explicitly point to the listbox, - // which is a list of suggestions (aka options) - 'aria-owns': options.listboxId - }) - .css(options.hint ? options.css.input : options.css.inputWithNoHint); - - // ie7 does not like it when dir is set to auto - try { - if (!$input.attr('dir')) { - $input.attr('dir', 'auto'); - } - } catch (e) { - // ignore - } - - $wrapper = options.appendTo - ? $wrapper.appendTo(DOM.element(options.appendTo).eq(0)).eq(0) - : $input.wrap($wrapper).parent(); - - $wrapper - .prepend(options.hint ? $hint : null) - .append($dropdown); - - return { - wrapper: $wrapper, - input: $input, - hint: $hint, - menu: $dropdown - }; - } - - function getBackgroundStyles($el) { - return { - backgroundAttachment: $el.css('background-attachment'), - backgroundClip: $el.css('background-clip'), - backgroundColor: $el.css('background-color'), - backgroundImage: $el.css('background-image'), - backgroundOrigin: $el.css('background-origin'), - backgroundPosition: $el.css('background-position'), - backgroundRepeat: $el.css('background-repeat'), - backgroundSize: $el.css('background-size') - }; - } - - function destroyDomStructure($node, cssClasses) { - var $input = $node.find(_.className(cssClasses.prefix, cssClasses.input)); - - // need to remove attrs that weren't previously defined and - // revert attrs that originally had a value - _.each($input.data(attrsKey), function(val, key) { - if (val === undefined) { - $input.removeAttr(key); - } else { - $input.attr(key, val); - } - }); - - $input - .detach() - .removeClass(_.className(cssClasses.prefix, cssClasses.input, true)) - .insertAfter($node); - if ($input.removeData) { - $input.removeData(attrsKey); - } - - $node.remove(); - } - - Typeahead.Dropdown = Dropdown; - Typeahead.Input = Input; - Typeahead.sources = __webpack_require__(20); - - module.exports = Typeahead; - - -/***/ }, -/* 7 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var specialKeyCodeMap; - - specialKeyCodeMap = { - 9: 'tab', - 27: 'esc', - 37: 'left', - 39: 'right', - 13: 'enter', - 38: 'up', - 40: 'down' - }; - - var _ = __webpack_require__(4); - var DOM = __webpack_require__(3); - var EventEmitter = __webpack_require__(8); - - // constructor - // ----------- - - function Input(o) { - var that = this; - var onBlur; - var onFocus; - var onKeydown; - var onInput; - - o = o || {}; - - if (!o.input) { - _.error('input is missing'); - } - - // bound functions - onBlur = _.bind(this._onBlur, this); - onFocus = _.bind(this._onFocus, this); - onKeydown = _.bind(this._onKeydown, this); - onInput = _.bind(this._onInput, this); - - this.$hint = DOM.element(o.hint); - this.$input = DOM.element(o.input) - .on('blur.aa', onBlur) - .on('focus.aa', onFocus) - .on('keydown.aa', onKeydown); - - // if no hint, noop all the hint related functions - if (this.$hint.length === 0) { - this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; - } - - // ie7 and ie8 don't support the input event - // ie9 doesn't fire the input event when characters are removed - // not sure if ie10 is compatible - if (!_.isMsie()) { - this.$input.on('input.aa', onInput); - } else { - this.$input.on('keydown.aa keypress.aa cut.aa paste.aa', function($e) { - // if a special key triggered this, ignore it - if (specialKeyCodeMap[$e.which || $e.keyCode]) { - return; - } - - // give the browser a chance to update the value of the input - // before checking to see if the query changed - _.defer(_.bind(that._onInput, that, $e)); - }); - } - - // the query defaults to whatever the value of the input is - // on initialization, it'll most likely be an empty string - this.query = this.$input.val(); - - // helps with calculating the width of the input's value - this.$overflowHelper = buildOverflowHelper(this.$input); - } - - // static methods - // -------------- - - Input.normalizeQuery = function(str) { - // strips leading whitespace and condenses all whitespace - return (str || '').replace(/^\s*/g, '').replace(/\s{2,}/g, ' '); - }; - - // instance methods - // ---------------- - - _.mixin(Input.prototype, EventEmitter, { - - // ### private - - _onBlur: function onBlur() { - this.resetInputValue(); - this.$input.removeAttr('aria-activedescendant'); - this.trigger('blurred'); - }, - - _onFocus: function onFocus() { - this.trigger('focused'); - }, - - _onKeydown: function onKeydown($e) { - // which is normalized and consistent (but not for ie) - var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; - - this._managePreventDefault(keyName, $e); - if (keyName && this._shouldTrigger(keyName, $e)) { - this.trigger(keyName + 'Keyed', $e); - } - }, - - _onInput: function onInput() { - this._checkInputValue(); - }, - - _managePreventDefault: function managePreventDefault(keyName, $e) { - var preventDefault; - var hintValue; - var inputValue; - - switch (keyName) { - case 'tab': - hintValue = this.getHint(); - inputValue = this.getInputValue(); - - preventDefault = hintValue && - hintValue !== inputValue && - !withModifier($e); - break; - - case 'up': - case 'down': - preventDefault = !withModifier($e); - break; - - default: - preventDefault = false; - } - - if (preventDefault) { - $e.preventDefault(); - } - }, - - _shouldTrigger: function shouldTrigger(keyName, $e) { - var trigger; - - switch (keyName) { - case 'tab': - trigger = !withModifier($e); - break; - - default: - trigger = true; - } - - return trigger; - }, - - _checkInputValue: function checkInputValue() { - var inputValue; - var areEquivalent; - var hasDifferentWhitespace; - - inputValue = this.getInputValue(); - areEquivalent = areQueriesEquivalent(inputValue, this.query); - hasDifferentWhitespace = areEquivalent && this.query ? - this.query.length !== inputValue.length : false; - - this.query = inputValue; - - if (!areEquivalent) { - this.trigger('queryChanged', this.query); - } else if (hasDifferentWhitespace) { - this.trigger('whitespaceChanged', this.query); - } - }, - - // ### public - - focus: function focus() { - this.$input.focus(); - }, - - blur: function blur() { - this.$input.blur(); - }, - - getQuery: function getQuery() { - return this.query; - }, - - setQuery: function setQuery(query) { - this.query = query; - }, - - getInputValue: function getInputValue() { - return this.$input.val(); - }, - - setInputValue: function setInputValue(value, silent) { - if (typeof value === 'undefined') { - value = this.query; - } - this.$input.val(value); - - // silent prevents any additional events from being triggered - if (silent) { - this.clearHint(); - } else { - this._checkInputValue(); - } - }, - - expand: function expand() { - this.$input.attr('aria-expanded', 'true'); - }, - - collapse: function collapse() { - this.$input.attr('aria-expanded', 'false'); - }, - - setActiveDescendant: function setActiveDescendant(activedescendantId) { - this.$input.attr('aria-activedescendant', activedescendantId); - }, - - removeActiveDescendant: function removeActiveDescendant() { - this.$input.removeAttr('aria-activedescendant'); - }, - - resetInputValue: function resetInputValue() { - this.setInputValue(this.query, true); - }, - - getHint: function getHint() { - return this.$hint.val(); - }, - - setHint: function setHint(value) { - this.$hint.val(value); - }, - - clearHint: function clearHint() { - this.setHint(''); - }, - - clearHintIfInvalid: function clearHintIfInvalid() { - var val; - var hint; - var valIsPrefixOfHint; - var isValid; - - val = this.getInputValue(); - hint = this.getHint(); - valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; - isValid = val !== '' && valIsPrefixOfHint && !this.hasOverflow(); - - if (!isValid) { - this.clearHint(); - } - }, - - getLanguageDirection: function getLanguageDirection() { - return (this.$input.css('direction') || 'ltr').toLowerCase(); - }, - - hasOverflow: function hasOverflow() { - // 2 is arbitrary, just picking a small number to handle edge cases - var constraint = this.$input.width() - 2; - - this.$overflowHelper.text(this.getInputValue()); - - return this.$overflowHelper.width() >= constraint; - }, - - isCursorAtEnd: function() { - var valueLength; - var selectionStart; - var range; - - valueLength = this.$input.val().length; - selectionStart = this.$input[0].selectionStart; - - if (_.isNumber(selectionStart)) { - return selectionStart === valueLength; - } else if (document.selection) { - // NOTE: this won't work unless the input has focus, the good news - // is this code should only get called when the input has focus - range = document.selection.createRange(); - range.moveStart('character', -valueLength); - - return valueLength === range.text.length; - } - - return true; - }, - - destroy: function destroy() { - this.$hint.off('.aa'); - this.$input.off('.aa'); - - this.$hint = this.$input = this.$overflowHelper = null; - } - }); - - // helper functions - // ---------------- - - function buildOverflowHelper($input) { - return DOM.element('') - .css({ - // position helper off-screen - position: 'absolute', - visibility: 'hidden', - // avoid line breaks and whitespace collapsing - whiteSpace: 'pre', - // use same font css as input to calculate accurate width - fontFamily: $input.css('font-family'), - fontSize: $input.css('font-size'), - fontStyle: $input.css('font-style'), - fontVariant: $input.css('font-variant'), - fontWeight: $input.css('font-weight'), - wordSpacing: $input.css('word-spacing'), - letterSpacing: $input.css('letter-spacing'), - textIndent: $input.css('text-indent'), - textRendering: $input.css('text-rendering'), - textTransform: $input.css('text-transform') - }) - .insertAfter($input); - } - - function areQueriesEquivalent(a, b) { - return Input.normalizeQuery(a) === Input.normalizeQuery(b); - } - - function withModifier($e) { - return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; - } - - module.exports = Input; - - -/***/ }, -/* 8 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var immediate = __webpack_require__(9); - var splitter = /\s+/; - - module.exports = { - onSync: onSync, - onAsync: onAsync, - off: off, - trigger: trigger - }; - - function on(method, types, cb, context) { - var type; - - if (!cb) { - return this; - } - - types = types.split(splitter); - cb = context ? bindContext(cb, context) : cb; - - this._callbacks = this._callbacks || {}; - - while (type = types.shift()) { - this._callbacks[type] = this._callbacks[type] || {sync: [], async: []}; - this._callbacks[type][method].push(cb); - } - - return this; - } - - function onAsync(types, cb, context) { - return on.call(this, 'async', types, cb, context); - } - - function onSync(types, cb, context) { - return on.call(this, 'sync', types, cb, context); - } - - function off(types) { - var type; - - if (!this._callbacks) { - return this; - } - - types = types.split(splitter); - - while (type = types.shift()) { - delete this._callbacks[type]; - } - - return this; - } - - function trigger(types) { - var type; - var callbacks; - var args; - var syncFlush; - var asyncFlush; - - if (!this._callbacks) { - return this; - } - - types = types.split(splitter); - args = [].slice.call(arguments, 1); - - while ((type = types.shift()) && (callbacks = this._callbacks[type])) { // eslint-disable-line - syncFlush = getFlush(callbacks.sync, this, [type].concat(args)); - asyncFlush = getFlush(callbacks.async, this, [type].concat(args)); - - if (syncFlush()) { - immediate(asyncFlush); - } - } - - return this; - } - - function getFlush(callbacks, context, args) { - return flush; - - function flush() { - var cancelled; - - for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { - // only cancel if the callback explicitly returns false - cancelled = callbacks[i].apply(context, args) === false; - } - - return !cancelled; - } - } - - function bindContext(fn, context) { - return fn.bind ? - fn.bind(context) : - function() { fn.apply(context, [].slice.call(arguments, 0)); }; - } - - -/***/ }, -/* 9 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - var types = [ - __webpack_require__(10), - __webpack_require__(12), - __webpack_require__(13), - __webpack_require__(14), - __webpack_require__(15) - ]; - var draining; - var currentQueue; - var queueIndex = -1; - var queue = []; - var scheduled = false; - function cleanUpNextTick() { - if (!draining || !currentQueue) { - return; - } - draining = false; - if (currentQueue.length) { - queue = currentQueue.concat(queue); - } else { - queueIndex = -1; - } - if (queue.length) { - nextTick(); - } - } - - //named nextTick for less confusing stack traces - function nextTick() { - if (draining) { - return; - } - scheduled = false; - draining = true; - var len = queue.length; - var timeout = setTimeout(cleanUpNextTick); - while (len) { - currentQueue = queue; - queue = []; - while (currentQueue && ++queueIndex < len) { - currentQueue[queueIndex].run(); - } - queueIndex = -1; - len = queue.length; - } - currentQueue = null; - queueIndex = -1; - draining = false; - clearTimeout(timeout); - } - var scheduleDrain; - var i = -1; - var len = types.length; - while (++i < len) { - if (types[i] && types[i].test && types[i].test()) { - scheduleDrain = types[i].install(nextTick); - break; - } - } - // v8 likes predictible objects - function Item(fun, array) { - this.fun = fun; - this.array = array; - } - Item.prototype.run = function () { - var fun = this.fun; - var array = this.array; - switch (array.length) { - case 0: - return fun(); - case 1: - return fun(array[0]); - case 2: - return fun(array[0], array[1]); - case 3: - return fun(array[0], array[1], array[2]); - default: - return fun.apply(null, array); - } - - }; - module.exports = immediate; - function immediate(task) { - var args = new Array(arguments.length - 1); - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - } - queue.push(new Item(task, args)); - if (!scheduled && !draining) { - scheduled = true; - scheduleDrain(); - } - } - - -/***/ }, -/* 10 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(process) {'use strict'; - exports.test = function () { - // Don't get fooled by e.g. browserify environments. - return (typeof process !== 'undefined') && !process.browser; - }; - - exports.install = function (func) { - return function () { - process.nextTick(func); - }; - }; - - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(11))) - -/***/ }, -/* 11 */ -/***/ function(module, exports) { - - // shim for using process in browser - var process = module.exports = {}; - - // cached from whatever global is present so that test runners that stub it - // don't break things. But we need to wrap it in a try catch in case it is - // wrapped in strict mode code which doesn't define any globals. It's inside a - // function because try/catches deoptimize in certain engines. - - var cachedSetTimeout; - var cachedClearTimeout; - - function defaultSetTimout() { - throw new Error('setTimeout has not been defined'); - } - function defaultClearTimeout () { - throw new Error('clearTimeout has not been defined'); - } - (function () { - try { - if (typeof setTimeout === 'function') { - cachedSetTimeout = setTimeout; - } else { - cachedSetTimeout = defaultSetTimout; - } - } catch (e) { - cachedSetTimeout = defaultSetTimout; - } - try { - if (typeof clearTimeout === 'function') { - cachedClearTimeout = clearTimeout; - } else { - cachedClearTimeout = defaultClearTimeout; - } - } catch (e) { - cachedClearTimeout = defaultClearTimeout; - } - } ()) - function runTimeout(fun) { - if (cachedSetTimeout === setTimeout) { - //normal enviroments in sane situations - return setTimeout(fun, 0); - } - // if setTimeout wasn't available but was latter defined - if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { - cachedSetTimeout = setTimeout; - return setTimeout(fun, 0); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedSetTimeout(fun, 0); - } catch(e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedSetTimeout.call(null, fun, 0); - } catch(e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error - return cachedSetTimeout.call(this, fun, 0); - } - } - - - } - function runClearTimeout(marker) { - if (cachedClearTimeout === clearTimeout) { - //normal enviroments in sane situations - return clearTimeout(marker); - } - // if clearTimeout wasn't available but was latter defined - if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { - cachedClearTimeout = clearTimeout; - return clearTimeout(marker); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedClearTimeout(marker); - } catch (e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedClearTimeout.call(null, marker); - } catch (e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. - // Some versions of I.E. have different rules for clearTimeout vs setTimeout - return cachedClearTimeout.call(this, marker); - } - } - - - - } - var queue = []; - var draining = false; - var currentQueue; - var queueIndex = -1; - - function cleanUpNextTick() { - if (!draining || !currentQueue) { - return; - } - draining = false; - if (currentQueue.length) { - queue = currentQueue.concat(queue); - } else { - queueIndex = -1; - } - if (queue.length) { - drainQueue(); - } - } - - function drainQueue() { - if (draining) { - return; - } - var timeout = runTimeout(cleanUpNextTick); - draining = true; - - var len = queue.length; - while(len) { - currentQueue = queue; - queue = []; - while (++queueIndex < len) { - if (currentQueue) { - currentQueue[queueIndex].run(); - } - } - queueIndex = -1; - len = queue.length; - } - currentQueue = null; - draining = false; - runClearTimeout(timeout); - } - - process.nextTick = function (fun) { - var args = new Array(arguments.length - 1); - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - } - queue.push(new Item(fun, args)); - if (queue.length === 1 && !draining) { - runTimeout(drainQueue); - } - }; - - // v8 likes predictible objects - function Item(fun, array) { - this.fun = fun; - this.array = array; - } - Item.prototype.run = function () { - this.fun.apply(null, this.array); - }; - process.title = 'browser'; - process.browser = true; - process.env = {}; - process.argv = []; - process.version = ''; // empty string to avoid regexp issues - process.versions = {}; - - function noop() {} - - process.on = noop; - process.addListener = noop; - process.once = noop; - process.off = noop; - process.removeListener = noop; - process.removeAllListeners = noop; - process.emit = noop; - - process.binding = function (name) { - throw new Error('process.binding is not supported'); - }; - - process.cwd = function () { return '/' }; - process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); - }; - process.umask = function() { return 0; }; - - -/***/ }, -/* 12 */ -/***/ function(module, exports) { - - /* WEBPACK VAR INJECTION */(function(global) {'use strict'; - //based off rsvp https://github.com/tildeio/rsvp.js - //license https://github.com/tildeio/rsvp.js/blob/master/LICENSE - //https://github.com/tildeio/rsvp.js/blob/master/lib/rsvp/asap.js - - var Mutation = global.MutationObserver || global.WebKitMutationObserver; - - exports.test = function () { - return Mutation; - }; - - exports.install = function (handle) { - var called = 0; - var observer = new Mutation(handle); - var element = global.document.createTextNode(''); - observer.observe(element, { - characterData: true - }); - return function () { - element.data = (called = ++called % 2); - }; - }; - /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) - -/***/ }, -/* 13 */ -/***/ function(module, exports) { - - /* WEBPACK VAR INJECTION */(function(global) {'use strict'; - - exports.test = function () { - if (global.setImmediate) { - // we can only get here in IE10 - // which doesn't handel postMessage well - return false; - } - return typeof global.MessageChannel !== 'undefined'; - }; - - exports.install = function (func) { - var channel = new global.MessageChannel(); - channel.port1.onmessage = func; - return function () { - channel.port2.postMessage(0); - }; - }; - /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) - -/***/ }, -/* 14 */ -/***/ function(module, exports) { - - /* WEBPACK VAR INJECTION */(function(global) {'use strict'; - - exports.test = function () { - return 'document' in global && 'onreadystatechange' in global.document.createElement('script'); - }; - - exports.install = function (handle) { - return function () { - - // Create a - - - - diff --git a/examples/basic_angular.html b/examples/basic_angular.html deleted file mode 100644 index 101bb5eb6a..0000000000 --- a/examples/basic_angular.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - -
-
-
- -
-
-
- - - - - - - - diff --git a/examples/basic_jquery.html b/examples/basic_jquery.html deleted file mode 100644 index cd17b5aa2f..0000000000 --- a/examples/basic_jquery.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - -
-
-
-
-

Basic example

- -
-
-
-
- - - - - - - diff --git a/examples/github-notification-filters/README.md b/examples/github-notification-filters/README.md new file mode 100644 index 0000000000..df8dc2e9a7 --- /dev/null +++ b/examples/github-notification-filters/README.md @@ -0,0 +1,34 @@ +# Autocomplete GitHub notification filters example + +This example shows how to use Autocomplete with the [Tags plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/) to reproduce the [GitHub notification filtering panel](https://github.com/notifications). + +

A capture of the GitHub notification filters example

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/github-notification-filters) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-github-notification-filters start +``` + +Alternatively, you may use npm: + +```sh +cd examples/github-notification-filters +npm install +npm start +``` + +Open to see your app. diff --git a/examples/github-notification-filters/app.tsx b/examples/github-notification-filters/app.tsx new file mode 100644 index 0000000000..8918e61887 --- /dev/null +++ b/examples/github-notification-filters/app.tsx @@ -0,0 +1,242 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { autocomplete, AutocompleteSource } from '@algolia/autocomplete-js'; +import { createTagsPlugin, Tag } from '@algolia/autocomplete-plugin-tags'; +import { h, render } from 'preact'; +import qs from 'qs'; + +import { + FilterHeader, + PanelLayout, + PostfixItem, + PrefixItem, + QueryItem, + TagItem, +} from './components'; +import { items } from './items'; +import { AutocompleteItem, NotificationFilter } from './types'; +import { groupBy } from './utils'; + +import '@algolia/autocomplete-theme-classic'; + +const queryParameters = qs.parse(location.search, { ignoreQueryPrefix: true }); + +const initialTags = Object.entries(queryParameters).flatMap(([token, values]) => + (values as string[]).map((value) => ({ + token, + value, + label: `${token}:${value}`, + attribute: items.find((item) => item.token === token).attribute, + })) +); + +const tagsPlugin = createTagsPlugin({ + initialTags, + getTagsSubscribers() { + return [ + { + sourceId: 'postfixes', + getTag({ item }) { + return { + label: `${item.token}:${item.label}`, + token: item.token, + value: item.label, + attribute: item.attribute, + }; + }, + }, + ]; + }, + transformSource() { + return undefined; + }, + onChange({ tags }) { + onTagsChange(tags); + }, +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Filter notifications', + openOnFocus: true, + defaultActiveItemId: 0, + plugins: [tagsPlugin], + detachedMediaQuery: 'none', + getSources({ query, state }) { + const [prefix, postfix] = splitQuery(query); + const prefixes = items.filter(({ token }) => token.startsWith(prefix)); + const tags = state.context.tagsPlugin.tags || []; + const tagsByToken = groupBy(tags, (tag) => tag.token); + + const allTags = getAlltags(tags, query); + const showQuerySource = allTags.length > 0 && prefixes.length > 0; + const showPrefixesSource = typeof postfix !== 'string'; + const showPostfixesSource = typeof postfix === 'string'; + + const querySource = { + sourceId: 'query', + onSelect() { + const params = Object.fromEntries( + Object.entries(groupBy(allTags, ({ token }) => token)).map( + ([key, entries]) => [key, entries.map(({ value }) => value)] + ) + ); + + window.location.assign( + [window.location.origin, qs.stringify(params)].join('?') + ); + }, + getItems() { + return [{}] as Array>; + }, + templates: { + item() { + return ( + label).join(' ')} /> + ); + }, + }, + }; + const prefixesSource = { + sourceId: 'prefixes', + onSelect({ item, setQuery, setIsOpen, refresh }) { + setQuery(`${item.token}:`); + setIsOpen(true); + + refresh(); + }, + getItems() { + if (query.length === 0) { + return items; + } + + const filtered = items.filter(({ token }) => token.startsWith(prefix)); + + return filtered.length > 0 ? filtered : items; + }, + templates: { + header() { + return ; + }, + item({ item }) { + return ; + }, + }, + }; + const postfixesSource = { + sourceId: 'postfixes', + onSelect({ setQuery }) { + setQuery(''); + }, + getItems() { + const [tag] = items.filter(({ token }) => token.startsWith(prefix)); + + if (!tag) { + return []; + } + + return tag.search({ + query: postfix, + facet: tag.token === 'org' ? 'org' : undefined, + tags: tagsByToken[tag.token], + }); + }, + templates: { + header() { + return ; + }, + item({ item, components }) { + return ; + }, + }, + }; + + return [ + showQuerySource && querySource, + showPrefixesSource && prefixesSource, + showPostfixesSource && postfixesSource, + ].filter(Boolean) as Array>; + }, + render({ sections, state }, root) { + const [prefix] = splitQuery(state.query); + const prefixes = items.filter(({ token }) => token.startsWith(prefix)); + + render( + + {prefixes.length === 0 && ( +
+ Sorry, we don't support the {state.query} filter + yet. +
+ )} + {sections} +
, + root + ); + }, +}); + +onTagsChange(tagsPlugin.data.tags); + +const searchInput: HTMLInputElement = document.querySelector( + '.aa-Autocomplete .aa-Input' +); + +searchInput.addEventListener('keydown', (event) => { + if ( + event.key === 'Backspace' && + searchInput.selectionStart === 0 && + searchInput.selectionEnd === 0 + ) { + const newTags = tagsPlugin.data.tags.slice(0, -1); + tagsPlugin.data.setTags(newTags); + } +}); + +function onTagsChange(tags: Array>) { + requestAnimationFrame(() => { + const container = document.querySelector('.aa-InputWrapperPrefix'); + const oldTagsContainer = document.querySelector('.aa-Tags'); + + const tagsContainer = document.createElement('div'); + tagsContainer.classList.add('aa-Tags'); + + render( + tags.length > 0 ? ( +
+ {tags.map((tag) => ( + + ))} +
+ ) : null, + tagsContainer + ); + + if (oldTagsContainer) { + container.replaceChild(tagsContainer, oldTagsContainer); + } else { + container.appendChild(tagsContainer); + } + }); +} + +function splitQuery(query: string) { + const [prefix, postfix] = query.split(':'); + + return [prefix, postfix]; +} + +function getAlltags(tags: Array>, query: string) { + const [prefix, postfix] = splitQuery(query); + const tagFromQuery = postfix + ? [ + { + label: `${prefix}:${postfix}`, + token: prefix, + value: postfix, + }, + ] + : []; + + return [...tags, ...tagFromQuery]; +} diff --git a/examples/github-notification-filters/capture.png b/examples/github-notification-filters/capture.png new file mode 100644 index 0000000000..0f6f05d727 Binary files /dev/null and b/examples/github-notification-filters/capture.png differ diff --git a/examples/github-notification-filters/components/FilterHeader.tsx b/examples/github-notification-filters/components/FilterHeader.tsx new file mode 100644 index 0000000000..783e2442f9 --- /dev/null +++ b/examples/github-notification-filters/components/FilterHeader.tsx @@ -0,0 +1,12 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { h, Fragment } from 'preact'; + +export function FilterHeader() { + return ( + + Available filters +
+ + ); +} diff --git a/examples/github-notification-filters/components/PanelLayout.tsx b/examples/github-notification-filters/components/PanelLayout.tsx new file mode 100644 index 0000000000..682d227eb1 --- /dev/null +++ b/examples/github-notification-filters/components/PanelLayout.tsx @@ -0,0 +1,28 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { AutocompleteState } from '@algolia/autocomplete-js'; +import { ComponentChildren, h } from 'preact'; + +import { AutocompleteItem } from '../types'; + +type PanelLayoutProps = { + state: AutocompleteState; + children: ComponentChildren; +}; + +export function PanelLayout({ state, children }: PanelLayoutProps) { + const hasResults = state.collections.some(({ items }) => items.length > 0); + + return ( +
+ {children} +
+ ); +} diff --git a/examples/github-notification-filters/components/PostfixItem.tsx b/examples/github-notification-filters/components/PostfixItem.tsx new file mode 100644 index 0000000000..3a11ad8d2c --- /dev/null +++ b/examples/github-notification-filters/components/PostfixItem.tsx @@ -0,0 +1,27 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { AutocompleteComponents } from '@algolia/autocomplete-js'; +import { h } from 'preact'; + +import { AutocompleteItem } from '../types'; + +type PostfixItemProps = { + item: AutocompleteItem; + components: AutocompleteComponents; +}; + +export function PostfixItem({ item, components }: PostfixItemProps) { + return ( +
+
+
+
+ + {item.token}: + +
+
+
+
+ ); +} diff --git a/examples/github-notification-filters/components/PrefixItem.tsx b/examples/github-notification-filters/components/PrefixItem.tsx new file mode 100644 index 0000000000..c5d43d7c0c --- /dev/null +++ b/examples/github-notification-filters/components/PrefixItem.tsx @@ -0,0 +1,23 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { h } from 'preact'; + +import { AutocompleteItem } from '../types'; + +type PrefixItemProps = { + item: AutocompleteItem; +}; + +export function PrefixItem({ item }: PrefixItemProps) { + return ( +
+
+
+
+ {item.token}: {item.label} +
+
+
+
+ ); +} diff --git a/examples/github-notification-filters/components/QueryItem.tsx b/examples/github-notification-filters/components/QueryItem.tsx new file mode 100644 index 0000000000..254748dfa2 --- /dev/null +++ b/examples/github-notification-filters/components/QueryItem.tsx @@ -0,0 +1,21 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { h } from 'preact'; + +type QueryItemProps = { + query: string; +}; + +export function QueryItem({ query }: QueryItemProps) { + return ( +
+
+
+
+ {query} - submit +
+
+
+
+ ); +} diff --git a/examples/github-notification-filters/components/TagItem.tsx b/examples/github-notification-filters/components/TagItem.tsx new file mode 100644 index 0000000000..c50328cae9 --- /dev/null +++ b/examples/github-notification-filters/components/TagItem.tsx @@ -0,0 +1,30 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { Tag } from '@algolia/autocomplete-plugin-tags'; +import { h } from 'preact'; + +export function TagItem({ label, remove }: Tag) { + return ( +
+ {label} + +
+ ); +} diff --git a/examples/github-notification-filters/components/index.ts b/examples/github-notification-filters/components/index.ts new file mode 100644 index 0000000000..91dead3c24 --- /dev/null +++ b/examples/github-notification-filters/components/index.ts @@ -0,0 +1,6 @@ +export * from './FilterHeader'; +export * from './PanelLayout'; +export * from './PostfixItem'; +export * from './PrefixItem'; +export * from './QueryItem'; +export * from './TagItem'; diff --git a/examples/github-notification-filters/env.ts b/examples/github-notification-filters/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/github-notification-filters/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/github-notification-filters/favicon.png b/examples/github-notification-filters/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/github-notification-filters/favicon.png differ diff --git a/examples/github-notification-filters/global.d.ts b/examples/github-notification-filters/global.d.ts new file mode 100644 index 0000000000..f3f2b3dd3e --- /dev/null +++ b/examples/github-notification-filters/global.d.ts @@ -0,0 +1,9 @@ +import { TagsApi } from '@algolia/autocomplete-plugin-tags'; + +import { NotificationFilter } from './types'; + +declare module '@algolia/autocomplete-core' { + interface AutocompleteContext { + tagsPlugin: TagsApi; + } +} diff --git a/examples/github-notification-filters/index.html b/examples/github-notification-filters/index.html new file mode 100644 index 0000000000..3de2d197b2 --- /dev/null +++ b/examples/github-notification-filters/index.html @@ -0,0 +1,20 @@ + + + + + + + + + GitHub notification filters | Autocomplete + + + +
+
+
+ + + + + diff --git a/examples/github-notification-filters/items.ts b/examples/github-notification-filters/items.ts new file mode 100644 index 0000000000..b6d5c0119b --- /dev/null +++ b/examples/github-notification-filters/items.ts @@ -0,0 +1,244 @@ +import { getAlgoliaResults, getAlgoliaFacets } from '@algolia/autocomplete-js'; +import { Tag } from '@algolia/autocomplete-plugin-tags'; + +import { searchClient } from './searchClient'; +import { Contributor, NotificationFilter, Repository } from './types'; + +type SearchParams = { + query?: string; + facet?: string; + tags?: Array>; +}; + +export const items = [ + { + token: 'repo', + label: 'filter by repository', + attribute: 'name', + search({ query, tags = [] }: SearchParams) { + const { token, attribute } = this; + + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'github_repos', + query, + params: { + hitsPerPage: 10, + filters: mapToAlgoliaNegativeFilters(tags, ['name']), + }, + }, + ], + transformResponse({ hits }) { + return hits[0].map((hit) => ({ + ...hit, + token, + label: hit.name, + attribute, + _highlightResult: { + ...hit._highlightResult, + label: hit._highlightResult[attribute], + }, + })); + }, + }); + }, + }, + { + token: 'is', + label: 'filter by status or discussion type', + search({ query, tags = [] }: SearchParams) { + const { token } = this; + + return Promise.resolve( + [ + { + label: 'read', + }, + { + label: 'unread', + }, + { + label: 'done', + }, + { + label: 'check-suite', + }, + { + label: 'commit', + }, + { + label: 'gist', + }, + { + label: 'release', + }, + { + label: 'repository-invitation', + }, + { + label: 'repository-vulnerability-alert', + }, + { + label: 'repository-advisory', + }, + { + label: 'team-discussion', + }, + { + label: 'discussion', + }, + { + label: 'issue-or-pull-request', + }, + ] + .filter( + ({ label }) => + label.startsWith(query) && + !tags.some(({ value }) => value === label) + ) + .map((item) => ({ ...item, token })) + ); + }, + }, + { + token: 'reason', + label: 'filter by notification reason', + search({ query, tags = [] }: SearchParams) { + const { token } = this; + + return Promise.resolve( + [ + { + label: 'assign', + }, + { + label: 'author', + }, + { + label: 'comment', + }, + { + label: 'invitation', + }, + { + label: 'manual', + }, + { + label: 'mention', + }, + { + label: 'review-requested', + }, + { + label: 'security-advisory-credit', + }, + { + label: 'security-alert', + }, + { + label: 'state-change', + }, + { + label: 'subscribed', + }, + { + label: 'team-mention', + }, + { + label: 'ci-activity', + }, + { + label: 'approval-requested', + }, + ] + .filter( + ({ label }) => + label.startsWith(query) && + !tags.some(({ value }) => value === label) + ) + .map((item) => ({ ...item, token })) + ); + }, + }, + { + token: 'author', + label: 'filter by notification author', + attribute: 'login', + search({ query, tags = [] }: SearchParams) { + const { token, attribute } = this; + + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'github_contributors', + query, + params: { + hitsPerPage: 10, + filters: mapToAlgoliaNegativeFilters(tags, ['login']), + }, + }, + ], + transformResponse({ hits }) { + return hits[0].map((hit) => ({ + ...hit, + token, + label: hit.login, + attribute, + _highlightResult: { + ...hit._highlightResult, + label: hit._highlightResult[attribute], + }, + })); + }, + }); + }, + }, + { + token: 'org', + label: 'filter by organization', + attribute: 'label', + search({ query, facet, tags = [] }: SearchParams) { + const { token, attribute } = this; + + return getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'github_contributors', + facet, + params: { + facetQuery: query, + maxFacetHits: 5, + filters: mapToAlgoliaNegativeFilters(tags, ['label']), + }, + }, + ], + transformResponse({ facetHits }) { + return facetHits[0].map((hit) => ({ + ...hit, + token, + attribute, + })); + }, + }); + }, + }, +]; + +function mapToAlgoliaNegativeFilters( + tags: Array>, + facetsToNegate: string[], + operator = 'AND' +) { + return tags + .map(({ value, attribute }) => { + const filter = `${attribute}:"${value}"`; + + return facetsToNegate.includes(attribute) && `NOT ${filter}`; + }) + .filter(Boolean) + .join(` ${operator} `); +} diff --git a/examples/github-notification-filters/package.json b/examples/github-notification-filters/package.json new file mode 100644 index 0000000000..601d2be686 --- /dev/null +++ b/examples/github-notification-filters/package.json @@ -0,0 +1,28 @@ +{ + "name": "@algolia/autocomplete-example-github-notification-filters", + "description": "Autocomplete example with GitHub notification filters", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-tags": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "preact": "10.13.2", + "qs": "6.11.1" + }, + "devDependencies": { + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/github-notification-filters/searchClient.ts b/examples/github-notification-filters/searchClient.ts new file mode 100644 index 0000000000..a07d1736e1 --- /dev/null +++ b/examples/github-notification-filters/searchClient.ts @@ -0,0 +1,6 @@ +import algoliasearch from 'algoliasearch/lite'; + +const appId = 'latency'; +const apiKey = '147a0e7dbc37d4c4dec9ec31b0f68716'; + +export const searchClient = algoliasearch(appId, apiKey); diff --git a/examples/github-notification-filters/style.css b/examples/github-notification-filters/style.css new file mode 100644 index 0000000000..74d49492a9 --- /dev/null +++ b/examples/github-notification-filters/style.css @@ -0,0 +1,102 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; + --aa-panel-max-height: 300px; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} + +.aa-UnsupportedFilter { + background: #fff8c5; + margin-top: calc(var(--aa-spacing-half) * -1); + margin-left: calc(var(--aa-spacing-half) * -1); + margin-right: calc(var(--aa-spacing-half) * -1); + padding: 0.5rem; + font-size: 80%; +} + +.aa-PanelLayout--noResults .aa-UnsupportedFilter { + margin-bottom: calc(var(--aa-spacing-half) * -1); +} + +.aa-ItemContent .aa-ItemContentTitle mark { + background: rgba( + var(--aa-description-highlight-background-color-rgb), + var(--aa-description-highlight-background-color-alpha) + ); +} + +.aa-Tags { + margin-right: 8px; +} + +.aa-Tags:empty { + display: none; +} + +.aa-TagsList { + display: flex; + margin: 0 calc((var(--aa-spacing) / 5) * -1); +} + +.aa-Tag { + background-color: rgba( + var(--aa-selected-color-rgb), + var(--aa-selected-color-alpha) + ); + align-items: center; + display: flex; + margin: 0 calc(var(--aa-spacing) / 5); + padding-left: var(--aa-spacing-half); + border-radius: calc(var(--aa-spacing) / 4); +} + +.aa-Tag:hover, +.aa-Tag:focus { + background-color: rgba(var(--aa-selected-color-rgb), 0.55); +} + +.aa-TagLabel { + font-size: 0.8em; +} + +.aa-TagRemoveButton { + cursor: pointer; + padding: 0; + border: 0; + background: none; +} + +.aa-TagRemoveButton svg { + color: rgba(var(--aa-muted-color-rgb), var(--aa-muted-color-alpha)); + margin: 0; + margin: calc(var(--aa-spacing) / 2.5); + stroke-width: var(--aa-icon-stroke-width); + width: calc(var(--aa-action-icon-size) / 1.5); +} + +.aa-TagRemoveButton:hover svg, +.aa-TagRemoveButton:focus svg { + color: rgba(var(--aa-text-color-rgb), var(--aa-text-color-alpha)); +} + +@media (hover: none) and (pointer: coarse) { + .aa-TagRemoveButton:hover, + .aa-TagRemoveButton:focus { + color: inherit; + } +} diff --git a/examples/github-notification-filters/types/AutocompleteItem.ts b/examples/github-notification-filters/types/AutocompleteItem.ts new file mode 100644 index 0000000000..b0c964125e --- /dev/null +++ b/examples/github-notification-filters/types/AutocompleteItem.ts @@ -0,0 +1,5 @@ +export type AutocompleteItem = { + token: string; + label: string; + attribute: string; +}; diff --git a/examples/github-notification-filters/types/Contributor.ts b/examples/github-notification-filters/types/Contributor.ts new file mode 100644 index 0000000000..cfa9d9ef09 --- /dev/null +++ b/examples/github-notification-filters/types/Contributor.ts @@ -0,0 +1,6 @@ +export type Contributor = { + login: string; + org: string; + avatar_url: string; + id: number; +}; diff --git a/examples/github-notification-filters/types/NotificationFilter.ts b/examples/github-notification-filters/types/NotificationFilter.ts new file mode 100644 index 0000000000..5951ea18af --- /dev/null +++ b/examples/github-notification-filters/types/NotificationFilter.ts @@ -0,0 +1,5 @@ +export type NotificationFilter = { + token: string; + value: string; + attribute: string; +}; diff --git a/examples/github-notification-filters/types/Repository.ts b/examples/github-notification-filters/types/Repository.ts new file mode 100644 index 0000000000..e48ba2dc2c --- /dev/null +++ b/examples/github-notification-filters/types/Repository.ts @@ -0,0 +1,17 @@ +export type Repository = { + name: string; + description: string; + stargazers_count: number; + forks: number; + language: string; + owner: string; + topics: string[]; + created_at: string; + default_branch: string; + homepage: string; + html_url: string; + id: number; + license: string; + open_issues: number; + watchers: number; +}; diff --git a/examples/github-notification-filters/types/index.ts b/examples/github-notification-filters/types/index.ts new file mode 100644 index 0000000000..c23635e627 --- /dev/null +++ b/examples/github-notification-filters/types/index.ts @@ -0,0 +1,4 @@ +export * from './AutocompleteItem'; +export * from './Contributor'; +export * from './NotificationFilter'; +export * from './Repository'; diff --git a/examples/github-notification-filters/utils/groupBy.ts b/examples/github-notification-filters/utils/groupBy.ts new file mode 100644 index 0000000000..9172b127ba --- /dev/null +++ b/examples/github-notification-filters/utils/groupBy.ts @@ -0,0 +1,16 @@ +export function groupBy( + collection: TValue[], + iteratee: (item: TValue) => TKey +) { + return collection.reduce>((acc, item) => { + const key = iteratee(item); + + if (!acc.hasOwnProperty(key)) { + acc[key] = []; + } + + acc[key].push(item); + + return acc; + }, {} as any); +} diff --git a/examples/github-notification-filters/utils/index.ts b/examples/github-notification-filters/utils/index.ts new file mode 100644 index 0000000000..7ccbdbc165 --- /dev/null +++ b/examples/github-notification-filters/utils/index.ts @@ -0,0 +1 @@ +export * from './groupBy'; diff --git a/examples/github-notification-filters/vite.config.mjs b/examples/github-notification-filters/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/github-notification-filters/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/github-repositories-custom-plugin/README.md b/examples/github-repositories-custom-plugin/README.md new file mode 100644 index 0000000000..c9d9edaced --- /dev/null +++ b/examples/github-repositories-custom-plugin/README.md @@ -0,0 +1,34 @@ +# Autocomplete GitHub repositories custom plugin example + +This example shows how to build an Autocomplete [plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/plugins/) fetching GitHub repositories. + +

A capture of the Autocomplete GitHub repositories custom plugin demo

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/github-repositories-custom-plugin) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-github-repositories-custom-plugin start +``` + +Alternatively, you may use npm: + +```sh +cd examples/github-repositories-custom-plugin +npm install +npm start +``` + +Open to see your app. diff --git a/examples/github-repositories-custom-plugin/app.tsx b/examples/github-repositories-custom-plugin/app.tsx new file mode 100644 index 0000000000..f1c6d077ce --- /dev/null +++ b/examples/github-repositories-custom-plugin/app.tsx @@ -0,0 +1,16 @@ +import { autocomplete } from '@algolia/autocomplete-js'; + +import { createGitHubReposPlugin } from './createGitHubReposPlugin'; + +import '@algolia/autocomplete-theme-classic'; + +const gitHubReposPlugin = createGitHubReposPlugin({ + // eslint-disable-next-line @typescript-eslint/camelcase + per_page: 5, +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search repositories', + plugins: [gitHubReposPlugin], +}); diff --git a/examples/github-repositories-custom-plugin/capture.png b/examples/github-repositories-custom-plugin/capture.png new file mode 100644 index 0000000000..49a4860c50 Binary files /dev/null and b/examples/github-repositories-custom-plugin/capture.png differ diff --git a/examples/github-repositories-custom-plugin/createGitHubReposPlugin.tsx b/examples/github-repositories-custom-plugin/createGitHubReposPlugin.tsx new file mode 100644 index 0000000000..b9325409c2 --- /dev/null +++ b/examples/github-repositories-custom-plugin/createGitHubReposPlugin.tsx @@ -0,0 +1,155 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { AutocompletePlugin } from '@algolia/autocomplete-js'; +import { h } from 'preact'; +import qs from 'qs'; + +type GitHubRepository = { + full_name: string; + description: string; + stargazers_count: number; + html_url: string; + owner: { + avatar_url: string; + }; +}; + +type CreateGithubReposPluginProps = { + accept?: string; + sort?: 'stars' | 'forks' | 'help-wanted-issues' | 'updated'; + order?: 'asc' | 'desc'; + per_page?: number; + page?: number; +}; + +function debouncePromise( + fn: (...params: TParams) => Promise, + time: number +) { + let timerId: ReturnType | undefined = undefined; + + return function (...args: TParams) { + if (timerId) { + clearTimeout(timerId); + } + + return new Promise((resolve) => { + timerId = setTimeout(() => resolve(fn(...args)), time); + }); + }; +} + +const debouncedFetch = debouncePromise(fetch, 300); + +const baseUrl = `https://api.github.com/search/repositories`; + +export function createGitHubReposPlugin( + options: CreateGithubReposPluginProps = {} +): AutocompletePlugin { + return { + getSources({ query }) { + const queryParameters = qs.stringify({ ...options, q: query }); + const endpoint = [baseUrl, queryParameters].join('?'); + + return debouncedFetch(endpoint) + .then((response) => response.json()) + .then((repositories) => { + return [ + { + sourceId: 'githubPlugin', + getItems() { + return repositories.items || []; + }, + getItemUrl({ item }) { + return item.html_url; + }, + templates: { + item({ item }) { + const stars = new Intl.NumberFormat('en-US').format( + item.stargazers_count + ); + + return ( +
+
+
+ {item.full_name} +
+
+
+
+
+ {item.full_name} +
+
+ + + {' '} + +
+
+
+
+ {item.description} +
+
+
+
+ +
+
+ ); + }, + }, + }, + ]; + }); + }, + }; +} diff --git a/examples/github-repositories-custom-plugin/env.ts b/examples/github-repositories-custom-plugin/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/github-repositories-custom-plugin/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/github-repositories-custom-plugin/favicon.png b/examples/github-repositories-custom-plugin/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/github-repositories-custom-plugin/favicon.png differ diff --git a/examples/github-repositories-custom-plugin/index.html b/examples/github-repositories-custom-plugin/index.html new file mode 100644 index 0000000000..4fc0e17dac --- /dev/null +++ b/examples/github-repositories-custom-plugin/index.html @@ -0,0 +1,20 @@ + + + + + + + + + Custom GitHub Repositories plugin | Autocomplete + + + +
+
+
+ + + + + diff --git a/examples/github-repositories-custom-plugin/package.json b/examples/github-repositories-custom-plugin/package.json new file mode 100644 index 0000000000..4ebb2390d2 --- /dev/null +++ b/examples/github-repositories-custom-plugin/package.json @@ -0,0 +1,28 @@ +{ + "name": "@algolia/autocomplete-example-github-repositories-custom-plugin", + "description": "Autocomplete example with custom GitHub Repositories plugin", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "preact": "10.13.2", + "qs": "6.11.1" + }, + "devDependencies": { + "@types/debounce-promise": "^3.1.6", + "@types/qs": "^6.9.6", + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/github-repositories-custom-plugin/style.css b/examples/github-repositories-custom-plugin/style.css new file mode 100644 index 0000000000..a4d3906cf3 --- /dev/null +++ b/examples/github-repositories-custom-plugin/style.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/examples/github-repositories-custom-plugin/vite.config.mjs b/examples/github-repositories-custom-plugin/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/github-repositories-custom-plugin/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/html-templates/README.md b/examples/html-templates/README.md new file mode 100644 index 0000000000..6e74b954fb --- /dev/null +++ b/examples/html-templates/README.md @@ -0,0 +1,34 @@ +# Autocomplete with HTML templates example + +This example shows how to use HTML templates in Autocomplete. + +

A capture of the Autocomplete with HTML templates example

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/html-templates) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-html-templates start +``` + +Alternatively, you may use npm: + +```sh +cd examples/html-templates +npm install +npm start +``` + +Open to see your app. diff --git a/examples/html-templates/app.js b/examples/html-templates/app.js new file mode 100644 index 0000000000..85e9a92ae9 --- /dev/null +++ b/examples/html-templates/app.js @@ -0,0 +1,66 @@ +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; +import algoliasearch from 'algoliasearch/lite'; + +import '@algolia/autocomplete-theme-classic'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + insights: true, + getSources({ query }) { + return [ + { + sourceId: 'products', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + }, + ], + }); + }, + templates: { + item({ item, components, html }) { + return html`
+
+
+ ${item.name} +
+ +
+
+ ${components.Highlight({ hit: item, attribute: 'name' })} +
+
+ By ${item.brand} in ${' '} + ${item.categories[0]} +
+
+
+
`; + }, + }, + }, + ]; + }, + render({ children, render, html }, root) { + render(html`
${children}
`, root); + }, + renderNoResults({ children, render, html }, root) { + render(html`
${children}
`, root); + }, +}); diff --git a/examples/html-templates/capture.png b/examples/html-templates/capture.png new file mode 100644 index 0000000000..0b8830e1c1 Binary files /dev/null and b/examples/html-templates/capture.png differ diff --git a/examples/html-templates/env.js b/examples/html-templates/env.js new file mode 100644 index 0000000000..3811e898da --- /dev/null +++ b/examples/html-templates/env.js @@ -0,0 +1,6 @@ +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app. +// See https://twitter.com/devongovett/status/1134231234605830144 +global.__DEV__ = process.env.NODE_ENV !== 'production'; +global.__TEST__ = false; diff --git a/examples/html-templates/favicon.png b/examples/html-templates/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/html-templates/favicon.png differ diff --git a/examples/html-templates/index.html b/examples/html-templates/index.html new file mode 100644 index 0000000000..f14b98af25 --- /dev/null +++ b/examples/html-templates/index.html @@ -0,0 +1,20 @@ + + + + + + + + + HTML Templates | Autocomplete + + + +
+
+
+ + + + + diff --git a/examples/html-templates/package.json b/examples/html-templates/package.json new file mode 100644 index 0000000000..aafb2a695a --- /dev/null +++ b/examples/html-templates/package.json @@ -0,0 +1,26 @@ +{ + "name": "@algolia/autocomplete-example-html-templates", + "description": "Autocomplete example with HTML templates", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0" + }, + "devDependencies": { + "@algolia/client-search": "4.16.0", + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/html-templates/style.css b/examples/html-templates/style.css new file mode 100644 index 0000000000..a4d3906cf3 --- /dev/null +++ b/examples/html-templates/style.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/examples/html-templates/vite.config.mjs b/examples/html-templates/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/html-templates/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/index.html b/examples/index.html deleted file mode 100644 index 4076f9149f..0000000000 --- a/examples/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - Autocomplete.js example - - - - Welcome to the Autocomplete.js examples: -
    -
  1. Basic example
  2. -
  3. Basic Angular.js example
  4. -
  5. Basic jQuery example
  6. -
  7. Test playground
  8. -
  9. Test playground (Angular.js)
  10. -
  11. Test playground (jQuery)
- - diff --git a/examples/instantsearch/README.md b/examples/instantsearch/README.md new file mode 100644 index 0000000000..dfe5a8eb7d --- /dev/null +++ b/examples/instantsearch/README.md @@ -0,0 +1,34 @@ +# Autocomplete with InstantSearch.js example + +This example shows how to integrate Autocomplete with [InstantSearch.js](https://github.com/algolia/instantsearch.js/). + +

A capture of the Autocomplete with InstantSearch.js demo

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/instantsearch) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-instantsearch start +``` + +Alternatively, you may use npm: + +```sh +cd examples/instantsearch +npm install +npm start +``` + +Open to see your app. \ No newline at end of file diff --git a/examples/instantsearch/capture.png b/examples/instantsearch/capture.png new file mode 100644 index 0000000000..c14c118882 Binary files /dev/null and b/examples/instantsearch/capture.png differ diff --git a/examples/instantsearch/env.ts b/examples/instantsearch/env.ts new file mode 100644 index 0000000000..674a16a13e --- /dev/null +++ b/examples/instantsearch/env.ts @@ -0,0 +1,6 @@ +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; diff --git a/examples/instantsearch/favicon.png b/examples/instantsearch/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/instantsearch/favicon.png differ diff --git a/examples/instantsearch/index.html b/examples/instantsearch/index.html new file mode 100644 index 0000000000..58d433d730 --- /dev/null +++ b/examples/instantsearch/index.html @@ -0,0 +1,40 @@ + + + + + + + + + InstantSearch | Autocomplete + + + +
+
+ +
+
+
+ +
+
+
+
+
+ + +
+ +
+
+ + + + + diff --git a/examples/instantsearch/iphone-banner.png b/examples/instantsearch/iphone-banner.png new file mode 100644 index 0000000000..36da9f7df3 Binary files /dev/null and b/examples/instantsearch/iphone-banner.png differ diff --git a/examples/instantsearch/package.json b/examples/instantsearch/package.json new file mode 100644 index 0000000000..5e5a591ee2 --- /dev/null +++ b/examples/instantsearch/package.json @@ -0,0 +1,29 @@ +{ + "name": "@algolia/autocomplete-example-instantsearch", + "description": "Autocomplete with InstantSearch example", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-query-suggestions": "1.19.7", + "@algolia/autocomplete-plugin-recent-searches": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "@algolia/client-search": "4.16.0", + "algoliasearch": "4.16.0", + "instantsearch.js": "4.73.1" + }, + "devDependencies": { + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/instantsearch/src/app.ts b/examples/instantsearch/src/app.ts new file mode 100644 index 0000000000..8dbed27916 --- /dev/null +++ b/examples/instantsearch/src/app.ts @@ -0,0 +1,8 @@ +import '@algolia/autocomplete-theme-classic'; +import '../style.css'; + +import { startAutocomplete } from './autocomplete'; +import { search } from './instantsearch'; + +search.start(); +startAutocomplete(search); diff --git a/examples/instantsearch/src/autocomplete.tsx b/examples/instantsearch/src/autocomplete.tsx new file mode 100644 index 0000000000..588642a220 --- /dev/null +++ b/examples/instantsearch/src/autocomplete.tsx @@ -0,0 +1,305 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { autocomplete } from '@algolia/autocomplete-js'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; +import { InstantSearch } from 'instantsearch.js'; + +import { + debouncedSetInstantSearchUiState, + getInstantSearchCurrentCategory, + getInstantSearchUiState, + getInstantSearchUrl, + INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE, + INSTANT_SEARCH_INDEX_NAME, + setInstantSearchUiState, +} from './instantsearch'; +import { isModifierEvent } from './isModifierEvent'; +import { searchClient } from './searchClient'; + +function onSelect({ setIsOpen, setQuery, event, query, category }) { + // You want to trigger the default browser behavior if the event is modified. + if (isModifierEvent(event)) { + return; + } + + setQuery(query); + setIsOpen(false); + setInstantSearchUiState({ + query, + hierarchicalMenu: { + [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [category], + }, + }); +} + +function getItemUrl({ query, category }) { + return getInstantSearchUrl({ + query, + hierarchicalMenu: { + [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [category], + }, + }); +} + +function getItemWrapper({ html, children, query, category }) { + const uiState = { + query, + hierarchicalMenu: { + [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [category], + }, + }; + + return html` { + if (!isModifierEvent(event)) { + // Bypass the original link behavior if there's no event modifier + // to set the InstantSearch UI state without reloading the page. + event.preventDefault(); + } + }} + > + ${children} + `; +} + +const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ + key: 'instantsearch', + limit: 3, + transformSource({ source }) { + return { + ...source, + getItemUrl({ item }) { + return getItemUrl({ + query: item.label, + category: item.category, + }); + }, + onSelect({ setIsOpen, setQuery, item, event }) { + onSelect({ + setQuery, + setIsOpen, + event, + query: item.label, + category: item.category, + }); + }, + templates: { + ...source.templates, + // Update the default `item` template to wrap it with a link + // and plug it to the InstantSearch router. + item(params) { + const { children } = (source.templates.item(params) as any).props; + const { item, html } = params; + + return getItemWrapper({ + query: item.label, + category: item.category, + html, + children, + }); + }, + }, + }; + }, +}); + +const querySuggestionsPluginInCategory = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams() { + const currentCategory = getInstantSearchCurrentCategory(); + + return recentSearchesPlugin.data!.getAlgoliaSearchParams({ + hitsPerPage: 3, + facetFilters: [ + `${INSTANT_SEARCH_INDEX_NAME}.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE}.value:${currentCategory}`, + ], + }); + }, + transformSource({ source }) { + const currentCategory = getInstantSearchCurrentCategory(); + + return { + ...source, + sourceId: 'querySuggestionsPluginInCategory', + getItemUrl({ item }) { + return getItemUrl({ + query: item.query, + category: currentCategory, + }); + }, + onSelect({ setIsOpen, setQuery, event, item }) { + onSelect({ + setQuery, + setIsOpen, + event, + query: item.query, + category: currentCategory, + }); + }, + getItems(params) { + if (!currentCategory) { + return []; + } + + return source.getItems(params); + }, + templates: { + ...source.templates, + header({ html }) { + return html` + In ${currentCategory} +
+ `; + }, + item(params) { + const { children } = (source.templates.item(params) as any).props; + const { item, html } = params; + + return getItemWrapper({ + query: item.query, + category: currentCategory, + html, + children, + }); + }, + }, + }; + }, +}); + +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams() { + const currentCategory = getInstantSearchCurrentCategory(); + + if (!currentCategory) { + return recentSearchesPlugin.data!.getAlgoliaSearchParams({ + hitsPerPage: 6, + }); + } + + return recentSearchesPlugin.data!.getAlgoliaSearchParams({ + hitsPerPage: 3, + facetFilters: [ + `${INSTANT_SEARCH_INDEX_NAME}.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE}.value:-${currentCategory}`, + ], + }); + }, + categoryAttribute: [ + INSTANT_SEARCH_INDEX_NAME, + 'facets', + 'exact_matches', + INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE, + ], + transformSource({ source }) { + const currentCategory = getInstantSearchCurrentCategory(); + + return { + ...source, + sourceId: 'querySuggestionsPlugin', + getItemUrl({ item }) { + return getItemUrl({ + query: item.query, + category: item.__autocomplete_qsCategory, + }); + }, + onSelect({ setIsOpen, setQuery, event, item }) { + onSelect({ + setQuery, + setIsOpen, + event, + query: item.query, + category: item.__autocomplete_qsCategory, + }); + }, + getItems(params) { + if (!params.state.query) { + return []; + } + + return source.getItems(params); + }, + templates: { + ...source.templates, + header({ html }) { + if (!currentCategory) { + return null; + } + + return html` + In other categories +
+ `; + }, + item(params) { + const { children } = (source.templates.item(params) as any).props; + const { item, html } = params; + + return getItemWrapper({ + query: item.query, + category: item.__autocomplete_qsCategory, + html, + children, + }); + }, + }, + }; + }, +}); + +const searchPageState = getInstantSearchUiState(); + +export function startAutocomplete(searchInstance: InstantSearch) { + let skipInstantSearchStateUpdate = false; + + const { setQuery } = autocomplete({ + container: '#autocomplete', + placeholder: 'Search for products', + insights: true, + openOnFocus: true, + plugins: [ + recentSearchesPlugin, + querySuggestionsPluginInCategory, + querySuggestionsPlugin, + ], + detachedMediaQuery: 'none', + initialState: { + query: searchPageState.query || '', + }, + navigator: { + navigate() { + // We don't navigate to a new page because we leverage the InstantSearch + // UI state API. + }, + }, + onSubmit({ state }) { + setInstantSearchUiState({ query: state.query }); + }, + onReset() { + setInstantSearchUiState({ + query: '', + hierarchicalMenu: { + [INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]: [], + }, + }); + }, + onStateChange({ prevState, state }) { + if (!skipInstantSearchStateUpdate && prevState.query !== state.query) { + debouncedSetInstantSearchUiState({ query: state.query }); + } + skipInstantSearchStateUpdate = false; + }, + }); + + window.addEventListener('popstate', () => { + skipInstantSearchStateUpdate = true; + setQuery( + (searchInstance.helper && searchInstance.helper.state.query) || '' + ); + }); +} diff --git a/examples/instantsearch/src/debounce.ts b/examples/instantsearch/src/debounce.ts new file mode 100644 index 0000000000..e505ead9c1 --- /dev/null +++ b/examples/instantsearch/src/debounce.ts @@ -0,0 +1,14 @@ +export function debounce( + fn: (...params: TParams[]) => void, + time: number +) { + let timerId: ReturnType | undefined = undefined; + + return function (...args: TParams[]) { + if (timerId) { + clearTimeout(timerId); + } + + timerId = setTimeout(() => fn(...args), time); + }; +} diff --git a/examples/instantsearch/src/instantsearch.ts b/examples/instantsearch/src/instantsearch.ts new file mode 100644 index 0000000000..4a024f5e8a --- /dev/null +++ b/examples/instantsearch/src/instantsearch.ts @@ -0,0 +1,193 @@ +import instantsearch from 'instantsearch.js'; +import { connectSearchBox } from 'instantsearch.js/es/connectors'; +import historyRouter from 'instantsearch.js/es/lib/routers/history'; +import { + configure, + hierarchicalMenu, + hits, + pagination, + panel, +} from 'instantsearch.js/es/widgets'; + +import { debounce } from '../src/debounce'; +import { searchClient } from '../src/searchClient'; + +export const INSTANT_SEARCH_INDEX_NAME = 'instant_search'; +export const INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE = + 'hierarchicalCategories.lvl0'; + +const instantSearchRouter = historyRouter(); + +export const search = instantsearch({ + searchClient, + indexName: INSTANT_SEARCH_INDEX_NAME, + routing: instantSearchRouter, +}); +const virtualSearchBox = connectSearchBox(() => {}); +const hierarchicalMenuWithHeader = panel({ + templates: { header: 'Categories' }, +})(hierarchicalMenu); + +search.addWidgets([ + configure({ + attributesToSnippet: ['name:7', 'description:15'], + snippetEllipsisText: '…', + }), + // Mount a virtual search box to manipulate InstantSearch's `query` UI + // state parameter. + virtualSearchBox(), + hierarchicalMenuWithHeader({ + container: '#categories', + attributes: [ + INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE, + 'hierarchicalCategories.lvl1', + ], + }), + hits({ + container: '#hits', + transformItems(items) { + return items.map((item) => ({ + ...item, + category: item.categories[0], + comments: (item.popularity % 100).toLocaleString(), + sale: item.free_shipping, + // eslint-disable-next-line @typescript-eslint/camelcase + sale_price: item.free_shipping + ? (item.price - item.price / 10).toFixed(2) + : item.price.toString(), + })); + }, + templates: { + item: (hit, { html, components }) => html` +
+
+ ${hit.name} +
+
+

${components.Snippet({ hit, attribute: 'name' })}

+
+ By ${hit.brand} in + ${hit.category} +
+
+ +
+ ${hit.rating > 0 && + html` +
+ + + + ${hit.rating} +
+ `} + +
+ + + + ${hit.comments} +
+
+
+ `, + }, + }), + pagination({ + container: '#pagination', + padding: 2, + showFirst: false, + showLast: false, + }), +]); + +// Set the InstantSearch index UI state from external events. +export function setInstantSearchUiState(indexUiState) { + search.setUiState((uiState) => ({ + ...uiState, + [INSTANT_SEARCH_INDEX_NAME]: { + ...uiState[INSTANT_SEARCH_INDEX_NAME], + // We reset the page when the search state changes. + page: 1, + ...indexUiState, + }, + })); +} + +export const debouncedSetInstantSearchUiState = debounce( + setInstantSearchUiState, + 500 +); + +// Get the current category from InstantSearch. +export function getInstantSearchCurrentCategory() { + const indexUiState = search.getUiState()[INSTANT_SEARCH_INDEX_NAME]; + const hierarchicalMenuUiState = indexUiState && indexUiState.hierarchicalMenu; + const currentCategories = + hierarchicalMenuUiState && + hierarchicalMenuUiState[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]; + + return currentCategories && currentCategories[0]; +} + +// Build URLs that InstantSearch understands. +export function getInstantSearchUrl(indexUiState) { + return search.createURL({ [INSTANT_SEARCH_INDEX_NAME]: indexUiState }); +} + +// Return the InstantSearch index UI state. +export function getInstantSearchUiState() { + const uiState = instantSearchRouter.read(); + + return (uiState && uiState[INSTANT_SEARCH_INDEX_NAME]) || {}; +} diff --git a/examples/instantsearch/src/isModifierEvent.ts b/examples/instantsearch/src/isModifierEvent.ts new file mode 100644 index 0000000000..c1b7998b06 --- /dev/null +++ b/examples/instantsearch/src/isModifierEvent.ts @@ -0,0 +1,17 @@ +/** + * Detect when an event is modified with a special key to let the browser + * trigger its default behavior. + */ +export function isModifierEvent( + event: TEvent +): boolean { + const isMiddleClick = (event as MouseEvent).button === 1; + + return ( + isMiddleClick || + event.altKey || + event.ctrlKey || + event.metaKey || + event.shiftKey + ); +} diff --git a/examples/instantsearch/src/searchClient.ts b/examples/instantsearch/src/searchClient.ts new file mode 100644 index 0000000000..376b339501 --- /dev/null +++ b/examples/instantsearch/src/searchClient.ts @@ -0,0 +1,6 @@ +import algoliasearch from 'algoliasearch/lite'; + +export const searchClient = algoliasearch( + 'latency', + '6be0576ff61c053d5f9a3225e2a90f76' +); diff --git a/examples/instantsearch/style.css b/examples/instantsearch/style.css new file mode 100644 index 0000000000..3020ddc5d9 --- /dev/null +++ b/examples/instantsearch/style.css @@ -0,0 +1,129 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + margin: 0; + padding: 0; +} + +a { + color: var(--aa-primary-color); + text-decoration: none; +} + +.header { + background: rgb(252 252 255 / 92%); + box-shadow: 0 0 0 1px rgba(35, 38, 59, 0.05), + 0 1px 3px 0 rgba(35, 38, 59, 0.15); + padding: 0.5rem 0; + position: fixed; + top: 0; + width: 100%; +} + +.header-wrapper { + align-items: center; + display: grid; + grid-template-columns: 100px 1fr; +} + +.header-nav { + font-weight: 500; +} + +.wrapper { + margin: 0 auto; + max-width: 1200px; + padding: 0 1.5rem; + width: 100%; +} + +.container { + margin-top: 3.5rem; + padding: 1.5rem; + display: grid; + gap: 1rem; + grid-template-columns: 1fr 3fr; +} + +.container img { + max-width: 100%; +} + +.banner { + border-radius: 3px; + margin-top: 1rem; +} + +/* Autocomplete */ + +.aa-Panel { + position: fixed; +} + +/* InstantSearch */ + +.ais-Hits { + margin-top: 1rem; +} + +.ais-Hits-list { + display: grid; + gap: 1rem; + grid-template-columns: 1fr 1fr 1fr; +} + +.ais-Hits-item { + padding: 1rem !important; +} + +.hit { + align-items: center; + display: grid; + gap: 1rem; +} + +.hit h1 { + font-size: 1rem; +} + +.hit p { + font-size: 0.8rem; + opacity: 0.8; +} + +.hit-image { + align-items: center; + display: flex; + height: 100px; + justify-content: center; +} + +.hit-image img { + max-height: 100%; +} + +.ais-HierarchicalMenu-item--selected.ais-HierarchicalMenu-item--parent + > div:first-of-type + .ais-HierarchicalMenu-label { + font-weight: bold; +} + +.ais-HierarchicalMenu-item--selected:not(.ais-HierarchicalMenu-item--parent) + .ais-HierarchicalMenu-label { + font-weight: bold; +} + +.ais-Pagination { + display: flex; + justify-content: center; + margin: 2rem 0; +} diff --git a/examples/instantsearch/vite.config.mjs b/examples/instantsearch/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/instantsearch/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/multiple-datasets-with-headers/README.md b/examples/multiple-datasets-with-headers/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/multiple-datasets-with-headers/app.tsx b/examples/multiple-datasets-with-headers/app.tsx new file mode 100644 index 0000000000..ca70f5f05b --- /dev/null +++ b/examples/multiple-datasets-with-headers/app.tsx @@ -0,0 +1,71 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { autocomplete } from '@algolia/autocomplete-js'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; +import algoliasearch from 'algoliasearch/lite'; +import { h, Fragment } from 'preact'; + +import '@algolia/autocomplete-theme-classic'; +import { createCategoriesPlugin } from './categoriesPlugin'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ + key: 'multi-datasets-with-headers-example', + limit: 3, + transformSource({ source }) { + return { + ...source, + templates: { + ...source.templates, + header() { + return ( + + Your searches +
+ + ); + }, + }, + }; + }, +}); +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams() { + return recentSearchesPlugin.data.getAlgoliaSearchParams({ + hitsPerPage: 5, + }); + }, + transformSource({ source }) { + return { + ...source, + templates: { + ...source.templates, + header({ state }) { + return ( + + + {state.query ? 'Suggested searches' : 'Popular searches'} + +
+ + ); + }, + }, + }; + }, +}); + +const categoriesPlugin = createCategoriesPlugin({ searchClient }); +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + insights: true, + plugins: [recentSearchesPlugin, querySuggestionsPlugin, categoriesPlugin], +}); diff --git a/examples/multiple-datasets-with-headers/categoriesPlugin.tsx b/examples/multiple-datasets-with-headers/categoriesPlugin.tsx new file mode 100644 index 0000000000..e0e26913cf --- /dev/null +++ b/examples/multiple-datasets-with-headers/categoriesPlugin.tsx @@ -0,0 +1,80 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { AutocompletePlugin, getAlgoliaFacets } from '@algolia/autocomplete-js'; +import { SearchClient } from 'algoliasearch/lite'; +import { h, Fragment } from 'preact'; + +type CategoryItem = { + label: string; + count: number; +}; + +type CreateCategoriesPluginProps = { + searchClient: SearchClient; +}; + +export function createCategoriesPlugin({ + searchClient, +}: CreateCategoriesPluginProps): AutocompletePlugin { + return { + getSources({ query }) { + return [ + { + sourceId: 'categoriesPlugin', + getItems() { + return getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'instant_search', + facet: 'categories', + params: { + facetQuery: query, + maxFacetHits: 5, + }, + }, + ], + }); + }, + templates: { + header() { + return ( + + Categories +
+ + ); + }, + item({ item, components }) { + return ( +
+
+
+ + + + + +
+
+
+ +
+
+
+
+ ); + }, + }, + }, + ]; + }, + }; +} diff --git a/examples/multiple-datasets-with-headers/env.ts b/examples/multiple-datasets-with-headers/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/multiple-datasets-with-headers/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/multiple-datasets-with-headers/favicon.png b/examples/multiple-datasets-with-headers/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/multiple-datasets-with-headers/favicon.png differ diff --git a/examples/multiple-datasets-with-headers/index.html b/examples/multiple-datasets-with-headers/index.html new file mode 100644 index 0000000000..a31f66d87f --- /dev/null +++ b/examples/multiple-datasets-with-headers/index.html @@ -0,0 +1,20 @@ + + + + + + + + + Multiple Datasets With Headers + + + +
+
+
+ + + + + diff --git a/examples/multiple-datasets-with-headers/package.json b/examples/multiple-datasets-with-headers/package.json new file mode 100644 index 0000000000..19d050c7f2 --- /dev/null +++ b/examples/multiple-datasets-with-headers/package.json @@ -0,0 +1,28 @@ +{ + "name": "@algolia/autocomplete-example-multiple-datasets-with-headers", + "description": "Autocomplete example with multiple datasets with headers", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-query-suggestions": "1.19.7", + "@algolia/autocomplete-plugin-recent-searches": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "preact": "10.13.2" + }, + "devDependencies": { + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/multiple-datasets-with-headers/style.css b/examples/multiple-datasets-with-headers/style.css new file mode 100644 index 0000000000..a4d3906cf3 --- /dev/null +++ b/examples/multiple-datasets-with-headers/style.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/examples/multiple-datasets-with-headers/vite.config.mjs b/examples/multiple-datasets-with-headers/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/multiple-datasets-with-headers/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/panel-placement-positioned/README.md b/examples/panel-placement-positioned/README.md new file mode 100644 index 0000000000..ffe28f8b02 --- /dev/null +++ b/examples/panel-placement-positioned/README.md @@ -0,0 +1,36 @@ +# Autocomplete panel placement in positioned container example + +This example shows how to use Autocomplete's [`panelPlacement` parameter](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/#param-panelplacement) +within a positioned container. + + +

A capture of the Autocomplete placement demo

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/panel-placement-positioned) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-panel-placement-positioned start +``` + +Alternatively, you may use npm: + +```sh +cd examples/panel-placement-positioned +npm install +npm start +``` + +Open to see your app. diff --git a/examples/panel-placement-positioned/app.tsx b/examples/panel-placement-positioned/app.tsx new file mode 100644 index 0000000000..0175029687 --- /dev/null +++ b/examples/panel-placement-positioned/app.tsx @@ -0,0 +1,121 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { + autocomplete, + AutocompleteComponents, + AutocompleteOptions, + getAlgoliaResults, + GetSources, +} from '@algolia/autocomplete-js'; +import { Hit } from '@algolia/client-search'; +import algoliasearch from 'algoliasearch/lite'; +import { h } from 'preact'; + +import '@algolia/autocomplete-theme-classic'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +type AutocompleteItem = Hit<{ + brand: string; + categories: string[]; + description: string; + image: string; + name: string; + price: number; + rating: number; + type: string; + url: string; +}>; + +const getSources: GetSources = ({ query }) => { + return [ + { + sourceId: 'products', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + }, + ], + }); + }, + templates: { + item({ item, components }) { + return ; + }, + noResults() { + return 'No products matching.'; + }, + }, + }, + ]; +}; + +const search = autocomplete({ + container: '#autocomplete', + panelContainer: '.parent', + placeholder: 'Search', + getSources, + insights: true, +}); + +const searchLeft = autocomplete({ + container: '#autocomplete-left', + panelContainer: '.parent', + placeholder: 'Search', + getSources, + insights: true, +}); + +const searchRight = autocomplete({ + container: '#autocomplete-right', + panelContainer: '.parent', + placeholder: 'Search', + getSources, + insights: true, +}); + +type ProductItemProps = { + item: AutocompleteItem; + components: AutocompleteComponents; +}; + +const ProductItem = ({ item, components }: ProductItemProps) => ( + +
+
+ {item.name} +
+ +
+
+ +
+
+ By {item.brand} in{' '} + {item.categories[0]} +
+
+
+
+); + +document + .querySelectorAll('input[name="placement"]') + .forEach((radio) => { + radio.addEventListener('change', (event) => { + const target = event.target as HTMLInputElement; + const panelPlacement = target.value as Required< + AutocompleteOptions['panelPlacement'] + >; + + search.update({ panelPlacement }); + searchLeft.update({ panelPlacement }); + searchRight.update({ panelPlacement }); + }); + }); diff --git a/examples/panel-placement-positioned/capture.jpg b/examples/panel-placement-positioned/capture.jpg new file mode 100644 index 0000000000..acf017758c Binary files /dev/null and b/examples/panel-placement-positioned/capture.jpg differ diff --git a/examples/panel-placement-positioned/env.ts b/examples/panel-placement-positioned/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/panel-placement-positioned/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/panel-placement-positioned/favicon.png b/examples/panel-placement-positioned/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/panel-placement-positioned/favicon.png differ diff --git a/examples/panel-placement-positioned/index.html b/examples/panel-placement-positioned/index.html new file mode 100644 index 0000000000..3d3589a019 --- /dev/null +++ b/examples/panel-placement-positioned/index.html @@ -0,0 +1,50 @@ + + + + + + + + + Panel placement | Autocomplete + + + +
+
+ Panel placement: + + + + + + + + +
+ +
+
+
+
+
+ +
+ +
+
+ + +
+ +
+ + + + + diff --git a/examples/panel-placement-positioned/package.json b/examples/panel-placement-positioned/package.json new file mode 100644 index 0000000000..03a5f2c44e --- /dev/null +++ b/examples/panel-placement-positioned/package.json @@ -0,0 +1,28 @@ +{ + "name": "@algolia/autocomplete-example-panel-placement-positioned", + "description": "Autocomplete panel placement example in positioned containers", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-core": "1.19.7", + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "preact": "10.13.2" + }, + "devDependencies": { + "@algolia/client-search": "4.16.0", + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/panel-placement-positioned/style.css b/examples/panel-placement-positioned/style.css new file mode 100644 index 0000000000..db2245808f --- /dev/null +++ b/examples/panel-placement-positioned/style.css @@ -0,0 +1,26 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + width: 100%; + display: grid; + grid-template-columns: 400px 1fr 400px; + grid-gap: 10px; +} + +.action { + align-content: center; +} diff --git a/examples/panel-placement-positioned/vite.config.mjs b/examples/panel-placement-positioned/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/panel-placement-positioned/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/panel-placement/README.md b/examples/panel-placement/README.md new file mode 100644 index 0000000000..e33b71499a --- /dev/null +++ b/examples/panel-placement/README.md @@ -0,0 +1,34 @@ +# Autocomplete panel placement example + +This example shows how to use Autocomplete's [`panelPlacement` parameter](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/#param-panelplacement). + +

A capture of the Autocomplete placement demo

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/panel-placement) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-panel-placement start +``` + +Alternatively, you may use npm: + +```sh +cd examples/panel-placement +npm install +npm start +``` + +Open to see your app. \ No newline at end of file diff --git a/examples/panel-placement/app.tsx b/examples/panel-placement/app.tsx new file mode 100644 index 0000000000..eabb0b6aff --- /dev/null +++ b/examples/panel-placement/app.tsx @@ -0,0 +1,118 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { + autocomplete, + AutocompleteComponents, + AutocompleteOptions, + getAlgoliaResults, + GetSources, +} from '@algolia/autocomplete-js'; +import { Hit } from '@algolia/client-search'; +import algoliasearch from 'algoliasearch/lite'; +import { h } from 'preact'; + +import '@algolia/autocomplete-theme-classic'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +type AutocompleteItem = Hit<{ + brand: string; + categories: string[]; + description: string; + image: string; + name: string; + price: number; + rating: number; + type: string; + url: string; +}>; + +const getSources: GetSources = ({ query }) => { + return [ + { + sourceId: 'products', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + }, + ], + }); + }, + templates: { + item({ item, components }) { + return ; + }, + noResults() { + return 'No products matching.'; + }, + }, + }, + ]; +}; + +const search = autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + getSources, + insights: true, +}); + +const searchLeft = autocomplete({ + container: '#autocomplete-left', + placeholder: 'Search', + getSources, + insights: true, +}); + +const searchRight = autocomplete({ + container: '#autocomplete-right', + placeholder: 'Search', + getSources, + insights: true, +}); + +type ProductItemProps = { + item: AutocompleteItem; + components: AutocompleteComponents; +}; + +const ProductItem = ({ item, components }: ProductItemProps) => ( + +
+
+ {item.name} +
+ +
+
+ +
+
+ By {item.brand} in{' '} + {item.categories[0]} +
+
+
+
+); + +document + .querySelectorAll('input[name="placement"]') + .forEach((radio) => { + radio.addEventListener('change', (event) => { + const target = event.target as HTMLInputElement; + const panelPlacement = target.value as Required< + AutocompleteOptions['panelPlacement'] + >; + + search.update({ panelPlacement }); + searchLeft.update({ panelPlacement }); + searchRight.update({ panelPlacement }); + }); + }); diff --git a/examples/panel-placement/capture.png b/examples/panel-placement/capture.png new file mode 100644 index 0000000000..c15f94c450 Binary files /dev/null and b/examples/panel-placement/capture.png differ diff --git a/examples/panel-placement/env.ts b/examples/panel-placement/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/panel-placement/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/panel-placement/favicon.png b/examples/panel-placement/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/panel-placement/favicon.png differ diff --git a/examples/panel-placement/index.html b/examples/panel-placement/index.html new file mode 100644 index 0000000000..4c4cc77bbd --- /dev/null +++ b/examples/panel-placement/index.html @@ -0,0 +1,39 @@ + + + + + + + + + Panel placement | Autocomplete + + + +
+ Panel placement: + + + + + + + + +
+ +
+
+
+
+
+ + + + + diff --git a/examples/panel-placement/package.json b/examples/panel-placement/package.json new file mode 100644 index 0000000000..8335ac9a28 --- /dev/null +++ b/examples/panel-placement/package.json @@ -0,0 +1,28 @@ +{ + "name": "@algolia/autocomplete-example-panel-placement", + "description": "Autocomplete panel placement example", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-core": "1.19.7", + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "preact": "10.13.2" + }, + "devDependencies": { + "@algolia/client-search": "4.16.0", + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/panel-placement/style.css b/examples/panel-placement/style.css new file mode 100644 index 0000000000..db2245808f --- /dev/null +++ b/examples/panel-placement/style.css @@ -0,0 +1,26 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + width: 100%; + display: grid; + grid-template-columns: 400px 1fr 400px; + grid-gap: 10px; +} + +.action { + align-content: center; +} diff --git a/examples/panel-placement/vite.config.mjs b/examples/panel-placement/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/panel-placement/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/playground/README.md b/examples/playground/README.md new file mode 100644 index 0000000000..31fa5a51f0 --- /dev/null +++ b/examples/playground/README.md @@ -0,0 +1,34 @@ +# Autocomplete playground + +This example shows a full-featured Autocomplete example with various plugins. + +

A capture of the Autocomplete playground

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/playground) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-playground start +``` + +Alternatively, you may use npm: + +```sh +cd examples/playground +npm install +npm start +``` + +Open to see your app. \ No newline at end of file diff --git a/examples/playground/app.tsx b/examples/playground/app.tsx new file mode 100644 index 0000000000..6879969907 --- /dev/null +++ b/examples/playground/app.tsx @@ -0,0 +1,307 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { + autocomplete, + AutocompleteComponents, + getAlgoliaResults, + AutocompleteInsightsApi, +} from '@algolia/autocomplete-js'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; +import algoliasearch from 'algoliasearch/lite'; +import { h, Fragment } from 'preact'; + +import '@algolia/autocomplete-theme-classic'; + +import { createCategoriesPlugin } from './categoriesPlugin'; +import { shortcutsPlugin } from './shortcutsPlugin'; +import { ProductRecord, ProductHit } from './types'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ + key: 'search', + limit: 3, +}); +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams({ state }) { + return recentSearchesPlugin.data.getAlgoliaSearchParams({ + hitsPerPage: state.query ? 5 : 10, + }); + }, + categoryAttribute: [ + 'instant_search', + 'facets', + 'exact_matches', + 'categories', + ], + categoriesPerItem: 2, +}); +const categoriesPlugin = createCategoriesPlugin({ searchClient }); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + debug: true, + openOnFocus: true, + insights: true, + plugins: [ + shortcutsPlugin, + recentSearchesPlugin, + querySuggestionsPlugin, + categoriesPlugin, + ], + getSources({ query, state }) { + if (!query) { + return []; + } + + return [ + { + sourceId: 'products', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + params: { + attributesToSnippet: ['name:10', 'description:35'], + snippetEllipsisText: '…', + }, + }, + ], + transformResponse({ hits }) { + const [bestBuyHits] = hits; + + return bestBuyHits.map((hit) => ({ + ...hit, + comments: hit.popularity % 100, + sale: hit.free_shipping, + // eslint-disable-next-line @typescript-eslint/camelcase + sale_price: hit.free_shipping + ? (hit.price - hit.price / 10).toFixed(2) + : hit.price.toString(), + })); + }, + }); + }, + templates: { + header() { + return ( + + Products +
+ + ); + }, + item({ item, components }) { + return ( + + ); + }, + noResults() { + return 'No products for this query.'; + }, + }, + }, + ]; + }, +}); + +type ProductItemProps = { + hit: ProductHit; + insights: AutocompleteInsightsApi; + components: AutocompleteComponents; +}; + +function ProductItem({ hit, insights, components }: ProductItemProps) { + return ( + +
+
+ {hit.name} +
+ +
+
+ +
+
+ By {hit.brand} in{' '} + {hit.categories[0]} +
+ +
+ {hit.rating > 0 && ( +
+
+ {Array.from({ length: 5 }, (_value, index) => { + const isFilled = hit.rating >= index + 1; + + return ( + + + + ); + })} +
+
+ )} +
+ + + + {hit.comments.toLocaleString()} +
+
+ +
+
+
+ + ${hit.sale_price.toLocaleString()} + {' '} + {hit.sale && ( + + ${hit.price.toLocaleString()} + + )} +
+ {hit.sale && ( + + On sale + + )} +
+
+
+
+ +
+ + +
+
+ ); +} diff --git a/examples/playground/capture.png b/examples/playground/capture.png new file mode 100644 index 0000000000..ea0732d85c Binary files /dev/null and b/examples/playground/capture.png differ diff --git a/examples/playground/categoriesPlugin.tsx b/examples/playground/categoriesPlugin.tsx new file mode 100644 index 0000000000..b4a22cbf96 --- /dev/null +++ b/examples/playground/categoriesPlugin.tsx @@ -0,0 +1,83 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { AutocompletePlugin, getAlgoliaFacets } from '@algolia/autocomplete-js'; +import { SearchClient } from 'algoliasearch/lite'; +import { h, Fragment } from 'preact'; + +type CategoryRecord = { + label: string; + count: number; +}; + +type CreateCategoriesPluginProps = { + searchClient: SearchClient; +}; + +export function createCategoriesPlugin({ + searchClient, +}: CreateCategoriesPluginProps): AutocompletePlugin { + return { + getSources({ query }) { + return [ + { + sourceId: 'categoriesPlugin', + getItems() { + return getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'instant_search', + facet: 'categories', + params: { + facetQuery: query, + maxFacetHits: query ? 3 : 5, + }, + }, + ], + }); + }, + templates: { + header() { + return ( + + Categories +
+ + ); + }, + item({ item, components }) { + return ( +
+
+
+ + + + + +
+ +
+
+ +
+
+
+
+ ); + }, + }, + }, + ]; + }, + }; +} diff --git a/examples/playground/darkMode.ts b/examples/playground/darkMode.ts new file mode 100644 index 0000000000..5c1c7a0fb8 --- /dev/null +++ b/examples/playground/darkMode.ts @@ -0,0 +1,31 @@ +function initTheme() { + if (isDarkThemeSelected()) { + applyDarkTheme(); + } else { + applyLightTheme(); + } +} + +export function isDarkThemeSelected() { + return localStorage.getItem('darkMode') === 'dark'; +} + +function applyDarkTheme() { + document.body.setAttribute('data-theme', 'dark'); + localStorage.setItem('darkMode', 'dark'); +} + +function applyLightTheme() { + document.body.removeAttribute('data-theme'); + localStorage.removeItem('darkMode'); +} + +export function toggleTheme() { + if (isDarkThemeSelected()) { + applyLightTheme(); + } else { + applyDarkTheme(); + } +} + +initTheme(); diff --git a/examples/playground/env.ts b/examples/playground/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/playground/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/playground/favicon.png b/examples/playground/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/playground/favicon.png differ diff --git a/examples/playground/index.html b/examples/playground/index.html new file mode 100644 index 0000000000..b3624c78f9 --- /dev/null +++ b/examples/playground/index.html @@ -0,0 +1,49 @@ + + + + + + + + + Playground | Autocomplete + + + +
+
+ +
+
    +
  • +
  • +
  • +
+
    +
  • +
  • +
  • +
+
    +
  • +
  • +
  • +
+
    +
  • +
  • +
  • +
+
    +
  • +
  • +
  • +
+
+
+ + + + + + diff --git a/examples/playground/package.json b/examples/playground/package.json new file mode 100644 index 0000000000..2b00ac42a5 --- /dev/null +++ b/examples/playground/package.json @@ -0,0 +1,31 @@ +{ + "name": "@algolia/autocomplete-example-playground", + "description": "Autocomplete playground", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-query-suggestions": "1.19.7", + "@algolia/autocomplete-plugin-recent-searches": "1.19.7", + "@algolia/autocomplete-preset-algolia": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "@algolia/client-search": "4.16.0", + "algoliasearch": "4.16.0", + "preact": "10.13.2", + "search-insights": "^2.15.0" + }, + "devDependencies": { + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/playground/shortcutsPlugin.tsx b/examples/playground/shortcutsPlugin.tsx new file mode 100644 index 0000000000..17fe99933a --- /dev/null +++ b/examples/playground/shortcutsPlugin.tsx @@ -0,0 +1,103 @@ +import { AutocompletePlugin } from '@algolia/autocomplete-js'; + +import { toggleTheme, isDarkThemeSelected } from './darkMode'; + +type DarkModeItem = { + label: string; +}; + +export const shortcutsPlugin: AutocompletePlugin = { + getSources({ query }) { + if (query !== '/' && query !== 'dark' && query !== 'light') { + return []; + } + + return [ + { + sourceId: 'shortcutsPlugin', + getItems() { + return [ + { + label: 'Toggle dark mode', + }, + ]; + }, + onSelect({ setIsOpen, setQuery, refresh }) { + toggleTheme(); + setQuery(''); + setIsOpen(true); + refresh(); + }, + templates: { + header({ createElement, Fragment }) { + return createElement( + Fragment, + {}, + createElement( + 'span', + { className: 'aa-SourceHeaderTitle' }, + 'Shortcuts' + ), + createElement('div', { className: 'aa-SourceHeaderLine' }) + ); + }, + item({ item, createElement }) { + const darkIcon = createElement( + 'svg', + { + xmlns: 'http://www.w3.org/2000/svg', + fill: 'none', + viewBox: '0 0 24 24', + stroke: 'currentColor', + }, + createElement('path', { + strokeLinecap: 'round', + strokeLinejoin: 'round', + strokeWidth: 2, + d: 'M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z', + }) + ); + const lightIcon = createElement( + 'svg', + { + xmlns: 'http://www.w3.org/2000/svg', + fill: 'none', + viewBox: '0 0 24 24', + stroke: 'currentColor', + }, + createElement('path', { + strokeLinecap: 'round', + strokeLinejoin: 'round', + strokeWidth: 2, + d: 'M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z', + }) + ); + + return createElement( + 'div', + { className: 'aa-ItemWrapper' }, + createElement( + 'div', + { className: 'aa-ItemContent' }, + createElement( + 'div', + { className: 'aa-ItemIcon' }, + isDarkThemeSelected() ? lightIcon : darkIcon + ), + createElement( + 'div', + { className: 'aa-ItemContentBody' }, + createElement( + 'div', + { className: 'aa-ItemContentTitle' }, + item.label + ) + ) + ) + ); + }, + }, + }, + ]; + }, +}; diff --git a/examples/playground/style.css b/examples/playground/style.css new file mode 100644 index 0000000000..bf841dbb12 --- /dev/null +++ b/examples/playground/style.css @@ -0,0 +1,46 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} + +body[data-theme='dark'] { + background-color: rgb(0, 0, 0); + color: rgb(183, 192, 199); +} + +.ais-Hits { + margin-top: 1rem; +} + +.ais-Hits-list { + display: grid; + gap: 1rem; + grid-template-columns: 1fr 1fr 1fr; + list-style: none; + padding: 0; +} + +.ais-Hits-item { + background: rgba(128, 126, 163, 0.08); + border-radius: 5px; + height: 100%; + min-height: 202px; + padding: 1rem; + width: 100%; +} diff --git a/examples/playground/types/Highlighted.ts b/examples/playground/types/Highlighted.ts new file mode 100644 index 0000000000..a543e461eb --- /dev/null +++ b/examples/playground/types/Highlighted.ts @@ -0,0 +1,5 @@ +import { HighlightResult } from '@algolia/client-search'; + +export type Highlighted = TRecord & { + _highlightResult: HighlightResult; +}; diff --git a/examples/playground/types/ProductHit.ts b/examples/playground/types/ProductHit.ts new file mode 100644 index 0000000000..74d7bd40f1 --- /dev/null +++ b/examples/playground/types/ProductHit.ts @@ -0,0 +1,35 @@ +import { Hit } from '@algolia/client-search'; + +export type ProductRecord = { + brand: string; + categories: string[]; + comments: number; + description: string; + free_shipping: boolean; + hierarchicalCategories: { + lvl0: string; + lvl1?: string; + lvl2?: string; + lvl3?: string; + lvl4?: string; + lvl5?: string; + lvl6?: string; + }; + image: string; + name: string; + popularity: number; + price: number; + prince_range: string; + rating: number; + sale: boolean; + sale_price: string; + type: string; + url: string; +}; + +type WithAutocompleteAnalytics = THit & { + __autocomplete_indexName: string; + __autocomplete_queryID: string; +}; + +export type ProductHit = WithAutocompleteAnalytics>; diff --git a/examples/playground/types/index.ts b/examples/playground/types/index.ts new file mode 100644 index 0000000000..5b77dc77b6 --- /dev/null +++ b/examples/playground/types/index.ts @@ -0,0 +1,2 @@ +export * from './Highlighted'; +export * from './ProductHit'; diff --git a/examples/playground/vite.config.mjs b/examples/playground/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/playground/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/preview-panel-in-modal/README.md b/examples/preview-panel-in-modal/README.md new file mode 100644 index 0000000000..a26fc00dac --- /dev/null +++ b/examples/preview-panel-in-modal/README.md @@ -0,0 +1,34 @@ +# Autocomplete preview panel in modal example + +This example shows how to display a preview panel in a modal with Autocomplete. + +

A capture of the Autocomplete preview panel in modal example

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/preview-panel-in-modal) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-preview-panel-in-modal start +``` + +Alternatively, you may use npm: + +```sh +cd examples/preview-panel-in-modal +npm install +npm start +``` + +Open to see your app. \ No newline at end of file diff --git a/examples/preview-panel-in-modal/app.tsx b/examples/preview-panel-in-modal/app.tsx new file mode 100644 index 0000000000..d74c53c918 --- /dev/null +++ b/examples/preview-panel-in-modal/app.tsx @@ -0,0 +1,134 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; +import algoliasearch from 'algoliasearch/lite'; +import { h, render } from 'preact'; + +import '@algolia/autocomplete-theme-classic'; + +const searchClient = algoliasearch( + 'latency', + '6be0576ff61c053d5f9a3225e2a90f76' +); + +autocomplete({ + container: '#autocomplete', + detachedMediaQuery: '', + defaultActiveItemId: 0, + insights: true, + getSources() { + return [ + { + sourceId: 'hits', + getItems({ query }) { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + params: { + hitsPerPage: 8, + }, + }, + ], + }); + }, + getItemUrl({ item }) { + return item.url; + }, + onActive({ item, setContext }) { + setContext({ preview: item }); + }, + templates: { + item({ item, components }) { + return ( + +
+
+ {item.name} +
+
+
+ +
+
+
+
+ ); + }, + }, + }, + { + sourceId: 'suggestions', + getItems({ query }) { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instantsearch_query_suggestions', + query, + params: { + hitsPerPage: 4, + }, + }, + ], + }); + }, + onSelect({ item, setQuery, setIsOpen, refresh }) { + setQuery(`${item.query} `); + setIsOpen(true); + refresh(); + }, + templates: { + header({ Fragment }) { + return ( + + + Can't find what you're looking for? + +
+ + ); + }, + item({ item, components }) { + return ( +
+ +
+ ); + }, + }, + }, + ]; + }, + render({ children, state, Fragment, components }, root) { + const { preview } = state.context; + + render( + +
+
{children}
+
+
+ {preview.name} +
+
+ +
+
${preview.price}
+
+ +
+
+
+
, + root + ); + }, +}); diff --git a/examples/preview-panel-in-modal/capture.png b/examples/preview-panel-in-modal/capture.png new file mode 100644 index 0000000000..832ce738e0 Binary files /dev/null and b/examples/preview-panel-in-modal/capture.png differ diff --git a/examples/preview-panel-in-modal/env.ts b/examples/preview-panel-in-modal/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/preview-panel-in-modal/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/preview-panel-in-modal/favicon.png b/examples/preview-panel-in-modal/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/preview-panel-in-modal/favicon.png differ diff --git a/examples/preview-panel-in-modal/index.html b/examples/preview-panel-in-modal/index.html new file mode 100644 index 0000000000..99b187d1cd --- /dev/null +++ b/examples/preview-panel-in-modal/index.html @@ -0,0 +1,48 @@ + + + + + + + + + Preview Panel in Modal | Autocomplete + + + +
+
+ +
+
    +
  • +
  • +
  • +
+
    +
  • +
  • +
  • +
+
    +
  • +
  • +
  • +
+
    +
  • +
  • +
  • +
+
    +
  • +
  • +
  • +
+
+
+ + + + + diff --git a/examples/preview-panel-in-modal/package.json b/examples/preview-panel-in-modal/package.json new file mode 100644 index 0000000000..e1503c3b2a --- /dev/null +++ b/examples/preview-panel-in-modal/package.json @@ -0,0 +1,29 @@ +{ + "name": "@algolia/autocomplete-example-preview-panel-in-modal", + "description": "Autocomplete example with a preview panel in a modal", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "preact": "10.13.2", + "qs": "6.11.1" + }, + "devDependencies": { + "@types/debounce-promise": "^3.1.6", + "@types/qs": "^6.9.6", + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/preview-panel-in-modal/style.css b/examples/preview-panel-in-modal/style.css new file mode 100644 index 0000000000..d61d5d649e --- /dev/null +++ b/examples/preview-panel-in-modal/style.css @@ -0,0 +1,182 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} + +body[data-theme='dark'] { + background-color: rgb(0, 0, 0); + color: rgb(183, 192, 199); +} + +.ais-Hits { + margin-top: 1rem; +} + +.ais-Hits-list { + display: grid; + gap: 1rem; + grid-template-columns: 1fr 1fr 1fr; + list-style: none; + padding: 0; +} + +.ais-Hits-item { + background: rgba(128, 126, 163, 0.08); + border-radius: 5px; + height: 100%; + min-height: 202px; + padding: 1rem; + width: 100%; +} + +.aa-Grid { + display: grid; + padding: 0 calc(var(--aa-spacing-half) / 2); + column-gap: calc(var(--aa-spacing-half) / 2); +} + +.aa-DetachedContainer .aa-Grid { + grid-template-columns: 1fr; +} + +.aa-DetachedContainer--modal .aa-Grid { + grid-template-columns: 50% 1fr; +} + +.aa-MultiGrid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + column-gap: calc(var(--aa-spacing-half) / 2); +} + +.aa-Column { + padding: calc(var(--aa-spacing-half) / 2) 0; +} + +.aa-PanelLayout { + padding: 0; + overflow-x: hidden; +} + +.aa-Detached .aa-DetachedContainer { + background: none; +} + +.aa-DetachedContainer .aa-DetachedFormContainer { + background: rgba( + var(--aa-background-color-rgb), + var(--aa-background-color-alpha) + ); + padding: calc(var(--aa-spacing-half) * 1.5); +} + +.aa-DetachedContainer .aa-Preview { + display: none; +} + +.aa-DetachedContainer--modal .aa-Preview { + display: block; +} + +.aa-PreviewImage { + display: flex; + justify-content: center; + height: 150px; + margin-bottom: var(--aa-spacing-half); + padding: var(--aa-spacing-half); + background: var(--aa-background-color); + border: 1px solid var(--aa-selected-color); + border-radius: 3px; +} + +.aa-PreviewImage img { + max-width: 100%; + object-fit: contain; +} + +.aa-PreviewTitle { + margin-bottom: var(--aa-spacing-half); + font-weight: bold; +} + +.aa-PreviewPrice { + margin-bottom: var(--aa-spacing-half); + color: var(--aa-icon-color); + font-weight: bold; +} + +.aa-PreviewDescription { + max-width: 100%; + padding: 0.3em 0; + font-size: 0.85em; + color: var(--aa-content-text-color); + overflow-x: hidden; + text-overflow: ellipsis; +} + +.aa-PreviewTitle mark, +.aa-PreviewDescription mark { + background: none; + color: var(--aa-primary-color); +} + +[data-autocomplete-source-id='hits'] { + margin-bottom: var(--aa-spacing-half); +} + +[data-autocomplete-source-id='suggestions'] .aa-List { + display: flex; + flex-wrap: wrap; + margin: 0 calc(calc(var(--aa-spacing-half) / 2) * -1px); +} + +[data-autocomplete-source-id='suggestions'] .aa-Item[aria-selected='true'] { + background: none; +} + +.aa-QuerySuggestion { + display: inline-block; + margin: calc(calc(var(--aa-spacing-half) / 2) / 2); + padding: var(--aa-spacing-half) var(--aa-spacing); + color: rgba(var(--aa-icon-color-rgb), var(--aa-icon-color-alpha)); + font-size: 0.85em; + border-width: 1px; + border-style: solid; + border-color: rgba( + var(--aa-panel-border-color-rgb), + var(--aa-panel-border-color-alpha) + ); + border-radius: 3px; +} + +.aa-QuerySuggestion mark { + background: none; + font-weight: bold; + color: currentColor; +} + +[data-autocomplete-source-id='suggestions'] + .aa-Item[aria-selected='true'] + .aa-QuerySuggestion { + background: rgba( + var(--aa-selected-color-rgb), + var(--aa-selected-color-alpha) + ); + border-color: transparent; +} diff --git a/examples/preview-panel-in-modal/vite.config.mjs b/examples/preview-panel-in-modal/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/preview-panel-in-modal/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/query-suggestions-with-categories/README.md b/examples/query-suggestions-with-categories/README.md new file mode 100644 index 0000000000..4b1fa1a260 --- /dev/null +++ b/examples/query-suggestions-with-categories/README.md @@ -0,0 +1,34 @@ +# Autocomplete Query Suggestions with categories example + +This example shows how to use Autocomplete with the [Query Suggestions plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/) and display categories for each suggestion. + +

A capture of the Autocomplete query suggestions with categories example

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/query-suggestions-with-categories) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-query-suggestions-with-categories start +``` + +Alternatively, you may use npm: + +```sh +cd examples/query-suggestions-with-categories +npm install +npm start +``` + +Open to see your app. \ No newline at end of file diff --git a/examples/query-suggestions-with-categories/app.tsx b/examples/query-suggestions-with-categories/app.tsx new file mode 100644 index 0000000000..8ffa4b3a4f --- /dev/null +++ b/examples/query-suggestions-with-categories/app.tsx @@ -0,0 +1,35 @@ +import { autocomplete } from '@algolia/autocomplete-js'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import algoliasearch from 'algoliasearch/lite'; + +import '@algolia/autocomplete-theme-classic'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams() { + return { + hitsPerPage: 10, + }; + }, + categoryAttribute: [ + 'instant_search', + 'facets', + 'exact_matches', + 'categories', + ], + itemsWithCategories: 1, + categoriesPerItem: 2, +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + insights: true, + plugins: [querySuggestionsPlugin], +}); diff --git a/examples/query-suggestions-with-categories/capture.png b/examples/query-suggestions-with-categories/capture.png new file mode 100644 index 0000000000..99ff51028d Binary files /dev/null and b/examples/query-suggestions-with-categories/capture.png differ diff --git a/examples/query-suggestions-with-categories/env.ts b/examples/query-suggestions-with-categories/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/query-suggestions-with-categories/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/query-suggestions-with-categories/favicon.png b/examples/query-suggestions-with-categories/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/query-suggestions-with-categories/favicon.png differ diff --git a/examples/query-suggestions-with-categories/index.html b/examples/query-suggestions-with-categories/index.html new file mode 100644 index 0000000000..5a780fef56 --- /dev/null +++ b/examples/query-suggestions-with-categories/index.html @@ -0,0 +1,20 @@ + + + + + + + + + Query Suggestions with categories | Autocomplete + + + +
+
+
+ + + + + diff --git a/examples/query-suggestions-with-categories/package.json b/examples/query-suggestions-with-categories/package.json new file mode 100644 index 0000000000..fbbbfd20ab --- /dev/null +++ b/examples/query-suggestions-with-categories/package.json @@ -0,0 +1,27 @@ +{ + "name": "@algolia/autocomplete-example-query-suggestion-with-categories", + "description": "Autocomplete example with Query Suggestions with categories", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-query-suggestions": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "preact": "10.13.2" + }, + "devDependencies": { + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/query-suggestions-with-categories/style.css b/examples/query-suggestions-with-categories/style.css new file mode 100644 index 0000000000..a4d3906cf3 --- /dev/null +++ b/examples/query-suggestions-with-categories/style.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/examples/query-suggestions-with-categories/vite.config.mjs b/examples/query-suggestions-with-categories/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/query-suggestions-with-categories/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/query-suggestions-with-hits/README.md b/examples/query-suggestions-with-hits/README.md new file mode 100644 index 0000000000..fdd9f74f2b --- /dev/null +++ b/examples/query-suggestions-with-hits/README.md @@ -0,0 +1,34 @@ +# Autocomplete Query Suggestions with hits example + +This example shows how to use Autocomplete with the [Query Suggestions plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/) along with displaying hits. + +

A capture of the Autocomplete query suggestions with hits example

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/query-suggestions-with-hits) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-query-suggestions-with-hits start +``` + +Alternatively, you may use npm: + +```sh +cd examples/query-suggestions-with-hits +npm install +npm start +``` + +Open to see your app. \ No newline at end of file diff --git a/examples/query-suggestions-with-hits/app.tsx b/examples/query-suggestions-with-hits/app.tsx new file mode 100644 index 0000000000..79ef992b9f --- /dev/null +++ b/examples/query-suggestions-with-hits/app.tsx @@ -0,0 +1,172 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { + autocomplete, + AutocompleteComponents, + getAlgoliaResults, + AutocompleteInsightsApi, +} from '@algolia/autocomplete-js'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import algoliasearch from 'algoliasearch/lite'; +import { h, Fragment } from 'preact'; + +import '@algolia/autocomplete-theme-classic'; + +import { ProductHit } from './types'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams() { + return { + hitsPerPage: 5, + }; + }, +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + insights: true, + plugins: [querySuggestionsPlugin], + getSources({ query, state }) { + if (!query) { + return []; + } + + return [ + { + sourceId: 'products', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + params: { + attributesToSnippet: ['name:10'], + snippetEllipsisText: '…', + }, + }, + ], + }); + }, + templates: { + header() { + return ( + + Products +
+ + ); + }, + item({ item, components }) { + return ( + + ); + }, + noResults() { + return 'No products for this query.'; + }, + }, + }, + ]; + }, +}); + +type ProductItemProps = { + hit: ProductHit; + insights: AutocompleteInsightsApi; + components: AutocompleteComponents; +}; + +function ProductItem({ hit, insights, components }: ProductItemProps) { + return ( + +
+
+ {hit.name} +
+
+
+ +
+
+ From {hit.brand} in{' '} + {hit.categories[0]} +
+ {hit.rating > 0 && ( +
+
+ {Array.from({ length: 5 }, (_value, index) => { + const isFilled = hit.rating >= index + 1; + + return ( + + + + ); + })} +
+
+ )} +
+ ${hit.price.toLocaleString()} +
+
+
+
+ + +
+
+ ); +} diff --git a/examples/query-suggestions-with-hits/capture.png b/examples/query-suggestions-with-hits/capture.png new file mode 100644 index 0000000000..bf2887ca15 Binary files /dev/null and b/examples/query-suggestions-with-hits/capture.png differ diff --git a/examples/query-suggestions-with-hits/env.ts b/examples/query-suggestions-with-hits/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/query-suggestions-with-hits/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/query-suggestions-with-hits/favicon.png b/examples/query-suggestions-with-hits/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/query-suggestions-with-hits/favicon.png differ diff --git a/examples/query-suggestions-with-hits/index.html b/examples/query-suggestions-with-hits/index.html new file mode 100644 index 0000000000..ef4b706553 --- /dev/null +++ b/examples/query-suggestions-with-hits/index.html @@ -0,0 +1,20 @@ + + + + + + + + + Query Suggestions with hits | Autocomplete + + + +
+
+
+ + + + + diff --git a/examples/query-suggestions-with-hits/package.json b/examples/query-suggestions-with-hits/package.json new file mode 100644 index 0000000000..65153f6f54 --- /dev/null +++ b/examples/query-suggestions-with-hits/package.json @@ -0,0 +1,31 @@ +{ + "name": "@algolia/autocomplete-example-query-suggestions-with-hits", + "description": "Autocomplete example with Query Suggestions and hits", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-query-suggestions": "1.19.7", + "@algolia/autocomplete-plugin-recent-searches": "1.19.7", + "@algolia/autocomplete-preset-algolia": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "@algolia/client-search": "4.16.0", + "algoliasearch": "4.16.0", + "preact": "10.13.2", + "search-insights": "^2.15.0" + }, + "devDependencies": { + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/query-suggestions-with-hits/style.css b/examples/query-suggestions-with-hits/style.css new file mode 100644 index 0000000000..a4d3906cf3 --- /dev/null +++ b/examples/query-suggestions-with-hits/style.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/examples/query-suggestions-with-hits/types/ProductHit.ts b/examples/query-suggestions-with-hits/types/ProductHit.ts new file mode 100644 index 0000000000..349301e3ca --- /dev/null +++ b/examples/query-suggestions-with-hits/types/ProductHit.ts @@ -0,0 +1,19 @@ +import { Hit } from '@algolia/client-search'; + +type ProductRecord = { + brand: string; + categories: string[]; + description: string; + image: string; + name: string; + price: number; + rating: number; + url: string; +}; + +type WithAutocompleteAnalytics = THit & { + __autocomplete_indexName: string; + __autocomplete_queryID: string; +}; + +export type ProductHit = WithAutocompleteAnalytics>; diff --git a/examples/query-suggestions-with-hits/types/index.ts b/examples/query-suggestions-with-hits/types/index.ts new file mode 100644 index 0000000000..bd26549b95 --- /dev/null +++ b/examples/query-suggestions-with-hits/types/index.ts @@ -0,0 +1 @@ +export * from './ProductHit'; diff --git a/examples/query-suggestions-with-hits/vite.config.mjs b/examples/query-suggestions-with-hits/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/query-suggestions-with-hits/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/query-suggestions-with-inline-categories/README.md b/examples/query-suggestions-with-inline-categories/README.md new file mode 100644 index 0000000000..52dd65840d --- /dev/null +++ b/examples/query-suggestions-with-inline-categories/README.md @@ -0,0 +1,34 @@ +# Autocomplete Query Suggestions with inline categories example + +This example shows how to use Autocomplete with the [Query Suggestions plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/) and display inline categories for each suggestion. + +

A capture of the Autocomplete query suggestions with inline categories example

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/query-suggestions-with-inline-categories) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-query-suggestions-with-inline-categories start +``` + +Alternatively, you may use npm: + +```sh +cd examples/query-suggestions-with-inline-categories +npm install +npm start +``` + +Open to see your app. \ No newline at end of file diff --git a/examples/query-suggestions-with-inline-categories/app.tsx b/examples/query-suggestions-with-inline-categories/app.tsx new file mode 100644 index 0000000000..b016997e05 --- /dev/null +++ b/examples/query-suggestions-with-inline-categories/app.tsx @@ -0,0 +1,98 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { autocomplete } from '@algolia/autocomplete-js'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import algoliasearch from 'algoliasearch/lite'; +import { h } from 'preact'; + +import '@algolia/autocomplete-theme-classic'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams() { + return { + hitsPerPage: 6, + }; + }, + categoryAttribute: [ + 'instant_search', + 'facets', + 'exact_matches', + 'categories', + ], + categoriesPerItem: 2, + transformSource: ({ source, onTapAhead }) => { + return { + ...source, + templates: { + ...source.templates, + item({ item, components }) { + return ( +
+
+
+ + + +
+
+
+ + {item.__autocomplete_qsCategory && ( + + in{' '} + + {item.__autocomplete_qsCategory} + + + )} +
+
+
+ +
+ +
+
+ ); + }, + }, + }; + }, +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + insights: true, + plugins: [querySuggestionsPlugin], +}); diff --git a/examples/query-suggestions-with-inline-categories/capture.png b/examples/query-suggestions-with-inline-categories/capture.png new file mode 100644 index 0000000000..e87af01c6a Binary files /dev/null and b/examples/query-suggestions-with-inline-categories/capture.png differ diff --git a/examples/query-suggestions-with-inline-categories/env.ts b/examples/query-suggestions-with-inline-categories/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/query-suggestions-with-inline-categories/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/query-suggestions-with-inline-categories/favicon.png b/examples/query-suggestions-with-inline-categories/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/query-suggestions-with-inline-categories/favicon.png differ diff --git a/examples/query-suggestions-with-inline-categories/index.html b/examples/query-suggestions-with-inline-categories/index.html new file mode 100644 index 0000000000..ce89fd3b2d --- /dev/null +++ b/examples/query-suggestions-with-inline-categories/index.html @@ -0,0 +1,20 @@ + + + + + + + + + Query Suggestions with inline categories | Autocomplete + + + +
+
+
+ + + + + diff --git a/examples/query-suggestions-with-inline-categories/package.json b/examples/query-suggestions-with-inline-categories/package.json new file mode 100644 index 0000000000..cbc5536262 --- /dev/null +++ b/examples/query-suggestions-with-inline-categories/package.json @@ -0,0 +1,27 @@ +{ + "name": "@algolia/autocomplete-example-query-suggestions-with-inline-categories", + "description": "Autocomplete example with Query Suggestions with inline categories", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-query-suggestions": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "preact": "10.13.2" + }, + "devDependencies": { + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/query-suggestions-with-inline-categories/style.css b/examples/query-suggestions-with-inline-categories/style.css new file mode 100644 index 0000000000..a4d3906cf3 --- /dev/null +++ b/examples/query-suggestions-with-inline-categories/style.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/examples/query-suggestions-with-inline-categories/vite.config.mjs b/examples/query-suggestions-with-inline-categories/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/query-suggestions-with-inline-categories/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/query-suggestions-with-recent-searches-and-categories/README.md b/examples/query-suggestions-with-recent-searches-and-categories/README.md new file mode 100644 index 0000000000..0ce13ffece --- /dev/null +++ b/examples/query-suggestions-with-recent-searches-and-categories/README.md @@ -0,0 +1,34 @@ +# Autocomplete Query Suggestions with recent searches and categories example + +This example shows how to use Autocomplete with the [Query Suggestions plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/) and the [Recent Searches plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches/) to display Query Suggestions with recent searches and categories. + +

A capture of the Autocomplete Query Suggestions with recent searches and categories example

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/query-suggestions-with-recent-searches-and-categories) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-query-suggestions-with-recent-searches-and-categories start +``` + +Alternatively, you may use npm: + +```sh +cd examples/query-suggestions-with-recent-searches-and-categories +npm install +npm start +``` + +Open to see your app. \ No newline at end of file diff --git a/examples/query-suggestions-with-recent-searches-and-categories/app.tsx b/examples/query-suggestions-with-recent-searches-and-categories/app.tsx new file mode 100644 index 0000000000..35fc3db3fc --- /dev/null +++ b/examples/query-suggestions-with-recent-searches-and-categories/app.tsx @@ -0,0 +1,39 @@ +import { autocomplete } from '@algolia/autocomplete-js'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; +import algoliasearch from 'algoliasearch/lite'; + +import '@algolia/autocomplete-theme-classic'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ + key: 'query-suggestions-history-with-category-example', + limit: 5, +}); + +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams() { + return recentSearchesPlugin.data.getAlgoliaSearchParams({ + hitsPerPage: 5, + }); + }, + categoryAttribute: [ + 'instant_search', + 'facets', + 'exact_matches', + 'categories', + ], +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + insights: true, + plugins: [recentSearchesPlugin, querySuggestionsPlugin], +}); diff --git a/examples/query-suggestions-with-recent-searches-and-categories/capture.png b/examples/query-suggestions-with-recent-searches-and-categories/capture.png new file mode 100644 index 0000000000..3e9b50d225 Binary files /dev/null and b/examples/query-suggestions-with-recent-searches-and-categories/capture.png differ diff --git a/examples/query-suggestions-with-recent-searches-and-categories/env.ts b/examples/query-suggestions-with-recent-searches-and-categories/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/query-suggestions-with-recent-searches-and-categories/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/query-suggestions-with-recent-searches-and-categories/favicon.png b/examples/query-suggestions-with-recent-searches-and-categories/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/query-suggestions-with-recent-searches-and-categories/favicon.png differ diff --git a/examples/query-suggestions-with-recent-searches-and-categories/index.html b/examples/query-suggestions-with-recent-searches-and-categories/index.html new file mode 100644 index 0000000000..d87289b8a8 --- /dev/null +++ b/examples/query-suggestions-with-recent-searches-and-categories/index.html @@ -0,0 +1,20 @@ + + + + + + + + + Query Suggestions with recent searches and categories + + + +
+
+
+ + + + + diff --git a/examples/query-suggestions-with-recent-searches-and-categories/package.json b/examples/query-suggestions-with-recent-searches-and-categories/package.json new file mode 100644 index 0000000000..f66d6e5ecf --- /dev/null +++ b/examples/query-suggestions-with-recent-searches-and-categories/package.json @@ -0,0 +1,29 @@ +{ + "name": "@algolia/autocomplete-example-query-suggestions-with-recent-searches-and-categories", + "description": "Autocomplete example with Query Suggestions with recent searches and categories", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-query-suggestions": "1.19.7", + "@algolia/autocomplete-plugin-recent-searches": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "@algolia/client-search": "4.16.0", + "algoliasearch": "4.16.0", + "preact": "10.13.2" + }, + "devDependencies": { + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/query-suggestions-with-recent-searches-and-categories/style.css b/examples/query-suggestions-with-recent-searches-and-categories/style.css new file mode 100644 index 0000000000..a4d3906cf3 --- /dev/null +++ b/examples/query-suggestions-with-recent-searches-and-categories/style.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/examples/query-suggestions-with-recent-searches-and-categories/vite.config.mjs b/examples/query-suggestions-with-recent-searches-and-categories/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/query-suggestions-with-recent-searches-and-categories/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/query-suggestions-with-recent-searches/README.md b/examples/query-suggestions-with-recent-searches/README.md new file mode 100644 index 0000000000..841da13361 --- /dev/null +++ b/examples/query-suggestions-with-recent-searches/README.md @@ -0,0 +1,34 @@ +# Autocomplete Query Suggestions with recent searches example + +This example shows how to use Autocomplete with the [Query Suggestions plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/) and the [Recent Searches plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches/). + +

A capture of the Autocomplete Query Suggestions with recent searches example

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/query-suggestions-with-recent-searches) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-query-suggestions-with-recent-searches start +``` + +Alternatively, you may use npm: + +```sh +cd examples/query-suggestions-with-recent-searches +npm install +npm start +``` + +Open to see your app. \ No newline at end of file diff --git a/examples/query-suggestions-with-recent-searches/app.tsx b/examples/query-suggestions-with-recent-searches/app.tsx new file mode 100644 index 0000000000..ae833f67d3 --- /dev/null +++ b/examples/query-suggestions-with-recent-searches/app.tsx @@ -0,0 +1,75 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { autocomplete } from '@algolia/autocomplete-js'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; +import algoliasearch from 'algoliasearch/lite'; +import { h, Fragment } from 'preact'; + +import '@algolia/autocomplete-theme-classic'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ + key: 'qs-with-rs-example', + limit: 3, + transformSource({ source }) { + return { + ...source, + templates: { + ...source.templates, + header({ state }) { + if (state.query) { + return null; + } + + return ( + + Your searches +
+ + ); + }, + }, + }; + }, +}); +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams() { + return recentSearchesPlugin.data.getAlgoliaSearchParams({ + hitsPerPage: 5, + }); + }, + transformSource({ source }) { + return { + ...source, + templates: { + ...source.templates, + header({ state }) { + if (state.query) { + return null; + } + + return ( + + Popular searches +
+ + ); + }, + }, + }; + }, +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + insights: true, + plugins: [recentSearchesPlugin, querySuggestionsPlugin], +}); diff --git a/examples/query-suggestions-with-recent-searches/capture.png b/examples/query-suggestions-with-recent-searches/capture.png new file mode 100644 index 0000000000..f3e3efda2d Binary files /dev/null and b/examples/query-suggestions-with-recent-searches/capture.png differ diff --git a/examples/query-suggestions-with-recent-searches/env.ts b/examples/query-suggestions-with-recent-searches/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/query-suggestions-with-recent-searches/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/query-suggestions-with-recent-searches/favicon.png b/examples/query-suggestions-with-recent-searches/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/query-suggestions-with-recent-searches/favicon.png differ diff --git a/examples/query-suggestions-with-recent-searches/index.html b/examples/query-suggestions-with-recent-searches/index.html new file mode 100644 index 0000000000..de88c73d18 --- /dev/null +++ b/examples/query-suggestions-with-recent-searches/index.html @@ -0,0 +1,20 @@ + + + + + + + + + Query Suggestions with Recent Searches | Autocomplete + + + +
+
+
+ + + + + diff --git a/examples/query-suggestions-with-recent-searches/package.json b/examples/query-suggestions-with-recent-searches/package.json new file mode 100644 index 0000000000..1f0a952f2b --- /dev/null +++ b/examples/query-suggestions-with-recent-searches/package.json @@ -0,0 +1,28 @@ +{ + "name": "@algolia/autocomplete-example-query-suggestions-with-recent-searches", + "description": "Autocomplete example with Query Suggestions and Recent Searches", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-query-suggestions": "1.19.7", + "@algolia/autocomplete-plugin-recent-searches": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "preact": "10.13.2" + }, + "devDependencies": { + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/query-suggestions-with-recent-searches/style.css b/examples/query-suggestions-with-recent-searches/style.css new file mode 100644 index 0000000000..a4d3906cf3 --- /dev/null +++ b/examples/query-suggestions-with-recent-searches/style.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/examples/query-suggestions-with-recent-searches/vite.config.mjs b/examples/query-suggestions-with-recent-searches/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/query-suggestions-with-recent-searches/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/query-suggestions/README.md b/examples/query-suggestions/README.md new file mode 100644 index 0000000000..e7a66cc707 --- /dev/null +++ b/examples/query-suggestions/README.md @@ -0,0 +1,34 @@ +# Autocomplete Query Suggestions example + +This example shows how to use Autocomplete with the [Query Suggestions plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/). + +

A capture of the Autocomplete query suggestions example

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/query-suggestions) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-query-suggestions start +``` + +Alternatively, you may use npm: + +```sh +cd examples/query-suggestions +npm install +npm start +``` + +Open to see your app. \ No newline at end of file diff --git a/examples/query-suggestions/app.tsx b/examples/query-suggestions/app.tsx new file mode 100644 index 0000000000..2a92af0384 --- /dev/null +++ b/examples/query-suggestions/app.tsx @@ -0,0 +1,27 @@ +import { autocomplete } from '@algolia/autocomplete-js'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import algoliasearch from 'algoliasearch/lite'; + +import '@algolia/autocomplete-theme-classic'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams() { + return { + hitsPerPage: 10, + }; + }, +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + insights: true, + plugins: [querySuggestionsPlugin], +}); diff --git a/examples/query-suggestions/capture.png b/examples/query-suggestions/capture.png new file mode 100644 index 0000000000..266918aff8 Binary files /dev/null and b/examples/query-suggestions/capture.png differ diff --git a/examples/query-suggestions/env.ts b/examples/query-suggestions/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/query-suggestions/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/query-suggestions/favicon.png b/examples/query-suggestions/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/query-suggestions/favicon.png differ diff --git a/examples/query-suggestions/index.html b/examples/query-suggestions/index.html new file mode 100644 index 0000000000..ddaebc83ee --- /dev/null +++ b/examples/query-suggestions/index.html @@ -0,0 +1,20 @@ + + + + + + + + + Query Suggestions | Autocomplete + + + +
+
+
+ + + + + diff --git a/examples/query-suggestions/package.json b/examples/query-suggestions/package.json new file mode 100644 index 0000000000..ee71da8047 --- /dev/null +++ b/examples/query-suggestions/package.json @@ -0,0 +1,27 @@ +{ + "name": "@algolia/autocomplete-example-query-suggestions", + "description": "Autocomplete example with Query Suggestions", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-query-suggestions": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "preact": "10.13.2" + }, + "devDependencies": { + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/query-suggestions/style.css b/examples/query-suggestions/style.css new file mode 100644 index 0000000000..a4d3906cf3 --- /dev/null +++ b/examples/query-suggestions/style.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/examples/query-suggestions/vite.config.mjs b/examples/query-suggestions/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/query-suggestions/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/react-17/.eslintignore b/examples/react-17/.eslintignore new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/examples/react-17/.eslintignore @@ -0,0 +1 @@ +* diff --git a/examples/react-17/.gitignore b/examples/react-17/.gitignore new file mode 100644 index 0000000000..4d29575de8 --- /dev/null +++ b/examples/react-17/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/examples/react-17/README.md b/examples/react-17/README.md new file mode 100644 index 0000000000..75053d617a --- /dev/null +++ b/examples/react-17/README.md @@ -0,0 +1,32 @@ +# Autocomplete with React 17 example + +This example shows how to integrate Autocomplete with React 17. + +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/react-17) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-react-17 start +``` + +Alternatively, you may use npm: + +```sh +cd examples/react-17 +npm install +npm start +``` + +Open to see your app. diff --git a/examples/react-17/index.html b/examples/react-17/index.html new file mode 100644 index 0000000000..98af7679ce --- /dev/null +++ b/examples/react-17/index.html @@ -0,0 +1,15 @@ + + + + + + + + + React 17 | Autocomplete + + +
+ + + diff --git a/examples/react-17/package.json b/examples/react-17/package.json new file mode 100644 index 0000000000..37aa39b836 --- /dev/null +++ b/examples/react-17/package.json @@ -0,0 +1,33 @@ +{ + "name": "@algolia/autocomplete-example-react-17", + "description": "Autocomplete example with React 17", + "version": "1.19.7", + "private": true, + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@algolia/autocomplete-core": "1.19.7", + "@algolia/autocomplete-preset-algolia": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "react": "17.0.2", + "react-dom": "17.0.2" + }, + "devDependencies": { + "@algolia/client-search": "4.16.0", + "@types/react": "^19.0.7", + "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "4.2.1", + "typescript": "^4.4.2", + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "react" + ] +} diff --git a/examples/react-17/public/favicon.png b/examples/react-17/public/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/react-17/public/favicon.png differ diff --git a/examples/react-17/public/manifest.json b/examples/react-17/public/manifest.json new file mode 100644 index 0000000000..25f260d5b8 --- /dev/null +++ b/examples/react-17/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React Renderer", + "name": "Autocomplete React Renderer", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/react-17/public/robots.txt b/examples/react-17/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/examples/react-17/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/examples/react-17/src/App.css b/examples/react-17/src/App.css new file mode 100644 index 0000000000..dc02ab37eb --- /dev/null +++ b/examples/react-17/src/App.css @@ -0,0 +1,10 @@ +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} + +.aa-Panel { + max-width: 640px; + width: 100%; +} diff --git a/examples/react-17/src/App.tsx b/examples/react-17/src/App.tsx new file mode 100644 index 0000000000..140ae72d1c --- /dev/null +++ b/examples/react-17/src/App.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +import { Autocomplete } from './Autocomplete'; +import './App.css'; + +export function App() { + return ( +
+ +
+ ); +} diff --git a/examples/react-17/src/Autocomplete.tsx b/examples/react-17/src/Autocomplete.tsx new file mode 100644 index 0000000000..9e3db75122 --- /dev/null +++ b/examples/react-17/src/Autocomplete.tsx @@ -0,0 +1,211 @@ +import { + AutocompleteOptions, + AutocompleteState, + createAutocomplete, +} from '@algolia/autocomplete-core'; +import { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia'; +import { Hit } from '@algolia/client-search'; +import algoliasearch from 'algoliasearch/lite'; +import React from 'react'; + +import { ClearIcon } from './ClearIcon'; +import { Highlight } from './Highlight'; +import { SearchIcon } from './SearchIcon'; + +const searchClient = algoliasearch( + 'latency', + '6be0576ff61c053d5f9a3225e2a90f76' +); + +type AutocompleteItem = Hit<{ + brand: string; + categories: string[]; + image: string; + name: string; + objectID: string; + url: string; +}>; + +export function Autocomplete( + props: Partial> +) { + const [autocompleteState, setAutocompleteState] = React.useState< + AutocompleteState + >({ + collections: [], + completion: null, + context: {}, + isOpen: false, + query: '', + activeItemId: null, + status: 'idle', + }); + const autocomplete = React.useMemo( + () => + createAutocomplete< + AutocompleteItem, + React.BaseSyntheticEvent, + React.MouseEvent, + React.KeyboardEvent + >({ + onStateChange({ state }) { + setAutocompleteState(state); + }, + insights: true, + getSources() { + return [ + { + sourceId: 'products', + getItems({ query }) { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + params: { + hitsPerPage: 5, + }, + }, + ], + }); + }, + getItemUrl({ item }) { + return item.url; + }, + }, + ]; + }, + ...props, + }), + [props] + ); + const inputRef = React.useRef(null); + const formRef = React.useRef(null); + const panelRef = React.useRef(null); + const { getEnvironmentProps } = autocomplete; + + React.useEffect(() => { + if (!formRef.current || !panelRef.current || !inputRef.current) { + return undefined; + } + + const { onTouchStart, onTouchMove, onMouseDown } = getEnvironmentProps({ + formElement: formRef.current, + inputElement: inputRef.current, + panelElement: panelRef.current, + }); + + window.addEventListener('mousedown', onMouseDown); + window.addEventListener('touchstart', onTouchStart); + window.addEventListener('touchmove', onTouchMove); + + return () => { + window.removeEventListener('mousedown', onMouseDown); + window.removeEventListener('touchstart', onTouchStart); + window.removeEventListener('touchmove', onTouchMove); + }; + }, [getEnvironmentProps, autocompleteState.isOpen]); + + return ( +
+
+
+ +
+
+ +
+
+ +
+
+ + {autocompleteState.isOpen && ( +
+
+ {autocompleteState.collections.map((collection, index) => { + const { source, items } = collection; + + return ( +
+ {items.length > 0 && ( +
    + {items.map((item) => { + return ( +
  • +
    +
    +
    + {item.name} +
    +
    +
    + +
    +
    + By {item.brand} in{' '} + {item.categories[0]} +
    +
    +
    +
    + +
    +
    +
  • + ); + })} +
+ )} +
+ ); + })} +
+
+ )} +
+ ); +} diff --git a/examples/react-17/src/ClearIcon.tsx b/examples/react-17/src/ClearIcon.tsx new file mode 100644 index 0000000000..854f6d2139 --- /dev/null +++ b/examples/react-17/src/ClearIcon.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +export function ClearIcon(props: React.SVGProps) { + return ( + + + + ); +} diff --git a/examples/react-17/src/Highlight.tsx b/examples/react-17/src/Highlight.tsx new file mode 100644 index 0000000000..6cf4d0e594 --- /dev/null +++ b/examples/react-17/src/Highlight.tsx @@ -0,0 +1,41 @@ +import { parseAlgoliaHitHighlight } from '@algolia/autocomplete-preset-algolia'; +import { createElement, Fragment, JSX } from 'react'; + +type HighlightHitParams = { + /** + * The Algolia hit whose attribute to retrieve the highlighted parts from. + */ + hit: THit; + /** + * The attribute to retrieve the highlighted parts from. + * + * You can use the array syntax to reference nested attributes. + */ + attribute: keyof THit | Array; + /** + * The tag name to use for highlighted parts. + * + * @default "mark" + */ + tagName?: string; +}; + +export function Highlight({ + hit, + attribute, + tagName = 'mark', +}: HighlightHitParams): JSX.Element { + return createElement( + Fragment, + {}, + parseAlgoliaHitHighlight({ hit, attribute }).map( + ({ value, isHighlighted }, index) => { + if (isHighlighted) { + return createElement(tagName, { key: index }, value); + } + + return value; + } + ) + ); +} diff --git a/examples/react-17/src/SearchIcon.tsx b/examples/react-17/src/SearchIcon.tsx new file mode 100644 index 0000000000..294dc6a534 --- /dev/null +++ b/examples/react-17/src/SearchIcon.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +export function SearchIcon(props: React.SVGProps) { + return ( + + + + ); +} diff --git a/examples/react-17/src/index.css b/examples/react-17/src/index.css new file mode 100644 index 0000000000..ef5e83c89c --- /dev/null +++ b/examples/react-17/src/index.css @@ -0,0 +1,14 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem 0; +} diff --git a/examples/react-17/src/index.tsx b/examples/react-17/src/index.tsx new file mode 100644 index 0000000000..fbb2f9650a --- /dev/null +++ b/examples/react-17/src/index.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import '@algolia/autocomplete-theme-classic'; + +import './index.css'; +import { App } from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/examples/react-17/tsconfig.json b/examples/react-17/tsconfig.json new file mode 100644 index 0000000000..9d379a3c4a --- /dev/null +++ b/examples/react-17/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"] +} diff --git a/examples/react-17/vite.config.mjs b/examples/react-17/vite.config.mjs new file mode 100644 index 0000000000..be938b9211 --- /dev/null +++ b/examples/react-17/vite.config.mjs @@ -0,0 +1,10 @@ +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 1234, + }, +}); diff --git a/examples/react-instantsearch/.gitignore b/examples/react-instantsearch/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/examples/react-instantsearch/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/react-instantsearch/README.md b/examples/react-instantsearch/README.md new file mode 100644 index 0000000000..6abb40558c --- /dev/null +++ b/examples/react-instantsearch/README.md @@ -0,0 +1,34 @@ +# Autocomplete with React InstantSearch example + +This example shows how to integrate Autocomplete with [React InstantSearch](https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/react/). + +

A capture of the Autocomplete with React InstantSearch demo

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/react-instantsearch) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-react-instantsearch dev +``` + +Alternatively, you may use npm: + +```sh +cd examples/react-instantsearch +npm install +npm run dev +``` + +Open to see your app. diff --git a/examples/react-instantsearch/capture.png b/examples/react-instantsearch/capture.png new file mode 100644 index 0000000000..f98c9a6d34 Binary files /dev/null and b/examples/react-instantsearch/capture.png differ diff --git a/examples/react-instantsearch/index.html b/examples/react-instantsearch/index.html new file mode 100644 index 0000000000..2c245f810c --- /dev/null +++ b/examples/react-instantsearch/index.html @@ -0,0 +1,22 @@ + + + + + + + + React InstantSearch | Autocomplete + + + + +
+ + + diff --git a/examples/react-instantsearch/package.json b/examples/react-instantsearch/package.json new file mode 100644 index 0000000000..4bd42a8790 --- /dev/null +++ b/examples/react-instantsearch/package.json @@ -0,0 +1,35 @@ +{ + "name": "@algolia/autocomplete-example-react-instantsearch", + "description": "Autocomplete example with React InstantSearch", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-query-suggestions": "1.19.7", + "@algolia/autocomplete-plugin-recent-searches": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-instantsearch": "7.12.1" + }, + "devDependencies": { + "@types/react": "^19.0.7", + "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "4.2.1", + "typescript": "4.5.4", + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "react" + ] +} diff --git a/examples/react-instantsearch/src/App.css b/examples/react-instantsearch/src/App.css new file mode 100644 index 0000000000..e153240389 --- /dev/null +++ b/examples/react-instantsearch/src/App.css @@ -0,0 +1,113 @@ +* { + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, + Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); +} + +a { + color: var(--aa-primary-color); + text-decoration: none; +} + +.header { + background: rgb(252 252 255 / 92%); + box-shadow: 0 0 0 1px rgba(35, 38, 59, 0.05), + 0 1px 3px 0 rgba(35, 38, 59, 0.15); + padding: 0.5rem 0; + position: fixed; + top: 0; + width: 100%; +} + +.header-wrapper { + align-items: center; + display: grid; + grid-template-columns: 100px 1fr; +} + +.header-nav { + font-weight: 500; +} + +.wrapper { + margin: 0 auto; + max-width: 1200px; + padding: 0 1.5rem; + width: 100%; +} + +.container { + margin-top: 3.5rem; + padding: 1.5rem; + display: grid; + gap: 1rem; + grid-template-columns: 1fr 3fr; +} + +/* Autocomplete */ + +.aa-Panel { + position: fixed; +} + +/* InstantSearch */ + +.ais-Hits-list { + display: grid; + gap: 1rem; + grid-template-columns: 1fr 1fr 1fr; +} + +.ais-Hits-item { + padding: 1rem !important; +} + +.hit { + align-items: center; + display: grid; + gap: 1rem; +} + +.hit h1 { + font-size: 1rem; +} + +.hit p { + font-size: 0.8rem; + opacity: 0.8; +} + +.hit-image { + align-items: center; + display: flex; + height: 100px; + justify-content: center; +} + +.hit-image img { + max-height: 100%; +} + +.ais-HierarchicalMenu-item--selected.ais-HierarchicalMenu-item--parent + > div:first-of-type + .ais-HierarchicalMenu-label { + font-weight: bold; +} + +.ais-HierarchicalMenu-item--selected:not(.ais-HierarchicalMenu-item--parent) + .ais-HierarchicalMenu-label { + font-weight: bold; +} + +.ais-Pagination { + display: flex; + justify-content: center; + margin: 2rem 0; +} diff --git a/examples/react-instantsearch/src/App.tsx b/examples/react-instantsearch/src/App.tsx new file mode 100644 index 0000000000..655e6aa7a7 --- /dev/null +++ b/examples/react-instantsearch/src/App.tsx @@ -0,0 +1,68 @@ +import algoliasearch from 'algoliasearch/lite'; +import { + Configure, + HierarchicalMenu, + Hits, + InstantSearch, + Pagination, +} from 'react-instantsearch'; + +import { Autocomplete, Hit } from './components'; +import { + INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES, + INSTANT_SEARCH_INDEX_NAME, +} from './constants'; +import { Panel } from './widgets/Panel'; + +import './App.css'; + +function App() { + const searchClient = algoliasearch( + 'latency', + '6be0576ff61c053d5f9a3225e2a90f76' + ); + + return ( +
+ +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+
+
+ ); +} + +export default App; diff --git a/examples/react-instantsearch/src/components/Autocomplete.tsx b/examples/react-instantsearch/src/components/Autocomplete.tsx new file mode 100644 index 0000000000..09ef695c6c --- /dev/null +++ b/examples/react-instantsearch/src/components/Autocomplete.tsx @@ -0,0 +1,247 @@ +import type { SearchClient } from 'algoliasearch/lite'; +import type { BaseItem } from '@algolia/autocomplete-core'; +import type { AutocompleteOptions } from '@algolia/autocomplete-js'; + +import { + createElement, + Fragment, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { createRoot, Root } from 'react-dom/client'; + +import { + useHierarchicalMenu, + usePagination, + useSearchBox, +} from 'react-instantsearch'; +import { autocomplete } from '@algolia/autocomplete-js'; +import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import { debounce } from '@algolia/autocomplete-shared'; + +import { + INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES, + INSTANT_SEARCH_INDEX_NAME, + INSTANT_SEARCH_QUERY_SUGGESTIONS, +} from '../constants'; + +import '@algolia/autocomplete-theme-classic'; + +type AutocompleteProps = Partial> & { + searchClient: SearchClient; + className?: string; +}; + +type SetInstantSearchUiStateOptions = { + query: string; + category?: string; +}; + +export function Autocomplete({ + searchClient, + className, + ...autocompleteProps +}: AutocompleteProps) { + const autocompleteContainer = useRef(null); + const panelRootRef = useRef(null); + const rootRef = useRef(null); + + const { query, refine: setQuery } = useSearchBox(); + const { items: categories, refine: setCategory } = useHierarchicalMenu({ + attributes: INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES, + }); + const { refine: setPage } = usePagination(); + + const [instantSearchUiState, setInstantSearchUiState] = + useState({ query }); + const debouncedSetInstantSearchUiState = debounce( + setInstantSearchUiState, + 500 + ); + + useEffect(() => { + setQuery(instantSearchUiState.query); + instantSearchUiState.category && setCategory(instantSearchUiState.category); + setPage(0); + }, [instantSearchUiState]); + + const currentCategory = useMemo( + () => categories.find(({ isRefined }) => isRefined)?.value, + [categories] + ); + + const plugins = useMemo(() => { + const recentSearches = createLocalStorageRecentSearchesPlugin({ + key: 'instantsearch', + limit: 3, + transformSource({ source }) { + return { + ...source, + onSelect({ item }) { + setInstantSearchUiState({ + query: item.label, + category: item.category, + }); + }, + }; + }, + }); + + const querySuggestionsInCategory = createQuerySuggestionsPlugin({ + searchClient, + indexName: INSTANT_SEARCH_QUERY_SUGGESTIONS, + getSearchParams() { + return recentSearches.data!.getAlgoliaSearchParams({ + hitsPerPage: 3, + facetFilters: [ + `${INSTANT_SEARCH_INDEX_NAME}.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0]}.value:${currentCategory}`, + ], + }); + }, + transformSource({ source }) { + return { + ...source, + sourceId: 'querySuggestionsInCategoryPlugin', + onSelect({ item }) { + setInstantSearchUiState({ + query: item.query, + category: item.__autocomplete_qsCategory, + }); + }, + getItems(params) { + if (!currentCategory) { + return []; + } + + return source.getItems(params); + }, + templates: { + ...source.templates, + header({ items }) { + if (items.length === 0) { + return ; + } + + return ( + + + In {currentCategory} + + + + ); + }, + }, + }; + }, + }); + + const querySuggestions = createQuerySuggestionsPlugin({ + searchClient, + indexName: INSTANT_SEARCH_QUERY_SUGGESTIONS, + getSearchParams() { + if (!currentCategory) { + return recentSearches.data!.getAlgoliaSearchParams({ + hitsPerPage: 6, + }); + } + + return recentSearches.data!.getAlgoliaSearchParams({ + hitsPerPage: 3, + facetFilters: [ + `${INSTANT_SEARCH_INDEX_NAME}.facets.exact_matches.${INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0]}.value:-${currentCategory}`, + ], + }); + }, + categoryAttribute: [ + INSTANT_SEARCH_INDEX_NAME, + 'facets', + 'exact_matches', + INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES[0], + ], + transformSource({ source }) { + return { + ...source, + sourceId: 'querySuggestionsPlugin', + onSelect({ item }) { + setInstantSearchUiState({ + query: item.query, + category: item.__autocomplete_qsCategory || '', + }); + }, + getItems(params) { + if (!params.state.query) { + return []; + } + + return source.getItems(params); + }, + templates: { + ...source.templates, + header({ items }) { + if (!currentCategory || items.length === 0) { + return ; + } + + return ( + + + In other categories + + + + ); + }, + }, + }; + }, + }); + + return [recentSearches, querySuggestionsInCategory, querySuggestions]; + }, [currentCategory]); + + useEffect(() => { + if (!autocompleteContainer.current) { + return; + } + + const autocompleteInstance = autocomplete({ + ...autocompleteProps, + container: autocompleteContainer.current, + initialState: { query }, + insights: true, + plugins, + onReset() { + setInstantSearchUiState({ query: '', category: currentCategory }); + }, + onSubmit({ state }) { + setInstantSearchUiState({ query: state.query }); + }, + onStateChange({ prevState, state }) { + if (prevState.query !== state.query) { + debouncedSetInstantSearchUiState({ + query: state.query, + }); + } + }, + renderer: { createElement, Fragment, render: () => {} }, + render({ children }, root) { + if (!panelRootRef.current || rootRef.current !== root) { + rootRef.current = root; + + panelRootRef.current?.unmount(); + panelRootRef.current = createRoot(root); + } + + panelRootRef.current.render(children); + }, + }); + + return () => autocompleteInstance.destroy(); + }, [plugins]); + + return
; +} diff --git a/examples/react-instantsearch/src/components/Hit.tsx b/examples/react-instantsearch/src/components/Hit.tsx new file mode 100644 index 0000000000..32762e5f0a --- /dev/null +++ b/examples/react-instantsearch/src/components/Hit.tsx @@ -0,0 +1,31 @@ +import type { Hit as AlgoliaHit } from 'instantsearch.js/es/types'; + +import { Snippet } from 'react-instantsearch'; + +type HitProps = { + hit: AlgoliaHit<{ + name: string; + image: string; + brand: string; + categories: string[]; + }>; +}; + +export function Hit({ hit }: HitProps) { + return ( +
+
+ {hit.name} +
+
+

+ +

+
+ By {hit.brand} in{' '} + {hit.categories[0]} +
+
+
+ ); +} diff --git a/examples/react-instantsearch/src/components/index.ts b/examples/react-instantsearch/src/components/index.ts new file mode 100644 index 0000000000..f57f9cd6c9 --- /dev/null +++ b/examples/react-instantsearch/src/components/index.ts @@ -0,0 +1,2 @@ +export * from './Autocomplete'; +export * from './Hit'; diff --git a/examples/react-instantsearch/src/constants.ts b/examples/react-instantsearch/src/constants.ts new file mode 100644 index 0000000000..3be804cad4 --- /dev/null +++ b/examples/react-instantsearch/src/constants.ts @@ -0,0 +1,7 @@ +export const INSTANT_SEARCH_INDEX_NAME = 'instant_search'; +export const INSTANT_SEARCH_QUERY_SUGGESTIONS = + 'instant_search_demo_query_suggestions'; +export const INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTES = [ + 'hierarchicalCategories.lvl0', + 'hierarchicalCategories.lvl1', +]; diff --git a/examples/react-instantsearch/src/favicon.png b/examples/react-instantsearch/src/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/react-instantsearch/src/favicon.png differ diff --git a/examples/react-instantsearch/src/main.tsx b/examples/react-instantsearch/src/main.tsx new file mode 100644 index 0000000000..7baec16db4 --- /dev/null +++ b/examples/react-instantsearch/src/main.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; + +import App from './App'; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); +root.render( + + + +); diff --git a/examples/react-instantsearch/src/widgets/Panel.tsx b/examples/react-instantsearch/src/widgets/Panel.tsx new file mode 100644 index 0000000000..713149fbc6 --- /dev/null +++ b/examples/react-instantsearch/src/widgets/Panel.tsx @@ -0,0 +1,16 @@ +export function Panel({ + children, + header, + footer, +}: React.PropsWithChildren<{ + header?: React.ReactNode; + footer?: React.ReactNode; +}>) { + return ( +
+ {header &&
{header}
} +
{children}
+ {footer &&
{footer}
} +
+ ); +} diff --git a/examples/react-instantsearch/tsconfig.json b/examples/react-instantsearch/tsconfig.json new file mode 100644 index 0000000000..1c82339880 --- /dev/null +++ b/examples/react-instantsearch/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], +} diff --git a/examples/react-instantsearch/vite.config.mjs b/examples/react-instantsearch/vite.config.mjs new file mode 100644 index 0000000000..a8a5bba96a --- /dev/null +++ b/examples/react-instantsearch/vite.config.mjs @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 1234, + }, +}); diff --git a/examples/react/.gitignore b/examples/react/.gitignore new file mode 100644 index 0000000000..4d29575de8 --- /dev/null +++ b/examples/react/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/examples/react/README.md b/examples/react/README.md new file mode 100644 index 0000000000..e2e5a1c33b --- /dev/null +++ b/examples/react/README.md @@ -0,0 +1,32 @@ +# Autocomplete with React example + +This example shows how to integrate Autocomplete with [React 18](https://reactjs.org/blog/2022/03/29/react-v18.html). + +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/react) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-react start +``` + +Alternatively, you may use npm: + +```sh +cd examples/react +npm install +npm start +``` + +Open to see your app. diff --git a/examples/react/index.html b/examples/react/index.html new file mode 100644 index 0000000000..8e18d04518 --- /dev/null +++ b/examples/react/index.html @@ -0,0 +1,15 @@ + + + + + + + + + React | Autocomplete + + +
+ + + diff --git a/examples/react/package.json b/examples/react/package.json new file mode 100644 index 0000000000..f084d46502 --- /dev/null +++ b/examples/react/package.json @@ -0,0 +1,33 @@ +{ + "name": "@algolia/autocomplete-example-react", + "description": "Autocomplete example with React", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@algolia/client-search": "4.16.0", + "@types/react": "^19.0.7", + "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "4.2.1", + "typescript": "^4.4.2", + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "react" + ] +} diff --git a/examples/react/public/favicon.png b/examples/react/public/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/react/public/favicon.png differ diff --git a/examples/react/src/App.tsx b/examples/react/src/App.tsx new file mode 100644 index 0000000000..678c27528f --- /dev/null +++ b/examples/react/src/App.tsx @@ -0,0 +1,114 @@ +import { useEffect, useRef, createElement, Fragment } from 'react'; +import { createRoot } from 'react-dom/client'; +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; +import algoliasearch from 'algoliasearch/lite'; + +import type { AutocompleteComponents } from '@algolia/autocomplete-js'; +import type { Hit } from '@algolia/client-search'; +import type { Root } from 'react-dom/client'; + +import '@algolia/autocomplete-theme-classic'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +type ProductHit = Hit<{ + brand: string; + categories: string[]; + description: string; + image: string; + name: string; + price: number; + rating: number; + type: string; + url: string; +}>; + +export default function App() { + const containerRef = useRef(null); + const panelRootRef = useRef(null); + const rootRef = useRef(null); + + useEffect(() => { + if (!containerRef.current) { + return undefined; + } + + const search = autocomplete({ + container: containerRef.current, + placeholder: 'Search', + insights: true, + getSources({ query }) { + return [ + { + sourceId: 'products', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + }, + ], + }); + }, + templates: { + item({ item, components }) { + return ; + }, + noResults() { + return 'No products matching.'; + }, + }, + }, + ]; + }, + renderer: { createElement, Fragment, render: () => {} }, + render({ children }, root) { + if (!panelRootRef.current || rootRef.current !== root) { + rootRef.current = root; + + panelRootRef.current?.unmount(); + panelRootRef.current = createRoot(root); + } + + panelRootRef.current.render(children); + }, + }); + + return () => { + search.destroy(); + }; + }, []); + + return
; +} + +type ProductItemProps = { + hit: ProductHit; + components: AutocompleteComponents; +}; + +function ProductItem({ hit, components }: ProductItemProps) { + return ( +
+
+
+ {hit.name} +
+ +
+
+ +
+
+ By {hit.brand} in{' '} + {hit.categories[0]} +
+
+
+
+ ); +} diff --git a/examples/react/src/index.tsx b/examples/react/src/index.tsx new file mode 100644 index 0000000000..c38eb22c81 --- /dev/null +++ b/examples/react/src/index.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +import './styles.css'; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); +root.render( + + + +); diff --git a/examples/react/src/styles.css b/examples/react/src/styles.css new file mode 100644 index 0000000000..ff781463c2 --- /dev/null +++ b/examples/react/src/styles.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/examples/react/tsconfig.json b/examples/react/tsconfig.json new file mode 100644 index 0000000000..a273b0cfc0 --- /dev/null +++ b/examples/react/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ] +} diff --git a/examples/react/vite.config.mjs b/examples/react/vite.config.mjs new file mode 100644 index 0000000000..a8a5bba96a --- /dev/null +++ b/examples/react/vite.config.mjs @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 1234, + }, +}); diff --git a/examples/recently-viewed-items/README.md b/examples/recently-viewed-items/README.md new file mode 100644 index 0000000000..aa2a2d2779 --- /dev/null +++ b/examples/recently-viewed-items/README.md @@ -0,0 +1,34 @@ +# Autocomplete recently viewed items custom plugin example + +This example shows how to build an Autocomplete [plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/plugins/) displaying recently viewed items. + +

A capture of the Autocomplete recently viewed items custom plugin demo

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/master/examples/recently-viewed-items) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-recently-viewed-items start +``` + +Alternatively, you may use npm: + +```sh +cd examples/recently-viewed-items +npm install +npm start +``` + +Open to see your app. \ No newline at end of file diff --git a/examples/recently-viewed-items/app.tsx b/examples/recently-viewed-items/app.tsx new file mode 100644 index 0000000000..4a01bdaa55 --- /dev/null +++ b/examples/recently-viewed-items/app.tsx @@ -0,0 +1,117 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { + autocomplete, + AutocompleteComponents, + getAlgoliaResults, +} from '@algolia/autocomplete-js'; +import algoliasearch from 'algoliasearch/lite'; +import { h, Fragment } from 'preact'; + +import '@algolia/autocomplete-theme-classic'; + +import { createLocalStorageRecentlyViewedItems } from './recentlyViewedItemsPlugin'; +import { ProductHit } from './types'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +const recentlyViewedItems = createLocalStorageRecentlyViewedItems({ + key: 'RECENTLY_VIEWED', + limit: 5, +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + insights: true, + plugins: [recentlyViewedItems], + getSources({ query }) { + if (!query) { + return []; + } + + return [ + { + sourceId: 'products', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + params: { + attributesToSnippet: ['name:10', 'description:35'], + snippetEllipsisText: '…', + }, + }, + ], + }); + }, + onSelect({ item }) { + recentlyViewedItems.data.addItem({ + id: item.objectID, + label: item.name, + image: item.image, + url: item.url, + }); + }, + templates: { + header() { + return ( + + Products +
+ + ); + }, + item({ item, components }) { + return ( + + ); + }, + noResults() { + return 'No products for this query.'; + }, + }, + }, + ]; + }, +}); + +type ProductItemProps = { + hit: ProductHit; + components: AutocompleteComponents; +}; + +function AutocompleteProductItem({ hit, components }: ProductItemProps) { + return ( +
+
+
+ {hit.name} +
+
+
+ +
+
+
+
+ +
+
+ ); +} diff --git a/examples/recently-viewed-items/capture.png b/examples/recently-viewed-items/capture.png new file mode 100644 index 0000000000..d38449e685 Binary files /dev/null and b/examples/recently-viewed-items/capture.png differ diff --git a/examples/recently-viewed-items/env.ts b/examples/recently-viewed-items/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/recently-viewed-items/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/recently-viewed-items/favicon.png b/examples/recently-viewed-items/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/recently-viewed-items/favicon.png differ diff --git a/examples/recently-viewed-items/index.html b/examples/recently-viewed-items/index.html new file mode 100644 index 0000000000..e3715354ab --- /dev/null +++ b/examples/recently-viewed-items/index.html @@ -0,0 +1,20 @@ + + + + + + + + + Recently Viewed Items | Autocomplete + + + +
+
+
+ + + + + diff --git a/examples/recently-viewed-items/package.json b/examples/recently-viewed-items/package.json new file mode 100644 index 0000000000..d59631e2f3 --- /dev/null +++ b/examples/recently-viewed-items/package.json @@ -0,0 +1,30 @@ +{ + "name": "@algolia/autocomplete-example-recently-viewed-items", + "description": "Autocomplete example with Recently Viewed Items", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-recent-searches": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "@algolia/client-search": "4.16.0", + "algoliasearch": "4.16.0", + "preact": "10.13.2" + }, + "devDependencies": { + "@algolia/autocomplete-core": "1.19.7", + "@algolia/autocomplete-shared": "1.19.7", + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/recently-viewed-items/recentlyViewedItemsPlugin.tsx b/examples/recently-viewed-items/recentlyViewedItemsPlugin.tsx new file mode 100644 index 0000000000..887dd0c3a9 --- /dev/null +++ b/examples/recently-viewed-items/recentlyViewedItemsPlugin.tsx @@ -0,0 +1,136 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { + AutocompletePlugin, + AutocompleteSource, + AutocompleteState, +} from '@algolia/autocomplete-js'; +import { + createLocalStorageRecentSearchesPlugin, + search, + SearchParams, +} from '@algolia/autocomplete-plugin-recent-searches'; +import { MaybePromise } from '@algolia/autocomplete-shared'; +import { h, Fragment } from 'preact'; + +import { Highlighted } from './types'; + +type RecentlyViewedRecord = { + id: string; + label: string; + url: string; + image: string; +}; + +type CreateLocalStorageRecentlyViewedItemsParams< + TItem extends RecentlyViewedRecord +> = { + key: string; + limit?: number; + search?(params: SearchParams): Array>; + transformSource?(params: { + source: AutocompleteSource; + state: AutocompleteState; + onRemove(id: string): void; + }): AutocompleteSource; +}; + +type RecentlyViewedItemsPluginData = { + addItem(item: TItem): void; + removeItem(id: string): void; + getAll(query?: string): MaybePromise>>; +}; + +export function createLocalStorageRecentlyViewedItems< + TItem extends RecentlyViewedRecord +>( + params: CreateLocalStorageRecentlyViewedItemsParams +): AutocompletePlugin> { + const { onReset, onSubmit, subscribe, ...plugin } = + createLocalStorageRecentSearchesPlugin({ + ...params, + search(params) { + if (params.query) { + return []; + } + + return search(params); + }, + transformSource({ source, onRemove, state }) { + const transformedSource = params.transformSource + ? params.transformSource({ source, onRemove, state }) + : source; + + return { + ...transformedSource, + sourceId: 'recentlyViewedItemsPlugin', + getItemUrl({ item }) { + return item.url; + }, + templates: { + ...transformedSource.templates, + header() { + return ( + + Recently viewed +
+ + ); + }, + item({ item, components }) { + return ( + +
+ {item.image ? ( +
+ {item.label} +
+ ) : ( +
+ + + +
+ )} +
+
+ +
+
+
+
+ +
+
+ ); + }, + }, + }; + }, + }); + const { getAlgoliaSearchParams, ...data } = plugin.data; + + return { + ...plugin, + name: 'aa.localStorageRecentlyViewedItemsPlugin', + data, + }; +} diff --git a/examples/recently-viewed-items/style.css b/examples/recently-viewed-items/style.css new file mode 100644 index 0000000000..a4d3906cf3 --- /dev/null +++ b/examples/recently-viewed-items/style.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/examples/recently-viewed-items/types/Highlighted.ts b/examples/recently-viewed-items/types/Highlighted.ts new file mode 100644 index 0000000000..b45ae01ecf --- /dev/null +++ b/examples/recently-viewed-items/types/Highlighted.ts @@ -0,0 +1,7 @@ +export type Highlighted = TItem & { + _highlightResult: { + label: { + value: string; + }; + }; +}; diff --git a/examples/recently-viewed-items/types/ProductHit.ts b/examples/recently-viewed-items/types/ProductHit.ts new file mode 100644 index 0000000000..ae763bfa10 --- /dev/null +++ b/examples/recently-viewed-items/types/ProductHit.ts @@ -0,0 +1,13 @@ +import { Hit } from '@algolia/client-search'; + +export type ProductItem = { + name: string; + image: string; + url: string; + description: string; +}; + +export type ProductHit = Hit & { + __autocomplete_indexName: string; + __autocomplete_queryID: string; +}; diff --git a/examples/recently-viewed-items/types/index.ts b/examples/recently-viewed-items/types/index.ts new file mode 100644 index 0000000000..5b77dc77b6 --- /dev/null +++ b/examples/recently-viewed-items/types/index.ts @@ -0,0 +1,2 @@ +export * from './Highlighted'; +export * from './ProductHit'; diff --git a/examples/recently-viewed-items/vite.config.mjs b/examples/recently-viewed-items/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/recently-viewed-items/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/redirect-url/README.md b/examples/redirect-url/README.md new file mode 100644 index 0000000000..060c40787f --- /dev/null +++ b/examples/redirect-url/README.md @@ -0,0 +1,39 @@ +# Autocomplete Redirect URL example + +This example helps you quickly bootstrap a basic Autocomplete with redirect functionality enabled. + +

A capture of the Autocomplete redirect URL example

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/redirect-url) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-redirect-url start +``` + +Alternatively, you may use npm: + +```sh +cd examples/redirect-url +npm install +npm start +``` + +Open to see your app. + +Typing “algolia” or "Apple - AirPods - White" in the app's input presents a redirect item at the top of the drop-down menu. Either submitting the form or selecting the redirect item will execute the redirect to the corresponding URL that was configured for its rule. + +## Additional resources +Learn [how to get started with Autocomplete](https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/getting-started/) or [how to configure the Redirect URL plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-redirect-url) in the Algolia documentation. diff --git a/examples/redirect-url/app.tsx b/examples/redirect-url/app.tsx new file mode 100644 index 0000000000..0ad2a58dd7 --- /dev/null +++ b/examples/redirect-url/app.tsx @@ -0,0 +1,48 @@ +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; +import { createRedirectUrlPlugin } from '@algolia/autocomplete-plugin-redirect-url'; +import algoliasearch from 'algoliasearch/lite'; + +import '@algolia/autocomplete-theme-classic'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +autocomplete<{ name: string }>({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + insights: true, + plugins: [createRedirectUrlPlugin()], + getSources({ query }) { + return [ + { + sourceId: 'demo-source', + templates: { + item(params) { + const { item, html } = params; + return html`${item.name}`; + }, + }, + getItemInputValue({ item }) { + return item.name; + }, + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + params: { + ruleContexts: ['enable-redirect-url'], // note: only needed for this demo data + hitsPerPage: 10, + }, + }, + ], + }); + }, + }, + ]; + }, +}); diff --git a/examples/redirect-url/capture.png b/examples/redirect-url/capture.png new file mode 100644 index 0000000000..bf30926c34 Binary files /dev/null and b/examples/redirect-url/capture.png differ diff --git a/examples/redirect-url/env.ts b/examples/redirect-url/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/redirect-url/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/redirect-url/favicon.png b/examples/redirect-url/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/redirect-url/favicon.png differ diff --git a/examples/redirect-url/index.html b/examples/redirect-url/index.html new file mode 100644 index 0000000000..da9e326e37 --- /dev/null +++ b/examples/redirect-url/index.html @@ -0,0 +1,20 @@ + + + + + + + + + Redirect URL | Autocomplete + + + +
+
+
+ + + + + diff --git a/examples/redirect-url/package.json b/examples/redirect-url/package.json new file mode 100644 index 0000000000..9c17a2419e --- /dev/null +++ b/examples/redirect-url/package.json @@ -0,0 +1,27 @@ +{ + "name": "@algolia/autocomplete-example-redirect-url", + "description": "Autocomplete example for Redirect URL plugin", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-redirect-url": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "algoliasearch": "4.16.0", + "preact": "10.13.2" + }, + "devDependencies": { + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/redirect-url/style.css b/examples/redirect-url/style.css new file mode 100644 index 0000000000..a4d3906cf3 --- /dev/null +++ b/examples/redirect-url/style.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/examples/redirect-url/vite.config.mjs b/examples/redirect-url/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/redirect-url/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/reshape/README.md b/examples/reshape/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/reshape/app.tsx b/examples/reshape/app.tsx new file mode 100644 index 0000000000..5dd62d22bb --- /dev/null +++ b/examples/reshape/app.tsx @@ -0,0 +1,72 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { autocomplete } from '@algolia/autocomplete-js'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; +import { h, Fragment } from 'preact'; +import { pipe } from 'ramda'; + +import '@algolia/autocomplete-theme-classic'; + +import { groupBy, limit, uniqBy } from './functions'; +import { productsPlugin } from './productsPlugin'; +import { searchClient } from './searchClient'; + +const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ + key: 'search', + limit: 10, +}); +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams() { + return { + hitsPerPage: 10, + }; + }, +}); + +const dedupeAndLimitSuggestions = pipe( + uniqBy(({ source, item }) => + source.sourceId === 'querySuggestionsPlugin' ? item.query : item.label + ), + limit(4) +); +const groupByCategory = groupBy((hit) => hit.categories[0], { + getSource({ name, items }) { + return { + getItems() { + return items.slice(0, 3); + }, + templates: { + header() { + return ( + + {name} +
+ + ); + }, + }, + }; + }, +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + debug: true, + openOnFocus: true, + insights: true, + plugins: [recentSearchesPlugin, querySuggestionsPlugin, productsPlugin], + reshape({ sourcesBySourceId }) { + const { recentSearchesPlugin, querySuggestionsPlugin, products, ...rest } = + sourcesBySourceId; + + return [ + dedupeAndLimitSuggestions(recentSearchesPlugin, querySuggestionsPlugin), + groupByCategory(products), + Object.values(rest), + ]; + }, +}); diff --git a/examples/reshape/env.ts b/examples/reshape/env.ts new file mode 100644 index 0000000000..6eef24529d --- /dev/null +++ b/examples/reshape/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/reshape/favicon.png b/examples/reshape/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/reshape/favicon.png differ diff --git a/examples/reshape/functions/AutocompleteReshapeFunction.ts b/examples/reshape/functions/AutocompleteReshapeFunction.ts new file mode 100644 index 0000000000..180794b117 --- /dev/null +++ b/examples/reshape/functions/AutocompleteReshapeFunction.ts @@ -0,0 +1,12 @@ +import { + AutocompleteReshapeSource, + BaseItem, +} from '@algolia/autocomplete-core'; + +export type AutocompleteReshapeFunction = < + TItem extends BaseItem +>( + ...params: TParams[] +) => ( + ...expressions: Array> +) => Array>; diff --git a/examples/reshape/functions/groupBy.ts b/examples/reshape/functions/groupBy.ts new file mode 100644 index 0000000000..66df8754d3 --- /dev/null +++ b/examples/reshape/functions/groupBy.ts @@ -0,0 +1,65 @@ +import { BaseItem } from '@algolia/autocomplete-core'; +import { AutocompleteSource } from '@algolia/autocomplete-js'; +import { flatten } from '@algolia/autocomplete-shared'; + +import { AutocompleteReshapeFunction } from './AutocompleteReshapeFunction'; +import { normalizeReshapeSources } from './normalizeReshapeSources'; + +export type GroupByOptions< + TItem extends BaseItem, + TSource extends AutocompleteSource +> = { + getSource(params: { name: string; items: TItem[] }): Partial; +}; + +export const groupBy: AutocompleteReshapeFunction = < + TItem extends BaseItem, + TSource extends AutocompleteSource = AutocompleteSource +>( + predicate: (value: TItem) => string, + options: GroupByOptions +) => { + return function runGroupBy(...rawSources) { + const sources = normalizeReshapeSources(rawSources); + + if (sources.length === 0) { + return []; + } + + // Since we create multiple sources from a single one, we take the first one + // as reference to create the new sources from. + const referenceSource = sources[0]; + const items = flatten(sources.map((source) => source.getItems())); + const groupedItems = items.reduce>((acc, item) => { + const key = predicate(item as TItem); + + if (!acc.hasOwnProperty(key)) { + acc[key] = []; + } + + acc[key].push(item as TItem); + + return acc; + }, {}); + + return Object.entries(groupedItems).map(([groupName, groupItems]) => { + const userSource = options.getSource({ + name: groupName, + items: groupItems, + }); + + return { + ...referenceSource, + sourceId: groupName, + getItems() { + return groupItems; + }, + ...userSource, + templates: { + ...((referenceSource as any).templates as any), + ...(userSource as any).templates, + }, + }; + }); + }; +}; diff --git a/examples/reshape/functions/index.ts b/examples/reshape/functions/index.ts new file mode 100644 index 0000000000..2af93dae42 --- /dev/null +++ b/examples/reshape/functions/index.ts @@ -0,0 +1,3 @@ +export * from './groupBy'; +export * from './limit'; +export * from './uniqBy'; diff --git a/examples/reshape/functions/limit.ts b/examples/reshape/functions/limit.ts new file mode 100644 index 0000000000..58e0e1242a --- /dev/null +++ b/examples/reshape/functions/limit.ts @@ -0,0 +1,26 @@ +import { AutocompleteReshapeFunction } from './AutocompleteReshapeFunction'; +import { normalizeReshapeSources } from './normalizeReshapeSources'; + +export const limit: AutocompleteReshapeFunction = (value) => { + return function runLimit(...rawSources) { + const sources = normalizeReshapeSources(rawSources); + const limitPerSource = Math.ceil(value / sources.length); + let sharedLimitRemaining = value; + + return sources.map((source, index) => { + const isLastSource = index === sources.length - 1; + const sourceLimit = isLastSource + ? sharedLimitRemaining + : Math.min(limitPerSource, sharedLimitRemaining); + const items = source.getItems().slice(0, sourceLimit); + sharedLimitRemaining = Math.max(sharedLimitRemaining - items.length, 0); + + return { + ...source, + getItems() { + return items; + }, + }; + }); + }; +}; diff --git a/examples/reshape/functions/normalizeReshapeSources.ts b/examples/reshape/functions/normalizeReshapeSources.ts new file mode 100644 index 0000000000..b9e1794753 --- /dev/null +++ b/examples/reshape/functions/normalizeReshapeSources.ts @@ -0,0 +1,13 @@ +import { + AutocompleteReshapeSource, + BaseItem, +} from '@algolia/autocomplete-core'; +import { flatten } from '@algolia/autocomplete-shared'; + +// We filter out falsy values because dynamic sources may not exist at every render. +// We flatten to support pipe operators from functional libraries like Ramda. +export function normalizeReshapeSources( + sources: Array> +) { + return flatten(sources).filter(Boolean); +} diff --git a/examples/reshape/functions/uniqBy.ts b/examples/reshape/functions/uniqBy.ts new file mode 100644 index 0000000000..331af3873c --- /dev/null +++ b/examples/reshape/functions/uniqBy.ts @@ -0,0 +1,41 @@ +import { + AutocompleteReshapeSource, + BaseItem, +} from '@algolia/autocomplete-core'; + +import { AutocompleteReshapeFunction } from './AutocompleteReshapeFunction'; +import { normalizeReshapeSources } from './normalizeReshapeSources'; + +type UniqByPredicate = (params: { + source: AutocompleteReshapeSource; + item: TItem; +}) => TItem; + +export const uniqBy: AutocompleteReshapeFunction> = < + TItem extends BaseItem +>( + predicate +) => { + return function runUniqBy(...rawSources) { + const sources = normalizeReshapeSources(rawSources); + const seen = new Set(); + + return sources.map((source) => { + const items = source.getItems().filter((item) => { + const appliedItem = predicate({ source, item }); + const hasSeen = seen.has(appliedItem); + + seen.add(appliedItem); + + return !hasSeen; + }); + + return { + ...source, + getItems() { + return items; + }, + }; + }); + }; +}; diff --git a/examples/reshape/index.html b/examples/reshape/index.html new file mode 100644 index 0000000000..d802bd4a74 --- /dev/null +++ b/examples/reshape/index.html @@ -0,0 +1,20 @@ + + + + + + + + + Reshape API | Autocomplete + + + +
+
+
+ + + + + diff --git a/examples/reshape/package.json b/examples/reshape/package.json new file mode 100644 index 0000000000..01ac36e8a4 --- /dev/null +++ b/examples/reshape/package.json @@ -0,0 +1,34 @@ +{ + "name": "@algolia/autocomplete-example-reshape", + "description": "Autocomplete example with the Reshape API", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.19.7", + "@algolia/autocomplete-plugin-query-suggestions": "1.19.7", + "@algolia/autocomplete-plugin-recent-searches": "1.19.7", + "@algolia/autocomplete-preset-algolia": "1.19.7", + "@algolia/autocomplete-shared": "1.19.7", + "@algolia/autocomplete-theme-classic": "1.19.7", + "@algolia/client-search": "4.16.0", + "algoliasearch": "4.16.0", + "preact": "10.13.2", + "ramda": "0.27.1", + "search-insights": "^2.15.0" + }, + "devDependencies": { + "@algolia/autocomplete-core": "1.19.7", + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/reshape/productsPlugin.tsx b/examples/reshape/productsPlugin.tsx new file mode 100644 index 0000000000..1c88527142 --- /dev/null +++ b/examples/reshape/productsPlugin.tsx @@ -0,0 +1,249 @@ +/** @jsxRuntime classic */ +/** @jsx h */ +import { + AutocompleteComponents, + getAlgoliaResults, +} from '@algolia/autocomplete-js'; +import { h, Fragment } from 'preact'; + +import { searchClient } from './searchClient'; +import { ProductRecord } from './types'; + +export const productsPlugin = { + getSources({ query }) { + if (!query) { + return []; + } + + return [ + { + sourceId: 'products', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + params: { + attributesToSnippet: ['name:10', 'description:35'], + snippetEllipsisText: '…', + hitsPerPage: 15, + }, + }, + ], + transformResponse({ hits }) { + const [bestBuyHits] = hits; + + return bestBuyHits.map((hit) => ({ + ...hit, + comments: hit.popularity % 100, + sale: hit.free_shipping, + // eslint-disable-next-line @typescript-eslint/camelcase + sale_price: hit.free_shipping + ? (hit.price - hit.price / 10).toFixed(2) + : hit.price.toString(), + })); + }, + }); + }, + templates: { + header() { + return ( + + Products +
+ + ); + }, + item({ item, components }) { + return ; + }, + noResults() { + return 'No products for this query.'; + }, + }, + }, + ]; + }, +}; + +type ProductItemProps = { + hit: ProductHit; + components: AutocompleteComponents; +}; + +function ProductItem({ hit, components }: ProductItemProps) { + return ( + +
+
+ {hit.name} +
+ +
+
+ +
+
+ By {hit.brand} in{' '} + {hit.categories[0]} +
+ +
+ {hit.rating > 0 && ( +
+
+ {Array.from({ length: 5 }, (_value, index) => { + const isFilled = hit.rating >= index + 1; + + return ( + + + + ); + })} +
+
+ )} +
+ + + + {hit.comments.toLocaleString()} +
+
+ +
+
+
+ + ${hit.sale_price.toLocaleString()} + {' '} + {hit.sale && ( + + ${hit.price.toLocaleString()} + + )} +
+ {hit.sale && ( + + On sale + + )} +
+
+
+
+ +
+ + +
+
+ ); +} diff --git a/examples/reshape/searchClient.ts b/examples/reshape/searchClient.ts new file mode 100644 index 0000000000..7805f8b2cd --- /dev/null +++ b/examples/reshape/searchClient.ts @@ -0,0 +1,6 @@ +import algoliasearch from 'algoliasearch/lite'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; + +export const searchClient = algoliasearch(appId, apiKey); diff --git a/examples/reshape/style.css b/examples/reshape/style.css new file mode 100644 index 0000000000..46a9ccf942 --- /dev/null +++ b/examples/reshape/style.css @@ -0,0 +1,25 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} + +body[data-theme='dark'] { + background-color: rgb(0, 0, 0); + color: rgb(183, 192, 199); +} diff --git a/examples/reshape/types/Highlighted.ts b/examples/reshape/types/Highlighted.ts new file mode 100644 index 0000000000..a543e461eb --- /dev/null +++ b/examples/reshape/types/Highlighted.ts @@ -0,0 +1,5 @@ +import { HighlightResult } from '@algolia/client-search'; + +export type Highlighted = TRecord & { + _highlightResult: HighlightResult; +}; diff --git a/examples/reshape/types/ProductHit.ts b/examples/reshape/types/ProductHit.ts new file mode 100644 index 0000000000..74d7bd40f1 --- /dev/null +++ b/examples/reshape/types/ProductHit.ts @@ -0,0 +1,35 @@ +import { Hit } from '@algolia/client-search'; + +export type ProductRecord = { + brand: string; + categories: string[]; + comments: number; + description: string; + free_shipping: boolean; + hierarchicalCategories: { + lvl0: string; + lvl1?: string; + lvl2?: string; + lvl3?: string; + lvl4?: string; + lvl5?: string; + lvl6?: string; + }; + image: string; + name: string; + popularity: number; + price: number; + prince_range: string; + rating: number; + sale: boolean; + sale_price: string; + type: string; + url: string; +}; + +type WithAutocompleteAnalytics = THit & { + __autocomplete_indexName: string; + __autocomplete_queryID: string; +}; + +export type ProductHit = WithAutocompleteAnalytics>; diff --git a/examples/reshape/types/index.ts b/examples/reshape/types/index.ts new file mode 100644 index 0000000000..5b77dc77b6 --- /dev/null +++ b/examples/reshape/types/index.ts @@ -0,0 +1,2 @@ +export * from './Highlighted'; +export * from './ProductHit'; diff --git a/examples/reshape/vite.config.mjs b/examples/reshape/vite.config.mjs new file mode 100644 index 0000000000..d4c3a1acc4 --- /dev/null +++ b/examples/reshape/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 1234, + }, +}); diff --git a/examples/slack-with-emojis-and-commands/.eslintignore b/examples/slack-with-emojis-and-commands/.eslintignore new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/examples/slack-with-emojis-and-commands/.eslintignore @@ -0,0 +1 @@ +* diff --git a/examples/slack-with-emojis-and-commands/.gitignore b/examples/slack-with-emojis-and-commands/.gitignore new file mode 100644 index 0000000000..4d29575de8 --- /dev/null +++ b/examples/slack-with-emojis-and-commands/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/examples/slack-with-emojis-and-commands/README.md b/examples/slack-with-emojis-and-commands/README.md new file mode 100644 index 0000000000..d865dc10ff --- /dev/null +++ b/examples/slack-with-emojis-and-commands/README.md @@ -0,0 +1,34 @@ +# Autocomplete Slack example with emojis and slash commands + +This example shows a Slack compose box with emojis and slash commands powered by Autocomplete. + +

A capture of the Autocomplete Slack example with emojis and slash commands

+ +## Demo + +[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/slack-with-emojis-and-commands) + +## How to run this example locally + +### 1. Clone this repository + +```sh +git clone git@github.com:algolia/autocomplete.git +``` + +### 2. Install the dependencies and run the server + +```sh +yarn +yarn workspace @algolia/autocomplete-example-slack start +``` + +Alternatively, you may use npm: + +```sh +cd examples/slack-with-emojis-and-commands +npm install +npm start +``` + +Open to see your app. diff --git a/examples/slack-with-emojis-and-commands/capture.png b/examples/slack-with-emojis-and-commands/capture.png new file mode 100644 index 0000000000..30845e4ef9 Binary files /dev/null and b/examples/slack-with-emojis-and-commands/capture.png differ diff --git a/examples/slack-with-emojis-and-commands/index.html b/examples/slack-with-emojis-and-commands/index.html new file mode 100644 index 0000000000..3362700f1a --- /dev/null +++ b/examples/slack-with-emojis-and-commands/index.html @@ -0,0 +1,15 @@ + + + + + + + + + Slack with emojis and slash commands | Autocomplete + + +
+ + + diff --git a/examples/slack-with-emojis-and-commands/package.json b/examples/slack-with-emojis-and-commands/package.json new file mode 100644 index 0000000000..6325da48c2 --- /dev/null +++ b/examples/slack-with-emojis-and-commands/package.json @@ -0,0 +1,35 @@ +{ + "name": "@algolia/autocomplete-example-slack", + "description": "Autocomplete example powering a Slack compose box with emojis and slash commands.", + "version": "1.19.7", + "private": true, + "license": "MIT", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@algolia/autocomplete-core": "1.19.7", + "@algolia/autocomplete-preset-algolia": "1.19.7", + "algoliasearch": "4.16.0", + "react": "17.0.2", + "react-dom": "17.0.2", + "textarea-caret": "3.1.0" + }, + "devDependencies": { + "@algolia/client-search": "4.16.0", + "@types/react": "^19.0.7", + "@types/react-dom": "^19.0.3", + "@types/textarea-caret": "3.0.1", + "@vitejs/plugin-react": "4.2.1", + "typescript": "^4.4.2", + "vite": "5.0.7" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/slack-with-emojis-and-commands/public/avatar.jpg b/examples/slack-with-emojis-and-commands/public/avatar.jpg new file mode 100644 index 0000000000..f8eaf7f2eb Binary files /dev/null and b/examples/slack-with-emojis-and-commands/public/avatar.jpg differ diff --git a/examples/slack-with-emojis-and-commands/public/favicon.png b/examples/slack-with-emojis-and-commands/public/favicon.png new file mode 100644 index 0000000000..084fdfdfc2 Binary files /dev/null and b/examples/slack-with-emojis-and-commands/public/favicon.png differ diff --git a/examples/slack-with-emojis-and-commands/public/index.html b/examples/slack-with-emojis-and-commands/public/index.html new file mode 100644 index 0000000000..c3eff37220 --- /dev/null +++ b/examples/slack-with-emojis-and-commands/public/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + + Slack with emojis and slash commands | Autocomplete + + + +
+ + diff --git a/examples/slack-with-emojis-and-commands/public/manifest.json b/examples/slack-with-emojis-and-commands/public/manifest.json new file mode 100644 index 0000000000..dcb8e05cd7 --- /dev/null +++ b/examples/slack-with-emojis-and-commands/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Slack", + "name": "Slack with emojis and slash commands | Autocomplete", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/slack-with-emojis-and-commands/public/robots.txt b/examples/slack-with-emojis-and-commands/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/examples/slack-with-emojis-and-commands/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/examples/slack-with-emojis-and-commands/src/App.css b/examples/slack-with-emojis-and-commands/src/App.css new file mode 100644 index 0000000000..963cc1dacf --- /dev/null +++ b/examples/slack-with-emojis-and-commands/src/App.css @@ -0,0 +1,277 @@ +body { + height: 100vh; + width: 100vw; + background-color: rgba(249, 250, 251, 1); + padding: 1.5rem; +} + +.container { + margin-left: auto; + margin-right: auto; + width: 100%; + max-width: 40rem; +} + +.box { + padding: 1.5rem; + color: rgba(31, 41, 55, 1); + display: grid; + gap: 1.5rem; + grid-auto-flow: row; +} + +.box-user { + gap: 1rem; + align-items: center; + grid-auto-flow: column; + grid-auto-columns: max-content; + display: grid; +} + +.user-avatar { + border-radius: 0.25rem; + width: 5rem; + height: 5rem; +} + +.user-info { + grid-auto-flow: row; + display: grid; +} + +.user-heading { + gap: 0.5rem; + align-items: center; + grid-auto-flow: column; + grid-auto-columns: max-content; + display: grid; +} + +.user-name { + font-weight: 600; +} + +.user-status { + background-color: rgba(5, 150, 105, 1); + border-radius: 9999px; + width: 0.5rem; + height: 0.5rem; + margin-top: 0.125rem; +} + +.user-handle { + color: rgba(156, 163, 175, 1); +} + +.box-intro { + color: rgba(75, 85, 99, 1); + font-size: 1.125rem; + line-height: 1.75rem; +} + +.box-compose { + position: relative; +} + +.box-textbox { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + padding-left: 1rem; + padding-right: 1rem; + background-color: rgba(255, 255, 255, 1); + border-color: rgba(229, 231, 235, 1); + border-top-width: 2px; + border-left-width: 2px; + border-right-width: 2px; + border-top-left-radius: 0.375rem; + border-top-right-radius: 0.375rem; + resize: none; + width: 100%; + height: 8rem; +} + +.box-form { + grid-auto-flow: row; + display: grid; +} + +.box-help { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + padding-left: 1rem; + padding-right: 1rem; + background-color: rgba(243, 244, 246, 0.6); + border-color: rgba(229, 231, 235, 1); + border-bottom-width: 2px; + border-left-width: 2px; + border-right-width: 2px; + border-bottom-left-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; + color: rgba(107, 114, 128, 1); + font-size: 0.875rem; + line-height: 1.25rem; + column-gap: 0.75rem; + grid-auto-flow: column; + grid-auto-columns: max-content; + display: grid; +} + +.box-help kbd { + color: rgba(107, 114, 128, 0.8); + font-size: 0.75rem; + line-height: 1rem; + padding-top: 0.125rem; + padding-bottom: 0.125rem; + padding-left: 0.375rem; + padding-right: 0.375rem; + background-color: rgba(229, 231, 235, 1); + border-radius: 0.25rem; +} + +.autocomplete-panel { + padding: 0.25rem; + background-color: rgba(255, 255, 255, 1); + border-color: rgba(229, 231, 235, 1); + border-width: 1px; + border-radius: 0.375rem; + overflow-y: scroll; + row-gap: 0.75rem; + grid-auto-rows: auto; + width: 18rem; + max-height: 14rem; + display: grid; + position: absolute; + box-shadow: 0 20px 25px -5px rgba(255, 255, 255, 0.1), + 0 10px 10px -5px rgba(255, 255, 255, 0.04), + 0 1px 3px 0 rgba(255, 255, 255, 0.1), 0 1px 2px 0 rgba(255, 255, 255, 0.06); +} + +.autocomplete-panel:empty { + display: none; +} + +.autocomplete-loading { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: rgba(37, 99, 235, 1); +} + +.autocomplete-loading-icon { + display: block; + height: 2rem; + width: 2rem; + margin-left: auto; + margin-right: auto; +} + +.autocomplete-header { + color: rgba(156, 163, 175, 1); + text-transform: uppercase; + font-size: 0.75rem; + line-height: 1rem; + margin-bottom: 0.5rem; +} + +.autocomplete-source { + display: grid; +} + +.autocomplete-source-stalled { + filter: grayscale(1); + opacity: 0.8; +} + +.autocomplete-item-selected { + background-color: rgba(243, 244, 246, 1); +} + +.source-emojis { + row-gap: 0.25rem; + grid-template-columns: repeat(8, minmax(0, 1fr)); + grid-auto-rows: min-content; +} + +.source-emojis .autocomplete-item { + text-align: center; + border-radius: 0.25rem; + cursor: pointer; + font-size: 1.25rem; + line-height: 1.75rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.source-commands { + row-gap: 0.25rem; + grid-auto-flow: row; +} + +.source-commands .autocomplete-item { + padding-right: 0.5rem; + border-radius: 0.25rem; + cursor: pointer; + column-gap: 0.5rem; + grid-auto-flow: column; + grid-auto-columns: max-content; + display: grid; +} + +.source-commands .autocomplete-item-icon { + padding: 0.625rem; + border-radius: 0.25rem; + width: 3rem; + height: 3rem; +} + +.source-commands .autocomplete-item-icon-videochat { + color: rgba(96, 165, 250, 1); + background-color: rgba(219, 234, 254, 1); +} + +.source-commands .autocomplete-item-icon-gif { + color: rgba(248, 113, 113, 1); + background-color: rgba(254, 226, 226, 1); +} + +.source-commands .autocomplete-item-icon-survey { + color: rgba(52, 211, 153, 1); + background-color: rgba(209, 250, 229, 1); +} + +.source-commands .autocomplete-item-content { + justify-content: space-between; + display: grid; +} + +.source-commands .autocomplete-item-title { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.source-commands .autocomplete-item-description { + color: rgba(156, 163, 175, 1); + font-size: 0.75rem; + line-height: 1rem; +} + +.highlighted { + color: rgba(96, 165, 250, 1); + padding-top: 0.125rem; + padding-bottom: 0.125rem; + padding-left: 0.25rem; + padding-right: 0.25rem; + background-color: rgba(219, 234, 254, 1); + border-radius: 0.25rem; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} diff --git a/examples/slack-with-emojis-and-commands/src/App.tsx b/examples/slack-with-emojis-and-commands/src/App.tsx new file mode 100644 index 0000000000..68ad49f4c4 --- /dev/null +++ b/examples/slack-with-emojis-and-commands/src/App.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import { Autocomplete } from './Autocomplete'; + +import './App.css'; + +export function App() { + return ( +
+ +
+ ); +} diff --git a/examples/slack-with-emojis-and-commands/src/Autocomplete.tsx b/examples/slack-with-emojis-and-commands/src/Autocomplete.tsx new file mode 100644 index 0000000000..1f4e3a35b4 --- /dev/null +++ b/examples/slack-with-emojis-and-commands/src/Autocomplete.tsx @@ -0,0 +1,313 @@ +import { + AutocompleteApi, + AutocompleteOptions, + AutocompleteReshapeSource, + InternalAutocompleteSource, +} from '@algolia/autocomplete-core'; +import { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia'; +import { Hit } from '@algolia/client-search'; +import algoliasearch from 'algoliasearch/lite'; +import React, { useEffect, useRef } from 'react'; + +import { commands } from './commands'; +import { CommandsSource } from './components/CommandsSource'; +import { EmojisSource } from './components/EmojisSource'; +import { useAutocomplete } from './hooks'; +import { Command, Emoji } from './types'; +import { + getActiveToken, + getCaretCoordinates, + groupBy, + isValidCommandSlug, + isValidEmojiSlug, + replaceAt, +} from './utils'; + +const searchClient = algoliasearch( + 'latency', + 'cc9db90d2f0e20780aa21362bb41dfd4' +); + +export function Autocomplete( + props: Partial>> +) { + const inputRef = useRef(null); + const { autocomplete, state } = useAutocomplete({ + ...props, + id: 'emoji-autocomplete', + defaultActiveItemId: 0, + placeholder: 'Jot something down', + autoFocus: true, + insights: true, + getSources({ query }) { + const cursorPosition = inputRef.current?.selectionEnd || 0; + const activeToken = getActiveToken(query, cursorPosition); + + if (activeToken?.word && isValidEmojiSlug(activeToken?.word)) { + return [ + { + sourceId: 'emojis', + getItems() { + const cleanQuery = activeToken.word + .replaceAll(':', '') + .replaceAll('-', ' '); + + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'autocomplete_emojis', + query: cleanQuery, + params: { + hitsPerPage: 100, + }, + }, + ], + }); + }, + onSelect({ item, setQuery }) { + const [index] = activeToken.range; + const replacement = `${(item as Hit).symbol} `; + const newQuery = replaceAt( + query, + replacement, + index, + activeToken.word.length + ); + + setQuery(newQuery); + + if (inputRef.current) { + inputRef.current.selectionEnd = index + replacement.length; + } + }, + }, + ]; + } + + if ( + activeToken?.word && + activeToken?.range[0] === 0 && + isValidCommandSlug(activeToken?.word) + ) { + return [ + { + sourceId: 'commands', + getItems() { + return commands.filter(({ slug }) => + slug.startsWith(activeToken?.word.slice(1)) + ); + }, + onSelect({ item, setQuery }) { + const [index] = activeToken.range; + const replacement = `/${item.slug} `; + const newQuery = replaceAt( + query, + replacement, + index, + activeToken.word.length + ); + + setQuery(newQuery); + + if (inputRef.current) { + inputRef.current.selectionEnd = index + replacement.length; + } + }, + }, + ]; + } + + return []; + }, + reshape({ sourcesBySourceId }) { + const { emojis, commands } = sourcesBySourceId; + + if (emojis) { + const emojisByGroup = groupBy( + (emojis as AutocompleteReshapeSource>).getItems(), + (item) => item.group + ); + + return Object.keys(emojisByGroup).map((group) => { + return { + ...emojis, + sourceId: `emojis/${group}`, + getItems() { + return emojisByGroup[group]; + }, + }; + }); + } + + if (commands) { + return [commands]; + } + + return []; + }, + }); + const cursorPosition = inputRef.current?.selectionEnd || 0; + const activeToken = getActiveToken(state.query, cursorPosition); + const { top, height } = getCaretCoordinates(inputRef.current); + const inputProps = autocomplete.getInputProps({ + inputElement: (inputRef.current as unknown) as HTMLInputElement, + }); + + useEffect(() => { + if ( + activeToken?.word && + isValidEmojiSlug(activeToken.word) && + activeToken.word.endsWith(':') && + state.status === 'idle' + ) { + const [exactSlugMatch] = state.collections.map(({ items }) => + items.find((item) => item.slug === activeToken.word.replaceAll(':', '')) + ); + + if (exactSlugMatch) { + const [index] = activeToken.range; + const replacement = (exactSlugMatch as Hit).symbol; + const newQuery = replaceAt( + state.query, + replacement, + index, + activeToken.word.length + ); + + autocomplete.setQuery(newQuery); + + requestAnimationFrame(() => { + autocomplete.setIsOpen(false); + }); + + if (inputRef.current) { + inputRef.current.selectionEnd = index + replacement.length; + } + } + } + }, [ + activeToken, + state.status, + state.collections, + autocomplete, + inputRef, + cursorPosition, + ]); + + return ( +
+
+
+ +
+
+

Leon Hendricks

+
+ Online +
+
+

leon

+
+
+

+ This is the very beginning of your direct message history with{' '} + @leon +

+
+
+