Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The CodeSignal Design System is a CSS-based design system organized into **Found

- **Semantic over Primitive**: Always prefer semantic tokens (e.g., `--Colors-Text-Body-Default`) over base scale tokens
- **Dark Mode Support**: All components automatically adapt to dark mode via `@media (prefers-color-scheme: dark)`
- **CSS-First**: Components are primarily CSS-based with minimal JavaScript (Dropdown, Numeric Slider, and Modal use JS)
- **CSS-First**: Components are primarily CSS-based with minimal JavaScript (Dropdown, Numeric Slider, Modal, Split Panel, and Horizontal Cards use JS; Input has an optional JS helper)
- **Accessibility**: Components follow WCAG guidelines and support keyboard navigation

---
Expand Down Expand Up @@ -69,11 +69,12 @@ The CodeSignal Design System is a CSS-based design system organized into **Found

```html
<script type="module">
import Dropdown from '/design-system/components/dropdown/dropdown.js';
import HorizontalCards from '/design-system/components/horizontal-cards/horizontal-cards.js';
import Modal from '/design-system/components/modal/modal.js';
import NumericSlider from '/design-system/components/numeric-slider/numeric-slider.js';
import SplitPanel from '/design-system/components/split-panel/split-panel.js';
import Dropdown from '/design-system/components/dropdown/dropdown.js';
import HorizontalCards from '/design-system/components/horizontal-cards/horizontal-cards.js';
import Modal from '/design-system/components/modal/modal.js';
import NumericSlider from '/design-system/components/numeric-slider/numeric-slider.js';
import SplitPanel from '/design-system/components/split-panel/split-panel.js';
import preventNumberInputScroll from '/design-system/components/input/input.js';
</script>
```

Expand Down Expand Up @@ -345,7 +346,22 @@ height: var(--UI-Input-md);
<input type="text" class="input" disabled placeholder="Disabled">
```

**Dependencies:** colors.css, spacing.css, typography.css
**Optional Scroll Fix (JavaScript):**
By default, scrolling the mouse wheel over a focused `<input type="number">` changes its value. Load `input/input.js` to disable this. It auto-initializes and uses event delegation, covering existing and dynamically added number inputs:

```html
<script type="module" src="/design-system/components/input/input.js"></script>
```

Or import and scope it manually:

```javascript
import preventNumberInputScroll from '/design-system/components/input/input.js';
preventNumberInputScroll(); // whole document (default)
const cleanup = preventNumberInputScroll(containerEl); // scoped; returns cleanup fn
```

**Dependencies:** colors.css, spacing.css, typography.css (input.js has no dependencies)

---

Expand Down Expand Up @@ -1195,6 +1211,7 @@ design-system/
│ │ └── test.html
│ ├── input/
│ │ ├── input.css
│ │ ├── input.js
│ │ ├── README.md
│ │ └── test.html
│ ├── modal/
Expand Down Expand Up @@ -1249,7 +1266,7 @@ This provides:
- **Design System Version**: Current
- **Browser Support**: Modern browsers (Chrome, Firefox, Safari, Edge)
- **CSS Features Used**: CSS Custom Properties, CSS Grid, Flexbox, CSS Masks
- **JavaScript**: ES6 Modules (Dropdown, Numeric Slider, and Modal components)
- **JavaScript**: ES6 Modules (Dropdown, Numeric Slider, Modal, Split Panel, and Horizontal Cards components; optional Input scroll-fix helper)

---

Expand Down
31 changes: 31 additions & 0 deletions components/input/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,34 @@ Number inputs (`type="number"`) include styled spinner buttons that:
### Focus Ring
When focused, inputs display a subtle focus ring using the primary color with reduced opacity for better accessibility.

### Scroll-to-Change Fix (JavaScript)
By default, browsers change the value of a focused `<input type="number">` when the mouse wheel is scrolled over it. This is rarely the intended behavior and can silently corrupt user input. The optional `input.js` helper fixes this by blurring the number input on wheel, so the value stays put and the page keeps scrolling normally.

#### Usage

Load the module once anywhere on the page. It auto-initializes and uses event delegation, so it covers both existing and dynamically added `.input[type="number"]` fields:

```html
<script type="module" src="/design-system/components/input/input.js"></script>
```

You can also import it and control scope manually:

```javascript
import preventNumberInputScroll from '/design-system/components/input/input.js';

