├── .github └── workflows │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── rollup.config.js ├── test.html ├── user-select-contain.css ├── user-select-contain.js ├── user-select-contain.js.flow ├── user-select-contain.mjs └── user-select-contain.mjs.flow /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | permissions: 8 | contents: read 9 | id-token: write 10 | 11 | jobs: 12 | publish-npm: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version: 22 19 | registry-url: https://registry.npmjs.org/ 20 | cache: npm 21 | - run: npm ci 22 | - run: npm test 23 | - run: npm version ${TAG_NAME} --git-tag-version=false 24 | env: 25 | TAG_NAME: ${{ github.event.release.tag_name }} 26 | - run: npm whoami; npm --ignore-scripts publish --provenance 27 | env: 28 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | yarn.lock 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 GitHub, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # user-select: contain polyfill 2 | 3 | Polyfills [`user-select: contain`](https://developer.mozilla.org/en-US/docs/Web/CSS/user-select) property value by implementing the behavior on a CSS class name `user-select-contain`. 4 | 5 | This is only implemented in Internet Explorer right now. 6 | 7 | - [CSS Basic User Interface Module Level 4](https://drafts.csswg.org/css-ui-4/#propdef-user-select) 8 | - [Firefox bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1036853) 9 | - [Chrome bug](https://bugs.chromium.org/p/chromium/issues/detail?id=658129) 10 | - [`wpt.fyi`](https://wpt.fyi/results/css/css-ui/parsing/user-select-valid.html?label=master&label=experimental&aligned&q=user-select) 11 | 12 | ## Installation 13 | 14 | ```sh 15 | $ npm install user-select-contain-polyfill 16 | ``` 17 | 18 | ## Usage 19 | 20 | ### JS 21 | 22 | ```js 23 | import "user-select-contain-polyfill"; 24 | ``` 25 | 26 | ### CSS 27 | 28 | ```css 29 | @import "user-select-contain-polyfill.css"; 30 | ``` 31 | 32 | ### HTML 33 | 34 | ```html 35 | 36 | 50ce349853 37 | 38 | ``` 39 | 40 | ## Development 41 | 42 | ```sh 43 | $ npm install 44 | $ npm run build 45 | ``` 46 | 47 | ## License 48 | 49 | Distributed under the MIT license. See LICENSE for details. 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user-select-contain-polyfill", 3 | "version": "1.0.0", 4 | "description": "Polyfills `user-select: contain` property value", 5 | "main": "user-select-contain.js", 6 | "module": "user-select-contain.mjs", 7 | "files": [ 8 | "user-select-contain.css", 9 | "user-select-contain.js", 10 | "user-select-contain.js.flow", 11 | "user-select-contain.mjs", 12 | "user-select-contain.mjs.flow" 13 | ], 14 | "scripts": { 15 | "build": "rollup -c", 16 | "prepublishOnly": "npm run build" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/github/user-select-contain-polyfill.git" 21 | }, 22 | "author": "GitHub, Inc.", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/github/user-select-contain-polyfill/issues" 26 | }, 27 | "homepage": "https://github.com/github/user-select-contain-polyfill#readme", 28 | "devDependencies": { 29 | "rollup": "^0.66.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const pkg = require("./package.json"); 2 | 3 | export default { 4 | input: pkg["module"], 5 | output: [ 6 | { 7 | file: pkg["main"], 8 | format: "umd" 9 | } 10 | ] 11 | }; 12 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 18 | 19 | 20 |Triple click to select me.
21 |This part won't be selected, but try to select me and nothing else.
22 | 23 | 24 | -------------------------------------------------------------------------------- /user-select-contain.css: -------------------------------------------------------------------------------- 1 | .user-select-contain { 2 | -ms-user-select: element; 3 | -ms-user-select: contain; 4 | user-select: contain; 5 | } 6 | -------------------------------------------------------------------------------- /user-select-contain.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | function supportsUserSelectContain() { 8 | const el = document.createElement("div"); 9 | el.style.cssText = "-ms-user-select: element; user-select: contain;"; 10 | return ( 11 | el.style.getPropertyValue("-ms-user-select") === "element" || 12 | el.style.getPropertyValue("-ms-user-select") === "contain" || 13 | el.style.getPropertyValue("user-select") === "contain" 14 | ); 15 | } 16 | 17 | function handleUserSelectContain(event) { 18 | if (!(event.target instanceof Element)) return; 19 | 20 | const currentTarget = event.target.closest(".user-select-contain"); 21 | if (!currentTarget) return; 22 | 23 | const selection = window.getSelection(); 24 | if (!selection.rangeCount || selection.type !== 'Range') return; 25 | 26 | const container = selection.getRangeAt(0).commonAncestorContainer; 27 | if (currentTarget.contains(container)) return; 28 | 29 | selection.selectAllChildren(currentTarget); 30 | } 31 | 32 | if (window.getSelection && !supportsUserSelectContain()) { 33 | document.addEventListener("click", handleUserSelectContain); 34 | } 35 | 36 | }))); 37 | -------------------------------------------------------------------------------- /user-select-contain.js.flow: -------------------------------------------------------------------------------- 1 | user-select-contain.mjs.flow -------------------------------------------------------------------------------- /user-select-contain.mjs: -------------------------------------------------------------------------------- 1 | function supportsUserSelectContain() { 2 | const el = document.createElement("div"); 3 | el.style.cssText = "-ms-user-select: element; user-select: contain;"; 4 | return ( 5 | el.style.getPropertyValue("-ms-user-select") === "element" || 6 | el.style.getPropertyValue("-ms-user-select") === "contain" || 7 | el.style.getPropertyValue("user-select") === "contain" 8 | ); 9 | } 10 | 11 | function handleUserSelectContain(event) { 12 | if (!(event.target instanceof Element)) return; 13 | 14 | const currentTarget = event.target.closest(".user-select-contain"); 15 | if (!currentTarget) return; 16 | 17 | const selection = window.getSelection(); 18 | if (!selection.rangeCount || selection.type !== 'Range') return; 19 | 20 | const container = selection.getRangeAt(0).commonAncestorContainer; 21 | if (currentTarget.contains(container)) return; 22 | 23 | selection.selectAllChildren(currentTarget); 24 | } 25 | 26 | if (window.getSelection && !supportsUserSelectContain()) { 27 | document.addEventListener("click", handleUserSelectContain); 28 | } 29 | -------------------------------------------------------------------------------- /user-select-contain.mjs.flow: -------------------------------------------------------------------------------- 1 | /* @flow strict */ 2 | --------------------------------------------------------------------------------