// Apply to the whole document (default; safe to call once).
preventNumberInputScroll();

// Or scope it to a specific container.
const cleanup = preventNumberInputScroll(document.querySelector('#my-form'));

// Call the returned function to remove the listener.
cleanup();
```

> **Note:** The fix only targets elements matching `.input[type="number"]`, so other inputs and page scrolling are unaffected.

## Dark Mode

The component automatically adapts to dark mode via the `@media (prefers-color-scheme: dark)` query. All states are optimized for both light and dark themes.
Expand All @@ -283,3 +311,6 @@ This component relies on variables from:
- `design-system/spacing/spacing.css`
- `design-system/typography/typography.css`

The optional scroll-to-change fix is provided by:
- `design-system/components/input/input.js` (no CSS dependencies)

72 changes: 72 additions & 0 deletions components/input/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Input Component Helpers
* Matches CodeSignal Design System
*
* Fixes the default browser behavior where scrolling the mouse wheel over a
* focused `<input type="number">` increments/decrements its value. We blur the
* input on wheel so the value stays put and the page keeps scrolling normally.
*
* The handler is attached once via event delegation, so it covers both existing
* and dynamically added number inputs without re-initialization.
*/

const NUMBER_INPUT_SELECTOR = '.input[type="number"]';

function handleWheel(event) {
const el = event.target;
if (
el instanceof HTMLInputElement &&
el.matches(NUMBER_INPUT_SELECTOR) &&
document.activeElement === el
) {
// Blurring during the wheel event (before the default action runs) cancels
// the value change because the input is no longer focused.
el.blur();
}
}

let documentDelegationActive = false;

/**
* Prevent mouse-wheel scrolling from changing `.input[type="number"]` values.
*
* @param {Document|HTMLElement} [root=document] Scope for the listener. When
* `document` (the default), a single delegated listener covers every current
* and future number input. Pass a specific element to scope it instead.
* @returns {() => void} A cleanup function that removes the listener.
*/
export function preventNumberInputScroll(root = document) {
if (root === document) {
if (!documentDelegationActive) {
document.addEventListener('wheel', handleWheel, { passive: true });
documentDelegationActive = true;
}
return () => {
document.removeEventListener('wheel', handleWheel, { passive: true });
documentDelegationActive = false;
};
}

root.addEventListener('wheel', handleWheel, { passive: true });
return () => root.removeEventListener('wheel', handleWheel, { passive: true });
}

function autoInit() {
preventNumberInputScroll(document);
}

// Auto-initialize as soon as the module loads.
if (typeof document !== 'undefined') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', autoInit);
} else {
autoInit();
}
}

export default preventNumberInputScroll;

// Also make available globally for non-module usage.
if (typeof window !== 'undefined') {
window.preventNumberInputScroll = preventNumberInputScroll;
}
3 changes: 3 additions & 0 deletions components/input/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ <h2>Text Input States</h2>
<div class="input-wrapper">
<input type="number" id="input-numeric-with-value" class="input" value="100">
</div>
<div class="description">Try scrolling the mouse wheel over this field while it's focused. With <code>input.js</code> loaded, the value no longer changes and the page scrolls normally.</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -392,6 +393,8 @@ <h2>Radio in table cell (matrix)</h2>
</table>
</div>
</div>

<script type="module" src="input.js"></script>
</body>
</html>

3 changes: 2 additions & 1 deletion llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Also include Work Sans font from Google Fonts.
- Types: `type="text"` or `type="number"`
- States: `.hover`, `.focus`, `:disabled`
- Example: `<input type="text" class="input" placeholder="...">`
- Optional JS helper: load `input/input.js` (or `import preventNumberInputScroll`) to stop the mouse wheel from changing `type="number"` values when focused; auto-inits + uses delegation

### Checkbox
- Base: `.input-checkbox` wrapper with `input[type="checkbox"]`
Expand Down Expand Up @@ -136,7 +137,7 @@ design-system/
├── dropdown/dropdown.css + dropdown.js
├── horizontal-cards/horizontal-cards.css + horizontal-cards.js
├── icons/icons.css
├── input/input.css
├── input/input.css + input.js (optional scroll-fix helper)
├── modal/modal.css + modal.js
├── numeric-slider/numeric-slider.css + numeric-slider.js
├── split-panel/split-panel.css + split-panel.js
Expand Down