feat: initial commit of Railtrack Pro prototype with complete test suite

This commit is contained in:
Railtrack Pro Dev
2026-03-13 14:26:16 +00:00
commit 40500bb503
7790 changed files with 986332 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
{"model_provider": "huggingface", "model_name": "sentence-transformers/all-MiniLM-L6-v2"}
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
{"/a0/knowledge/main/about/installation.md": {"file": "/a0/knowledge/main/about/installation.md", "checksum": "9ed8bf50b81ca30e74ba2d6bd76836c0", "ids": ["JcTPJOm2xY", "lsNPsPKrNk", "NQsxMOH0d0", "UseSosbPiN", "Y4GKJzIvFr", "S9sbwpCrxh"]}, "/a0/knowledge/main/about/github_readme.md": {"file": "/a0/knowledge/main/about/github_readme.md", "checksum": "60fcbd470ea57133e6f949b80e14490e", "ids": ["GUwypmwwjQ", "j9VSqb5fzz", "7u5rNPv4X1", "cYqQSNZ6wd", "VJbw44i4GQ", "zItMn0N0AF"]}}
+1
View File
@@ -0,0 +1 @@
{"title": "Railtrack Pro", "description": "This is a web-based railway construction game where multiple track parts can be used to populate a world. Junctions and signals are implemented, as well as a 3D driver's eye view feature.", "instructions": "You're a great web developer who loves making things which are well tested and are visually appealing. You're an ace at both 2D and 3D rendering in JS.\n\nEverything you do you'll test before handing over. TDD is something you believe in, as is well documented code (comments, readmes).\n\nIf you feel you need a new skill, find one and add it or ask the user to add it for you.\n", "color": "#fb5607", "git_url": "", "memory": "own", "file_structure": {"enabled": true, "max_depth": 5, "max_files": 20, "max_folders": 20, "max_lines": 250, "gitignore": "# Python environments & cache\nvenv/**\n**/__pycache__/**\n\n# Node.js dependencies\n**/node_modules/**\n**/.npm/**\n\n# Version control metadata\n**/.git/**\n"}}
View File
View File
+57
View File
@@ -0,0 +1,57 @@
# Railtrack Pro - Development Plan
## Project Overview
A web-based railway construction game featuring track building, junctions, signals, and 3D driver's eye view.
## Features
- 🚂 Track pieces (straight, curved, junctions, signals)
- 🌍 World grid system for track placement
- 🎮 Interactive construction interface
- 👁️ 3D Driver's eye view
- 🔌 Junction and signal logic
- 💾 Save/load game states
## Architecture
```
/railtrack_pro/
├── index.html # Main HTML structure
├── css/
│ └── styles.css # Application styles
├── js/
│ ├── app.js # Main application logic
│ ├── renderer.js # Three.js renderer
│ ├── tracks.js # Track piece definitions
│ ├── world.js # Grid and world management
│ └── game.js # Game state and controls
├── assets/
│ ├── textures/ # Track textures
│ └── models/ # 3D models
├── test/
│ └── unit/ # Test files (TDD)
└── README.md # Project documentation
```
## Technology Stack
- **Frontend**: HTML5, CSS3, JavaScript ES6+
- **3D Rendering**: Three.js
- **Build**: No build system (vanilla JS)
- **Testing**: Jest or Mocha for unit tests
## Development Phases
1. **Phase 1**: Basic project structure and HTML layout
2. **Phase 2**: Three.js renderer setup with camera and scene
3. **Phase 3**: Track piece system with placement
4. **Phase 4**: Grid and world management
5. **Phase 5**: Junctions and signals logic
6. **Phase 6**: Driver's eye view feature
7. **Phase 7**: Game state save/load
8. **Phase 8**: Testing and documentation
## Next Steps
- Create project file structure
- Set up Three.js renderer
- Implement basic track pieces
- Build placement interface
---
*Generated: 2026-03-13*
+125
View File
@@ -0,0 +1,125 @@
# 🚂 Railtrack Pro
**A web-based railway construction game featuring track building, junctions, signals, and 3D driver's eye view.**
![Railtrack Pro](https://img.shields.io/badge/Status-Prototype-orange)
![License](https://img.shields.io/badge/License-MIT-blue)
![Three.js](https://img.shields.io/badge/Renderer-Three.js-green)
## ✨ Features
- 🚂 **Track Pieces**: Straight, curved, junction, and signal tracks
- 🌍 **World Grid**: Interactive grid system for precise placement
- 🎮 **Construction Interface**: Click-to-place track building
- 👁️ **3D View**: Full 3D driver's eye view with camera controls
- 🔌 **Junction Logic**: Connect multiple tracks at junctions
- 🚦 **Signal System**: Railway signals for traffic control
- 💾 **Save/Load**: Game state persistence (coming soon)
- 🎨 **Visual Effects**: Dynamic lighting and shadows
## 🏗️ Project Structure
```
railtrack_pro/
├── index.html # Main HTML entry point
├── css/
│ └── styles.css # Application styles
├── js/
│ ├── app.js # Main application logic
│ ├── renderer.js # Three.js renderer
│ ├── tracks.js # Track piece definitions
│ ├── world.js # Grid and world management
│ └── game.js # Game state and controls
├── assets/
│ ├── textures/ # Track textures (TBD)
│ └── models/ # 3D models (TBD)
├── test/
│ └── unit/ # Unit tests (TDD)
├── PLAN.md # Development plan
└── README.md # This file
```
## 🚀 Getting Started
### Requirements
- Modern web browser (Chrome, Firefox, Safari, Edge)
- WebGL 2.0 support
- No server required - works as local file
### Installation
1. Clone or download this repository
2. Open `index.html` in your browser
3. Start building your railway empire!
### Development Setup
```bash
# Navigate to project directory
cd /a0/usr/projects/railtrack_pro
# View development plan
cat PLAN.md
# Open in browser
open index.html # Mac
x-www-browser index.html # Linux
```
## 🎮 Controls
| Control | Action |
|---------|--------|
| **Mouse Wheel** | Zoom in/out |
| **Left Drag** | Rotate camera |
| **Right Drag** | Pan camera |
| **Click Track** | Remove track |
| **Click Viewport** | Place selected track |
| **Track Buttons** | Select track type |
## 🛠️ Development
### Technology Stack
- **Frontend**: HTML5, CSS3, JavaScript ES6+
- **3D Rendering**: Three.js
- **No Build System**: Vanilla JavaScript
- **Testing**: TDD approach (Jest planned)
### Code Style
- **TDD First**: Test before implementation
- **Well Documented**: JSDoc comments throughout
- **Clean Code**: Follows SOLID principles
- **Modular**: Class-based architecture
### Adding Features
1. Create test file in `test/unit/`
2. Implement feature in `js/`
3. Update documentation
4. Run tests to verify
## 📜 License
MIT License - See LICENSE file for details
## 🤝 Contributing
1. Fork the repository
2. Create feature branch (`git checkout -b feature/amazing-feature`)
3. Commit changes (`git commit -m 'Add amazing feature'`)
4. Push to branch (`git push origin feature/amazing-feature`)
5. Open Pull Request
## 📝 Version History
- **0.1.0** - Initial prototype with basic track system
- *Coming Soon*: Driver's eye view, signal logic, save/load
## 🙏 Acknowledgments
- Three.js for amazing 3D rendering
- Railway enthusiasts for inspiration
---
*Built with ❤️ for railway simulation* | 2026 Railtrack Pro Development Team
+121
View File
@@ -0,0 +1,121 @@
/* Railtrack Pro - Main Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #ffffff;
overflow: hidden;
}
#app {
display: flex;
flex-direction: column;
height: 100vh;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
header h1 {
font-size: 1.8rem;
color: #fb5607;
text-shadow: 0 0 10px rgba(251, 86, 7, 0.5);
}
#controls {
display: flex;
gap: 10px;
}
button {
padding: 10px 20px;
border: none;
border-radius: 8px;
background: rgba(251, 86, 7, 0.3);
color: #ffffff;
cursor: pointer;
font-size: 1rem;
transition: all 0.3s ease;
border: 2px solid rgba(251, 86, 7, 0.5);
}
button:hover {
background: rgba(251, 86, 7, 0.6);
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(251, 86, 7, 0.3);
}
button.active {
background: #fb5607;
box-shadow: 0 0 15px rgba(251, 86, 7, 0.7);
}
#viewport {
flex: 1;
position: relative;
cursor: crosshair;
}
#viewport canvas {
display: block;
}
#info-panel {
width: 250px;
background: rgba(0, 0, 0, 0.3);
padding: 15px;
margin: 10px;
border-radius: 10px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
#stats {
margin-bottom: 15px;
}
#stats h3 {
color: #fb5607;
margin-bottom: 5px;
}
#stats p {
font-size: 0.9rem;
color: #cccccc;
}
#selected-info {
padding-top: 15px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
#selected-info h4 {
color: #fb5607;
margin-bottom: 10px;
}
#selected-info p {
font-size: 0.85rem;
color: #cccccc;
}
.tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.8);
padding: 5px 10px;
border-radius: 5px;
pointer-events: none;
font-size: 0.85rem;
}
+33
View File
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Railtrack Pro</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<div id="app">
<header>
<h1>🚂 Railtrack Pro</h1>
<nav id="controls">
<button id="btn-straight">Straight</button>
<button id="btn-curved">Curved</button>
<button id="btn-junction">Junction</button>
<button id="btn-signal">Signal</button>
</nav>
</header>
<div id="viewport"></div>
<div id="info-panel">
<div id="stats"></div>
<div id="selected-info"></div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.156.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.156.0/examples/js/controls/OrbitControls.js"></script>
<script src="js/renderer.js"></script>
<script src="js/tracks.js"></script>
<script src="js/world.js"></script>
<script src="js/game.js"></script>
</body>
</html>
+228
View File
@@ -0,0 +1,228 @@
/**
* Railtrack Pro - Game Logic
* Main game controller, UI interactions, and game state management
* @author Railtrack Pro Development Team
*/
class Game {
constructor() {
this.renderer = null;
this.world = null;
this.selectedTrackType = 'straight';
this.stats = { total: 0, straight: 0, curved: 0, junction: 0, signal: 0 };
this.init();
}
/**
* Initialize game
*/
init() {
console.log('[Game] Initializing Railtrack Pro...');
// Initialize renderer
this.renderer = new Renderer();
// Initialize world
this.world = new World(this.renderer);
// Setup UI controls
this.setupUIControls();
// Initialize stats display
this.updateStats(0);
// Highlight first track type as active
this.setActiveTrackType('straight');
console.log('[Game] Initialization complete');
}
/**
* Setup UI controls and event listeners
*/
setupUIControls() {
const trackTypes = ['straight', 'curved', 'junction', 'signal'];
trackTypes.forEach(type => {
const button = document.getElementById(`btn-${type}`);
if (button) {
button.addEventListener('click', () => this.setActiveTrackType(type));
}
});
}
/**
* Set active track type for placement
*/
setActiveTrackType(type) {
this.selectedTrackType = type;
// Update button states
document.querySelectorAll('#controls button').forEach(btn => {
btn.classList.remove('active');
});
const activeButton = document.getElementById(`btn-${type}`);
if (activeButton) {
activeButton.classList.add('active');
}
console.log(`[Game] Selected track type: ${type}`);
}
/**
* Handle track click from renderer
*/
static onTrackClick(trackMesh) {
if (trackMesh.userData.piece) {
Game.removeTrack(trackMesh.userData.piece);
}
}
/**
* Handle mouse click on viewport for track placement
*/
static onViewportClick(event) {
// Only handle placement if a track type is selected
if (!Game.instance.selectedTrackType) {
console.log('[Game] No track type selected');
return;
}
// Raycast for placement
const mouse = new THREE.Vector2();
const rect = Game.instance.renderer.renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
Game.instance.renderer.raycaster.setFromCamera(mouse, Game.instance.renderer.camera);
// Create plane for intersection
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
const target = new THREE.Vector3();
const intersectPoint = new THREE.Vector3();
Game.instance.renderer.raycaster.ray.intersectPlane(plane, intersectPoint);
if (intersectPoint) {
// Snap to grid
const snappedPosition = Game.instance.world.snapToGrid(intersectPoint);
// Check if placement is valid
if (Game.instance.world.isValidPlacement(snappedPosition)) {
// Calculate rotation based on selected type
const rotation = new THREE.Vector3(0, Math.PI / 2, 0);
// Add track piece to world
Game.instance.world.addTrackPiece(
Game.instance.selectedTrackType,
snappedPosition,
rotation
);
} else {
console.log('[Game] Placement invalid - invalid position');
}
}
}
/**
* Remove track piece
*/
static removeTrack(trackPiece) {
if (Game.instance && Game.instance.world) {
Game.instance.world.removeTrackPiece(trackPiece);
}
}
/**
* Update stats display in info panel
*/
static updateStats(count) {
if (!Game.instance) return;
const stats = Game.instance.world.getStats();
Game.instance.stats = stats;
const statsElement = document.getElementById('stats');
if (statsElement) {
statsElement.innerHTML = `
<h3>📊 Track Statistics</h3>
<p><strong>Total:</strong> ${stats.total}</p>
<p>⬇️ Straight: ${stats.straight}</p>
<p>⬇️ Curved: ${stats.curved}</p>
<p>⬇️ Junction: ${stats.junction}</p>
<p>⬇️ Signal: ${stats.signal}</p>
`;
}
}
/**
* Update selected track info display
*/
static updateSelectedInfo(selectedMesh) {
if (!Game.instance) return;
const infoElement = document.getElementById('selected-info');
if (!selectedMesh) {
infoElement.innerHTML = `
<h4>🔍 Selection</h4>
<p>No track selected</p>
`;
return;
}
const track = selectedMesh.userData.piece;
if (!track) return;
const config = TrackConfig.types[track.type] || { name: 'Unknown' };
infoElement.innerHTML = `
<h4>📦 Track Info</h4>
<p><strong>Type:</strong> ${config.name}</p>
<p><strong>Position:</strong> ${track.position.x.toFixed(1)}, ${track.position.z.toFixed(1)}</p>
<p><strong>Rotation:</strong> ${(track.rotation.y * 180 / Math.PI).toFixed(0)}°</p>
<p><strong>Cost:</strong> $${config.cost || 0}</p>
`;
}
/**
* Get current game instance
*/
static getInstance() {
return this.instance;
}
/**
* Initialize game on DOM ready
*/
initGame() {
Game.instance = new Game();
// Add click handler to viewport
const viewport = document.getElementById('viewport');
if (viewport) {
viewport.addEventListener('click', (event) => {
// Prevent placement if clicking on info panel
if (event.target.closest('#info-panel')) return;
Game.onViewportClick(event);
});
}
console.log('[Game] Game instance created and ready');
}
}
// Global game instance
Game.instance = null;
// Initialize game when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
Game.initGame();
});
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = Game;
}
+309
View File
@@ -0,0 +1,309 @@
/**
* Railtrack Pro - Three.js Renderer
* Handles 3D scene setup, camera, and rendering loop
* @author Railtrack Pro Development Team
*/
class Renderer {
constructor() {
this.scene = null;
this.camera = null;
this.renderer = null;
this.controls = null;
this.gridHelper = null;
this.trackMeshes = [];
this.selectedObject = null;
this.isDragging = false;
this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2();
this.init();
}
/**
* Initialize Three.js scene with camera, renderer, and controls
*/
init() {
// Create scene
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x1a1a2e);
this.scene.fog = new THREE.Fog(0x1a1a2e, 50, 200);
// Create camera
this.camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
0.1,
1000
);
this.camera.position.set(20, 20, 20);
this.camera.lookAt(0, 0, 0);
// Create renderer
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setSize(window.innerWidth - 50, window.innerHeight - 150);
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.getElementById('viewport').appendChild(this.renderer.domElement);
// Add orbit controls
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.05;
this.controls.maxPolarAngle = Math.PI / 2 - 0.1;
this.controls.minDistance = 5;
this.controls.maxDistance = 100;
// Add lighting
this.setupLighting();
// Add ground grid
this.addGridHelper();
// Add event listeners
this.addEventListeners();
// Start animation loop
this.animate();
console.log('[Renderer] Initialized successfully');
}
/**
* Setup scene lighting
*/
setupLighting() {
// Ambient light
const ambientLight = new THREE.AmbientLight(0x404040, 1);
this.scene.add(ambientLight);
// Directional light (sun)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(20, 30, 20);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 100;
directionalLight.shadow.camera.left = -50;
directionalLight.shadow.camera.right = 50;
directionalLight.shadow.camera.top = 50;
directionalLight.shadow.camera.bottom = -50;
this.scene.add(directionalLight);
// Point light near center
const pointLight = new THREE.PointLight(0xfb5607, 0.5, 30);
pointLight.position.set(0, 10, 0);
this.scene.add(pointLight);
console.log('[Renderer] Lighting setup complete');
}
/**
* Add ground plane with grid helper
*/
addGridHelper() {
// Ground plane
const planeGeometry = new THREE.PlaneGeometry(100, 100);
const planeMaterial = new THREE.MeshStandardMaterial({
color: 0x2a2a4e,
roughness: 0.8,
metalness: 0.2
});
const ground = new THREE.Mesh(planeGeometry, planeMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
this.scene.add(ground);
// Grid helper
this.gridHelper = new THREE.GridHelper(100, 50, 0x404080, 0x202040);
this.scene.add(this.gridHelper);
// Axis helper
const axesHelper = new THREE.AxesHelper(5);
this.scene.add(axesHelper);
console.log('[Renderer] Grid helper added');
}
/**
* Add event listeners for mouse interactions
*/
addEventListeners() {
window.addEventListener('resize', () => this.onWindowResize());
this.renderer.domElement.addEventListener('mousedown', (e) => this.onMouseDown(e));
this.renderer.domElement.addEventListener('mousemove', (e) => this.onMouseMove(e));
this.renderer.domElement.addEventListener('mouseup', (e) => this.onMouseUp(e));
this.renderer.domElement.addEventListener('click', (e) => this.onClick(e));
this.renderer.domElement.addEventListener('contextmenu', (e) => e.preventDefault());
}
/**
* Handle window resize
*/
onWindowResize() {
const viewport = document.getElementById('viewport');
this.camera.aspect = viewport.clientWidth / viewport.clientHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(viewport.clientWidth, viewport.clientHeight);
}
/**
* Handle mouse down event
*/
onMouseDown(event) {
this.isDragging = true;
this.updateMouse(event);
this.raycaster.setFromCamera(this.mouse, this.camera);
// Track track meshes
const intersects = this.raycaster.intersectObjects(this.trackMeshes);
if (intersects.length > 0) {
this.selectedObject = intersects[0].object;
this.highlightSelection(this.selectedObject);
} else {
this.deselectAll();
}
}
/**
* Handle mouse move event
*/
onMouseMove(event) {
this.updateMouse(event);
// Highlight object under cursor
this.raycaster.setFromCamera(this.mouse, this.camera);
const intersects = this.raycaster.intersectObjects(this.trackMeshes);
if (intersects.length > 0 && intersects[0].object !== this.selectedObject) {
this.highlightHover(intersects[0].object);
} else {
this.removeHoverHighlight();
}
}
/**
* Handle mouse up event
*/
onMouseUp() {
this.isDragging = false;
}
/**
* Handle click event
*/
onClick(event) {
if (!this.isDragging && this.selectedObject) {
console.log('[Renderer] Track clicked:', this.selectedObject.userData);
Game.onTrackClick(this.selectedObject);
}
}
/**
* Update mouse position from event
*/
updateMouse(event) {
const rect = this.renderer.domElement.getBoundingClientRect();
this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
}
/**
* Highlight selected object
*/
highlightSelection(object) {
// Remove previous highlight
if (object.originalColor) {
object.material.emissive.setHex(object.originalColor);
}
// Store original color and set highlight
object.originalColor = object.material.emissive.getHex();
object.material.emissive.setHex(0xfb5607);
this.selectedObject = object;
// Update info panel
Game.updateSelectedInfo(object);
}
/**
* Highlight hover object
*/
highlightHover(object) {
if (object.hoverColor) {
object.material.emissive.setHex(object.hoverColor);
} else {
object.hoverColor = object.material.emissive.getHex();
object.material.emissive.setHex(0x404080);
}
}
/**
* Remove hover highlight
*/
removeHoverHighlight() {
for (const mesh of this.trackMeshes) {
if (mesh.hoverColor) {
mesh.material.emissive.setHex(mesh.hoverColor);
delete mesh.hoverColor;
}
}
}
/**
* Deselect all objects
*/
deselectAll() {
if (this.selectedObject) {
if (this.selectedObject.originalColor) {
this.selectedObject.material.emissive.setHex(this.selectedObject.originalColor);
}
this.selectedObject = null;
}
Game.updateSelectedInfo(null);
}
/**
* Add track piece to scene
*/
addTrackPiece(geometry, material, position, rotation) {
const mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(position);
mesh.rotation.set(rotation.x, rotation.y, rotation.z);
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.userData = { type: 'track', id: this.trackMeshes.length };
this.scene.add(mesh);
this.trackMeshes.push(mesh);
return mesh;
}
/**
* Remove track piece from scene
*/
removeTrackPiece(mesh) {
const index = this.trackMeshes.indexOf(mesh);
if (index > -1) {
this.trackMeshes.splice(index, 1);
this.scene.remove(mesh);
mesh.geometry.dispose();
mesh.material.dispose();
}
}
/**
* Animation loop
*/
animate() {
requestAnimationFrame(() => this.animate());
this.controls.update();
this.renderer.render(this.scene, this.camera);
}
}
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = Renderer;
}
+189
View File
@@ -0,0 +1,189 @@
/**
* Railtrack Pro - Track Piece System
* Defines track geometries and placement logic
* @author Railtrack Pro Development Team
*/
class TrackPiece {
constructor(type, position, rotation) {
this.type = type; // 'straight', 'curved', 'junction', 'signal'
this.id = position ? Math.floor(Math.random() * 10000) : null;
this.position = position || new THREE.Vector3(0, 0, 0);
this.rotation = rotation || new THREE.Vector3(0, Math.PI / 2, 0);
this.connections = [];
this.geometry = null;
this.material = null;
this.mesh = null;
}
/**
* Create track piece in scene
*/
create(scene) {
this.geometry = this.getGeometry();
this.material = new THREE.MeshStandardMaterial({
color: 0x888888,
roughness: 0.6,
metalness: 0.4
});
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.mesh.position.copy(this.position);
this.mesh.rotation.copy(this.rotation);
this.mesh.castShadow = true;
this.mesh.receiveShadow = true;
this.mesh.userData = { type: 'track', id: this.id, piece: this };
scene.add(this.mesh);
return this;
}
/**
* Get geometry based on track type
*/
getGeometry() {
switch (this.type) {
case 'straight':
return new THREE.BoxGeometry(1, 0.3, 2);
case 'curved':
return this.createCurvedGeometry();
case 'junction':
return this.createJunctionGeometry();
case 'signal':
return this.createSignalGeometry();
default:
return new THREE.BoxGeometry(1, 0.3, 2);
}
}
/**
* Create curved track geometry
*/
createCurvedGeometry() {
const points = [];
const segments = 20;
const radius = 2;
for (let i = 0; i <= segments; i++) {
const angle = (i / segments) * Math.PI;
const x = Math.sin(angle) * radius;
const z = -Math.cos(angle) * radius + radius;
points.push(new THREE.Vector3(x, 0, z));
}
const curve = new THREE.CatmullRomCurve3(points);
const geometry = new THREE.TubeGeometry(curve, segments, 0.5, 8, false);
return geometry;
}
/**
* Create junction track geometry (T-junction)
*/
createJunctionGeometry() {
const group = new THREE.Group();
// Main stem
const stem = new THREE.Mesh(
new THREE.BoxGeometry(1, 0.3, 2),
this.material
);
stem.position.set(0, 0, 1);
// Top branch
const top = new THREE.Mesh(
new THREE.BoxGeometry(1, 0.3, 2),
this.material
);
top.position.set(0, 0, -1);
// Side branch
const side = new THREE.Mesh(
new THREE.BoxGeometry(2, 0.3, 1),
this.material
);
side.position.set(0, 0, 0);
group.add(stem, top, side);
const geometry = new THREE.Group(group);
return geometry;
}
/**
* Create signal track piece
*/
createSignalGeometry() {
const group = new THREE.Group();
// Signal pole
const pole = new THREE.Mesh(
new THREE.CylinderGeometry(0.1, 0.1, 1.5),
new THREE.MeshStandardMaterial({ color: 0x333333 })
);
pole.position.set(0, 0.75, 0);
// Signal lights
const lightBox = new THREE.Mesh(
new THREE.BoxGeometry(0.3, 0.2, 0.1),
new THREE.MeshStandardMaterial({ color: 0xff0000 })
);
lightBox.position.set(0, 1.5, 0);
// Track base
const base = new THREE.Mesh(
new THREE.BoxGeometry(1, 0.3, 1),
this.material
);
base.position.set(0, 0, 0);
group.add(base, pole, lightBox);
const geometry = new THREE.Group(group);
return geometry;
}
/**
* Get connection points for track joining
*/
getConnectionPoints() {
const points = [];
switch (this.type) {
case 'straight':
points.push(new THREE.Vector3(0, 0, -1));
points.push(new THREE.Vector3(0, 0, 1));
break;
case 'curved':
points.push(new THREE.Vector3(0, 0, -2));
points.push(new THREE.Vector3(2, 0, 0));
break;
case 'junction':
points.push(new THREE.Vector3(0, 0, -2));
points.push(new THREE.Vector3(0, 0, 2));
points.push(new THREE.Vector3(-2, 0, 0));
points.push(new THREE.Vector3(2, 0, 0));
break;
}
return points;
}
}
/**
* Track type configuration
*/
const TrackConfig = {
types: {
straight: { name: 'Straight Track', color: 0x888888, cost: 10 },
curved: { name: 'Curved Track', color: 0x888888, cost: 15 },
junction: { name: 'Junction', color: 0x888888, cost: 25 },
signal: { name: 'Signal', color: 0xff0000, cost: 30 }
},
placement: {
snapDistance: 0.1,
maxConnections: 4
}
};
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = { TrackPiece, TrackConfig };
}
+164
View File
@@ -0,0 +1,164 @@
/**
* Railtrack Pro - World Management
* Handles grid system, track placement, and world state
* @author Railtrack Pro Development Team
*/
class World {
constructor(renderer) {
this.renderer = renderer;
this.gridSize = 100;
this.gridUnit = 1; // Grid unit size
this.tracks = [];
this.selectedType = null;
this.snapDistance = 0.1;
this.isPlacementMode = false;
this.initGrid();
}
/**
* Initialize grid system
*/
initGrid() {
this.gridHelper = this.renderer.gridHelper;
console.log('[World] Grid system initialized');
}
/**
* Snap position to nearest grid point
*/
snapToGrid(position) {
const x = Math.round(position.x / this.gridUnit) * this.gridUnit;
const z = Math.round(position.z / this.gridUnit) * this.gridUnit;
return new THREE.Vector3(x, 0, z);
}
/**
* Check if position is valid for track placement
*/
isValidPlacement(position) {
// Check bounds
if (Math.abs(position.x) > this.gridSize || Math.abs(position.z) > this.gridSize) {
return false;
}
// Check collisions with existing tracks
const positionSnap = this.snapToGrid(position);
const collisionBuffer = this.snapDistance * 2;
for (const track of this.tracks) {
if (track.mesh === null) continue;
const trackPos = track.mesh.position;
const distance = positionSnap.distanceTo(trackPos);
if (distance < collisionBuffer) {
return false;
}
}
return true;
}
/**
* Add track piece to world
*/
addTrackPiece(type, position, rotation) {
if (!this.isValidPlacement(position)) {
console.warn('[World] Invalid placement position');
return null;
}
const track = new TrackPiece(type, position, rotation);
track.create(this.renderer.scene);
this.tracks.push(track);
// Update stats
Game.updateStats(this.tracks.length);
console.log(`[World] Added ${type} track at ${position.x.toFixed(1)}, ${position.z.toFixed(1)}`);
return track;
}
/**
* Remove track piece from world
*/
removeTrackPiece(trackPiece) {
const index = this.tracks.indexOf(trackPiece);
if (index > -1) {
this.tracks.splice(index, 1);
this.renderer.removeTrackPiece(trackPiece.mesh);
Game.updateStats(this.tracks.length);
Game.updateSelectedInfo(null);
console.log(`[World] Removed track piece`);
return true;
}
return false;
}
/**
* Get track piece at position
*/
getTrackAtPosition(position) {
for (const track of this.tracks) {
if (track.mesh === null) continue;
const distance = position.distanceTo(track.mesh.position);
if (distance < this.snapDistance) {
return track;
}
}
return null;
}
/**
* Connect tracks at junctions
*/
connectTracks(track1, track2) {
// Simple connection logic
// In full implementation, check compatibility and adjust orientation
console.log('[World] Connecting tracks');
}
/**
* Clear all tracks
*/
clear() {
for (const track of this.tracks) {
this.removeTrackPiece(track);
}
this.tracks = [];
Game.updateStats(0);
console.log('[World] All tracks cleared');
}
/**
* Get world statistics
*/
getStats() {
const counts = {
straight: 0,
curved: 0,
junction: 0,
signal: 0
};
for (const track of this.tracks) {
if (counts[track.type] !== undefined) {
counts[track.type]++;
}
}
return {
total: this.tracks.length,
...counts
};
}
}
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = World;
}
+1
View File
@@ -0,0 +1 @@
../baseline-browser-mapping/dist/cli.cjs
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../browserslist/cli.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../esprima/bin/esparse.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../esprima/bin/esvalidate.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../glob/dist/esm/bin.mjs
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../import-local/fixtures/cli.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../jest/bin/jest.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../js-yaml/bin/js-yaml.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../jsesc/bin/jsesc
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../json5/lib/cli.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../napi-postinstall/lib/cli.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../which/bin/node-which
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../@babel/parser/bin/babel-parser.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../@playwright/test/cli.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../playwright-core/cli.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../semver/bin/semver.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../@bramus/specificity/bin/cli.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../tldts/bin/cli.js
+1
View File
@@ -0,0 +1 @@
../update-browserslist-db/cli.js
+4323
View File
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) Nikita Vasilyev
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights 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 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.
+64
View File
@@ -0,0 +1,64 @@
# CSSOM
CSSOM.js is a CSS parser written in pure JavaScript. It is also a partial implementation of [CSS Object Model](http://dev.w3.org/csswg/cssom/).
CSSOM.parse("body {color: black}")
-> {
cssRules: [
{
selectorText: "body",
style: {
0: "color",
color: "black",
length: 1
}
}
]
}
## [Parser demo](https://acemir.github.io/CSSOM/docs/parse.html)
Works well in Google Chrome 6+, Safari 5+, Firefox 3.6+, Opera 10.63+.
Doesn't work in IE < 9 because of unsupported getters/setters.
To use CSSOM.js in the browser you might want to build a one-file version that exposes a single `CSSOM` global variable:
➤ git clone https://github.com/acemir/CSSOM.git
➤ cd CSSOM
➤ node build.js
build/CSSOM.js is done
To use it with Node.js or any other CommonJS loader:
➤ npm install @acemir/cssom
## Dont use it if...
You parse CSS to mungle, minify or reformat code like this:
```css
div {
background: gray;
background: linear-gradient(to bottom, white 0%, black 100%);
}
```
This pattern is often used to give browsers that dont understand linear gradients a fallback solution (e.g. gray color in the example).
In CSSOM, `background: gray` [gets overwritten](http://nv.github.io/CSSOM/docs/parse.html#css=div%20%7B%0A%20%20%20%20%20%20background%3A%20gray%3B%0A%20%20%20%20background%3A%20linear-gradient(to%20bottom%2C%20white%200%25%2C%20black%20100%25)%3B%0A%7D).
It does **NOT** get preserved.
If you do CSS mungling, minification, or image inlining, considere using one of the following:
* [postcss](https://github.com/postcss/postcss)
* [reworkcss/css](https://github.com/reworkcss/css)
* [csso](https://github.com/css/csso)
* [mensch](https://github.com/brettstimmerman/mensch)
## [Tests](https://acemir.github.io/CSSOM/spec/)
To run tests locally:
➤ git submodule init
➤ git submodule update
+6611
View File
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
CSSGroupingRule: require("./CSSGroupingRule").CSSGroupingRule
};
///CommonJS
/**
* @constructor
* @see https://www.w3.org/TR/css-conditional-3/#the-cssconditionrule-interface
*/
CSSOM.CSSConditionRule = function CSSConditionRule() {
CSSOM.CSSGroupingRule.call(this);
this.__conditionText = '';
};
CSSOM.CSSConditionRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
CSSOM.CSSConditionRule.prototype.constructor = CSSOM.CSSConditionRule;
Object.setPrototypeOf(CSSOM.CSSConditionRule, CSSOM.CSSGroupingRule);
Object.defineProperty(CSSOM.CSSConditionRule.prototype, "conditionText", {
get: function () {
return this.__conditionText;
}
});
//.CommonJS
exports.CSSConditionRule = CSSOM.CSSConditionRule;
///CommonJS
+70
View File
@@ -0,0 +1,70 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
CSSGroupingRule: require("./CSSGroupingRule").CSSGroupingRule,
CSSConditionRule: require("./CSSConditionRule").CSSConditionRule,
};
///CommonJS
/**
* @constructor
* @see https://drafts.csswg.org/css-contain-3/
* @see https://www.w3.org/TR/css-contain-3/
*/
CSSOM.CSSContainerRule = function CSSContainerRule() {
CSSOM.CSSConditionRule.call(this);
};
CSSOM.CSSContainerRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
CSSOM.CSSContainerRule.prototype.constructor = CSSOM.CSSContainerRule;
Object.setPrototypeOf(CSSOM.CSSContainerRule, CSSOM.CSSConditionRule);
Object.defineProperty(CSSOM.CSSContainerRule.prototype, "type", {
value: 17,
writable: false
});
Object.defineProperties(CSSOM.CSSContainerRule.prototype, {
"cssText": {
get: function() {
var values = "";
var valuesArr = [" {"];
if (this.cssRules.length) {
valuesArr.push(this.cssRules.reduce(function(acc, rule){
if (rule.cssText !== "") {
acc.push(rule.cssText);
}
return acc;
}, []).join("\n "));
}
values = valuesArr.join("\n ") + "\n}";
return "@container " + this.conditionText + values;
}
},
"containerName": {
get: function() {
var parts = this.conditionText.trim().split(/\s+/);
if (parts.length > 1 && parts[0] !== '(' && !parts[0].startsWith('(')) {
return parts[0];
}
return "";
}
},
"containerQuery": {
get: function() {
var parts = this.conditionText.trim().split(/\s+/);
if (parts.length > 1 && parts[0] !== '(' && !parts[0].startsWith('(')) {
return parts.slice(1).join(' ');
}
return this.conditionText;
}
},
});
//.CommonJS
exports.CSSContainerRule = CSSOM.CSSContainerRule;
///CommonJS
+57
View File
@@ -0,0 +1,57 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule
};
///CommonJS
/**
* @constructor
* @see https://drafts.csswg.org/css-counter-styles/#the-csscounterstylerule-interface
*/
CSSOM.CSSCounterStyleRule = function CSSCounterStyleRule() {
CSSOM.CSSRule.call(this);
this.name = "";
this.__props = "";
};
CSSOM.CSSCounterStyleRule.prototype = Object.create(CSSOM.CSSRule.prototype);
CSSOM.CSSCounterStyleRule.prototype.constructor = CSSOM.CSSCounterStyleRule;
Object.setPrototypeOf(CSSOM.CSSCounterStyleRule, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSCounterStyleRule.prototype, "type", {
value: 11,
writable: false
});
Object.defineProperty(CSSOM.CSSCounterStyleRule.prototype, "cssText", {
get: function() {
// FIXME : Implement real cssText generation based on properties
return "@counter-style " + this.name + " { " + this.__props + " }";
}
});
/**
* NON-STANDARD
* Rule text parser.
* @param {string} cssText
*/
Object.defineProperty(CSSOM.CSSCounterStyleRule.prototype, "parse", {
value: function(cssText) {
// Extract the name from "@counter-style <name> { ... }"
var match = cssText.match(/@counter-style\s+([^\s{]+)\s*\{([^]*)\}/);
if (match) {
this.name = match[1];
// Get the text inside the brackets and clean it up
var propsText = match[2];
this.__props = propsText.trim().replace(/\n/g, " ").replace(/(['"])(?:\\.|[^\\])*?\1|(\s{2,})/g, function (match, quote) {
return quote ? match : ' ';
});
}
}
});
//.CommonJS
exports.CSSCounterStyleRule = CSSOM.CSSCounterStyleRule;
///CommonJS
+48
View File
@@ -0,0 +1,48 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
MatcherList: require("./MatcherList").MatcherList
};
///CommonJS
/**
* @constructor
* @see https://developer.mozilla.org/en/CSS/@-moz-document
* @deprecated This rule is a non-standard Mozilla-specific extension and is not part of any official CSS specification.
*/
CSSOM.CSSDocumentRule = function CSSDocumentRule() {
CSSOM.CSSRule.call(this);
this.matcher = new CSSOM.MatcherList();
this.cssRules = new CSSOM.CSSRuleList();
};
CSSOM.CSSDocumentRule.prototype = Object.create(CSSOM.CSSRule.prototype);
CSSOM.CSSDocumentRule.prototype.constructor = CSSOM.CSSDocumentRule;
Object.setPrototypeOf(CSSOM.CSSDocumentRule, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSDocumentRule.prototype, "type", {
value: 10,
writable: false
});
//FIXME
//CSSOM.CSSDocumentRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
//CSSOM.CSSDocumentRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
Object.defineProperty(CSSOM.CSSDocumentRule.prototype, "cssText", {
get: function() {
var cssTexts = [];
for (var i=0, length=this.cssRules.length; i < length; i++) {
cssTexts.push(this.cssRules[i].cssText);
}
return "@-moz-document " + this.matcher.matcherText + " {" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
}
});
//.CommonJS
exports.CSSDocumentRule = CSSOM.CSSDocumentRule;
///CommonJS
+62
View File
@@ -0,0 +1,62 @@
//.CommonJS
var CSSOM = {
CSSStyleDeclaration: require("./CSSStyleDeclaration").CSSStyleDeclaration,
CSSRule: require("./CSSRule").CSSRule
};
// Use cssstyle if available
try {
CSSOM.CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration;
} catch (e) {
// ignore
}
///CommonJS
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#css-font-face-rule
*/
CSSOM.CSSFontFaceRule = function CSSFontFaceRule() {
CSSOM.CSSRule.call(this);
this.__style = new CSSOM.CSSStyleDeclaration();
this.__style.parentRule = this;
};
CSSOM.CSSFontFaceRule.prototype = Object.create(CSSOM.CSSRule.prototype);
CSSOM.CSSFontFaceRule.prototype.constructor = CSSOM.CSSFontFaceRule;
Object.setPrototypeOf(CSSOM.CSSFontFaceRule, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "type", {
value: 5,
writable: false
});
//FIXME
//CSSOM.CSSFontFaceRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
//CSSOM.CSSFontFaceRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "style", {
get: function() {
return this.__style;
},
set: function(value) {
if (typeof value === "string") {
this.__style.cssText = value;
} else {
this.__style = value;
}
}
});
// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSFontFaceRule.cpp
Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "cssText", {
get: function() {
return "@font-face {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
}
});
//.CommonJS
exports.CSSFontFaceRule = CSSOM.CSSFontFaceRule;
///CommonJS
+165
View File
@@ -0,0 +1,165 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
parse: require('./parse').parse
};
var errorUtils = require("./errorUtils").errorUtils;
///CommonJS
/**
* @constructor
* @see https://drafts.csswg.org/cssom/#the-cssgroupingrule-interface
*/
CSSOM.CSSGroupingRule = function CSSGroupingRule() {
CSSOM.CSSRule.call(this);
this.__cssRules = new CSSOM.CSSRuleList();
};
CSSOM.CSSGroupingRule.prototype = Object.create(CSSOM.CSSRule.prototype);
CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
Object.setPrototypeOf(CSSOM.CSSGroupingRule, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSGroupingRule.prototype, "cssRules", {
get: function() {
return this.__cssRules;
}
});
/**
* Used to insert a new CSS rule to a list of CSS rules.
*
* @example
* cssGroupingRule.cssText
* -> "body{margin:0;}"
* cssGroupingRule.insertRule("img{border:none;}", 1)
* -> 1
* cssGroupingRule.cssText
* -> "body{margin:0;}img{border:none;}"
*
* @param {string} rule
* @param {number} [index]
* @see https://www.w3.org/TR/cssom-1/#dom-cssgroupingrule-insertrule
* @return {number} The index within the grouping rule's collection of the newly inserted rule.
*/
CSSOM.CSSGroupingRule.prototype.insertRule = function insertRule(rule, index) {
if (rule === undefined && index === undefined) {
errorUtils.throwMissingArguments(this, 'insertRule', this.constructor.name);
}
if (index === void 0) {
index = 0;
}
index = Number(index);
if (index < 0) {
index = 4294967296 + index;
}
if (index > this.cssRules.length) {
errorUtils.throwIndexError(this, 'insertRule', this.constructor.name, index, this.cssRules.length);
}
var ruleToParse = processedRuleToParse = String(rule);
ruleToParse = ruleToParse.trim().replace(/^\/\*[\s\S]*?\*\/\s*/, "");
var isNestedSelector = this.constructor.name === "CSSStyleRule";
if (isNestedSelector === false) {
var currentRule = this;
while (currentRule.parentRule) {
currentRule = currentRule.parentRule;
if (currentRule.constructor.name === "CSSStyleRule") {
isNestedSelector = true;
break;
}
}
}
if (isNestedSelector) {
processedRuleToParse = 's { n { } ' + ruleToParse + '}';
}
var isScopeRule = this.constructor.name === "CSSScopeRule";
if (isScopeRule) {
if (isNestedSelector) {
processedRuleToParse = 's { ' + '@scope {' + ruleToParse + '}}';
} else {
processedRuleToParse = '@scope {' + ruleToParse + '}';
}
}
var parsedRules = new CSSOM.CSSRuleList();
CSSOM.parse(processedRuleToParse, {
styleSheet: this.parentStyleSheet,
cssRules: parsedRules
});
if (isScopeRule) {
if (isNestedSelector) {
parsedRules = parsedRules[0].cssRules[0].cssRules;
} else {
parsedRules = parsedRules[0].cssRules
}
}
if (isNestedSelector) {
parsedRules = parsedRules[0].cssRules.slice(1);
}
if (parsedRules.length !== 1) {
if (isNestedSelector && parsedRules.length === 0 && ruleToParse.indexOf('@font-face') === 0) {
errorUtils.throwError(this, 'DOMException',
"Failed to execute 'insertRule' on '" + this.constructor.name + "': " +
"Only conditional nested group rules, style rules, @scope rules, @apply rules, and nested declaration rules may be nested.",
'HierarchyRequestError');
} else {
errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
}
}
var cssRule = parsedRules[0];
if (cssRule.constructor.name === 'CSSNestedDeclarations' && cssRule.style.length === 0) {
errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
}
// Check for rules that cannot be inserted inside a CSSGroupingRule
if (cssRule.constructor.name === 'CSSImportRule' || cssRule.constructor.name === 'CSSNamespaceRule') {
var ruleKeyword = cssRule.constructor.name === 'CSSImportRule' ? '@import' : '@namespace';
errorUtils.throwError(this, 'DOMException',
"Failed to execute 'insertRule' on '" + this.constructor.name + "': " +
"'" + ruleKeyword + "' rules cannot be inserted inside a group rule.",
'HierarchyRequestError');
}
// Check for CSSLayerStatementRule (@layer statement rules)
if (cssRule.constructor.name === 'CSSLayerStatementRule') {
errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
}
cssRule.__parentRule = this;
this.cssRules.splice(index, 0, cssRule);
return index;
};
/**
* Used to delete a rule from the grouping rule.
*
* cssGroupingRule.cssText
* -> "img{border:none;}body{margin:0;}"
* cssGroupingRule.deleteRule(0)
* cssGroupingRule.cssText
* -> "body{margin:0;}"
*
* @param {number} index within the grouping rule's rule list of the rule to remove.
* @see https://www.w3.org/TR/cssom-1/#dom-cssgroupingrule-deleterule
*/
CSSOM.CSSGroupingRule.prototype.deleteRule = function deleteRule(index) {
if (index === undefined) {
errorUtils.throwMissingArguments(this, 'deleteRule', this.constructor.name);
}
index = Number(index);
if (index < 0) {
index = 4294967296 + index;
}
if (index >= this.cssRules.length) {
errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
}
this.cssRules[index].__parentRule = null;
this.cssRules[index].__parentStyleSheet = null;
this.cssRules.splice(index, 1);
};
//.CommonJS
exports.CSSGroupingRule = CSSOM.CSSGroupingRule;
///CommonJS
+54
View File
@@ -0,0 +1,54 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList
};
///CommonJS
/**
* @constructor
* @see http://www.w3.org/TR/shadow-dom/#host-at-rule
* @see http://html5index.org/Shadow%20DOM%20-%20CSSHostRule.html
* @deprecated This rule was part of early Shadow DOM drafts but was removed in favor of the more flexible :host and :host-context() pseudo-classes in modern CSS for Web Components.
*/
CSSOM.CSSHostRule = function CSSHostRule() {
CSSOM.CSSRule.call(this);
this.cssRules = new CSSOM.CSSRuleList();
};
CSSOM.CSSHostRule.prototype = Object.create(CSSOM.CSSRule.prototype);
CSSOM.CSSHostRule.prototype.constructor = CSSOM.CSSHostRule;
Object.setPrototypeOf(CSSOM.CSSHostRule, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSHostRule.prototype, "type", {
value: 1001,
writable: false
});
//FIXME
//CSSOM.CSSHostRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
//CSSOM.CSSHostRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
Object.defineProperty(CSSOM.CSSHostRule.prototype, "cssText", {
get: function() {
var values = "";
var valuesArr = [" {"];
if (this.cssRules.length) {
valuesArr.push(this.cssRules.reduce(function(acc, rule){
if (rule.cssText !== "") {
acc.push(rule.cssText);
}
return acc;
}, []).join("\n "));
}
values = valuesArr.join("\n ") + "\n}";
return "@host" + values;
}
});
//.CommonJS
exports.CSSHostRule = CSSOM.CSSHostRule;
///CommonJS
+267
View File
@@ -0,0 +1,267 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSStyleSheet: require("./CSSStyleSheet").CSSStyleSheet,
MediaList: require("./MediaList").MediaList
};
var regexPatterns = require("./regexPatterns").regexPatterns;
///CommonJS
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#cssimportrule
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSImportRule
*/
CSSOM.CSSImportRule = function CSSImportRule() {
CSSOM.CSSRule.call(this);
this.__href = "";
this.__media = new CSSOM.MediaList();
this.__layerName = null;
this.__supportsText = null;
this.__styleSheet = new CSSOM.CSSStyleSheet();
};
CSSOM.CSSImportRule.prototype = Object.create(CSSOM.CSSRule.prototype);
CSSOM.CSSImportRule.prototype.constructor = CSSOM.CSSImportRule;
Object.setPrototypeOf(CSSOM.CSSImportRule, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSImportRule.prototype, "type", {
value: 3,
writable: false
});
Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
get: function() {
var mediaText = this.media.mediaText;
return "@import url(\"" + this.href.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + "\")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
}
});
Object.defineProperty(CSSOM.CSSImportRule.prototype, "href", {
get: function() {
return this.__href;
}
});
Object.defineProperty(CSSOM.CSSImportRule.prototype, "media", {
get: function() {
return this.__media;
},
set: function(value) {
if (typeof value === "string") {
this.__media.mediaText = value;
} else {
this.__media = value;
}
}
});
Object.defineProperty(CSSOM.CSSImportRule.prototype, "layerName", {
get: function() {
return this.__layerName;
}
});
Object.defineProperty(CSSOM.CSSImportRule.prototype, "supportsText", {
get: function() {
return this.__supportsText;
}
});
Object.defineProperty(CSSOM.CSSImportRule.prototype, "styleSheet", {
get: function() {
return this.__styleSheet;
}
});
/**
* NON-STANDARD
* Rule text parser.
* @param {string} cssText
*/
Object.defineProperty(CSSOM.CSSImportRule.prototype, "parse", {
value: function(cssText) {
var i = 0;
/**
* @import url(partial.css) screen, handheld;
* || |
* after-import media
* |
* url
*/
var state = '';
var buffer = '';
var index;
var layerRegExp = regexPatterns.layerRegExp;
var layerRuleNameRegExp = regexPatterns.layerRuleNameRegExp;
var doubleOrMoreSpacesRegExp = regexPatterns.doubleOrMoreSpacesRegExp;
/**
* Extracts the content inside supports() handling nested parentheses.
* @param {string} text - The text to parse
* @returns {object|null} - {content: string, endIndex: number} or null if not found
*/
function extractSupportsContent(text) {
var supportsIndex = text.indexOf('supports(');
if (supportsIndex !== 0) {
return null;
}
var depth = 0;
var start = supportsIndex + 'supports('.length;
var i = start;
for (; i < text.length; i++) {
if (text[i] === '(') {
depth++;
} else if (text[i] === ')') {
if (depth === 0) {
// Found the closing parenthesis for supports()
return {
content: text.slice(start, i),
endIndex: i
};
}
depth--;
}
}
return null; // Unbalanced parentheses
}
for (var character; (character = cssText.charAt(i)); i++) {
switch (character) {
case ' ':
case '\t':
case '\r':
case '\n':
case '\f':
if (state === 'after-import') {
state = 'url';
} else {
buffer += character;
}
break;
case '@':
if (!state && cssText.indexOf('@import', i) === i) {
state = 'after-import';
i += 'import'.length;
buffer = '';
}
break;
case 'u':
if (state === 'media') {
buffer += character;
}
if (state === 'url' && cssText.indexOf('url(', i) === i) {
index = cssText.indexOf(')', i + 1);
if (index === -1) {
throw i + ': ")" not found';
}
i += 'url('.length;
var url = cssText.slice(i, index);
if (url[0] === url[url.length - 1]) {
if (url[0] === '"' || url[0] === "'") {
url = url.slice(1, -1);
}
}
this.__href = url;
i = index;
state = 'media';
}
break;
case '"':
if (state === 'after-import' || state === 'url') {
index = cssText.indexOf('"', i + 1);
if (!index) {
throw i + ": '\"' not found";
}
this.__href = cssText.slice(i + 1, index);
i = index;
state = 'media';
}
break;
case "'":
if (state === 'after-import' || state === 'url') {
index = cssText.indexOf("'", i + 1);
if (!index) {
throw i + ': "\'" not found';
}
this.__href = cssText.slice(i + 1, index);
i = index;
state = 'media';
}
break;
case ';':
if (state === 'media') {
if (buffer) {
var bufferTrimmed = buffer.trim();
if (bufferTrimmed.indexOf('layer') === 0) {
var layerMatch = bufferTrimmed.match(layerRegExp);
if (layerMatch) {
var layerName = layerMatch[1].trim();
if (layerName.match(layerRuleNameRegExp) !== null) {
this.__layerName = layerMatch[1].trim();
bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
.replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
.trim();
} else {
// REVIEW: In the browser, an empty layer() is not processed as a unamed layer
// and treats the rest of the string as mediaText, ignoring the parse of supports()
if (bufferTrimmed) {
this.media.mediaText = bufferTrimmed;
return;
}
}
} else {
this.__layerName = "";
bufferTrimmed = bufferTrimmed.substring('layer'.length).trim()
}
}
var supportsResult = extractSupportsContent(bufferTrimmed);
if (supportsResult) {
// REVIEW: In the browser, an empty supports() invalidates and ignores the entire @import rule
this.__supportsText = supportsResult.content.trim();
// Remove the entire supports(...) from the buffer
bufferTrimmed = bufferTrimmed.slice(0, 0) + bufferTrimmed.slice(supportsResult.endIndex + 1);
bufferTrimmed = bufferTrimmed.replace(doubleOrMoreSpacesRegExp, ' ').trim();
}
// REVIEW: In the browser, any invalid media is replaced with 'not all'
if (bufferTrimmed) {
this.media.mediaText = bufferTrimmed;
}
}
}
break;
default:
if (state === 'media') {
buffer += character;
}
break;
}
}
}
});
//.CommonJS
exports.CSSImportRule = CSSOM.CSSImportRule;
///CommonJS
+63
View File
@@ -0,0 +1,63 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSStyleDeclaration: require('./CSSStyleDeclaration').CSSStyleDeclaration
};
// Use cssstyle if available
try {
CSSOM.CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration;
} catch (e) {
// ignore
}
///CommonJS
/**
* @constructor
* @see http://www.w3.org/TR/css3-animations/#DOM-CSSKeyframeRule
*/
CSSOM.CSSKeyframeRule = function CSSKeyframeRule() {
CSSOM.CSSRule.call(this);
this.keyText = '';
this.__style = new CSSOM.CSSStyleDeclaration();
this.__style.parentRule = this;
};
CSSOM.CSSKeyframeRule.prototype = Object.create(CSSOM.CSSRule.prototype);
CSSOM.CSSKeyframeRule.prototype.constructor = CSSOM.CSSKeyframeRule;
Object.setPrototypeOf(CSSOM.CSSKeyframeRule, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "type", {
value: 8,
writable: false
});
//FIXME
//CSSOM.CSSKeyframeRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
//CSSOM.CSSKeyframeRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "style", {
get: function() {
return this.__style;
},
set: function(value) {
if (typeof value === "string") {
this.__style.cssText = value;
} else {
this.__style = value;
}
}
});
// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframeRule.cpp
Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "cssText", {
get: function() {
return this.keyText + " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
}
});
//.CommonJS
exports.CSSKeyframeRule = CSSOM.CSSKeyframeRule;
///CommonJS
+247
View File
@@ -0,0 +1,247 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
parse: require("./parse").parse
};
var errorUtils = require("./errorUtils").errorUtils;
///CommonJS
/**
* @constructor
* @see http://www.w3.org/TR/css3-animations/#DOM-CSSKeyframesRule
*/
CSSOM.CSSKeyframesRule = function CSSKeyframesRule() {
CSSOM.CSSRule.call(this);
this.name = '';
this.cssRules = new CSSOM.CSSRuleList();
// Set up initial indexed access
this._setupIndexedAccess();
// Override cssRules methods after initial setup, store references as non-enumerable properties
var self = this;
var originalPush = this.cssRules.push;
var originalSplice = this.cssRules.splice;
// Create non-enumerable method overrides
Object.defineProperty(this.cssRules, 'push', {
value: function() {
var result = originalPush.apply(this, arguments);
self._setupIndexedAccess();
return result;
},
writable: true,
enumerable: false,
configurable: true
});
Object.defineProperty(this.cssRules, 'splice', {
value: function() {
var result = originalSplice.apply(this, arguments);
self._setupIndexedAccess();
return result;
},
writable: true,
enumerable: false,
configurable: true
});
};
CSSOM.CSSKeyframesRule.prototype = Object.create(CSSOM.CSSRule.prototype);
CSSOM.CSSKeyframesRule.prototype.constructor = CSSOM.CSSKeyframesRule;
Object.setPrototypeOf(CSSOM.CSSKeyframesRule, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "type", {
value: 7,
writable: false
});
// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframesRule.cpp
Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "cssText", {
get: function() {
var values = "";
var valuesArr = [" {"];
if (this.cssRules.length) {
valuesArr.push(this.cssRules.reduce(function(acc, rule){
if (rule.cssText !== "") {
acc.push(rule.cssText);
}
return acc;
}, []).join("\n "));
}
values = valuesArr.join("\n ") + "\n}";
var cssWideKeywords = ['initial', 'inherit', 'revert', 'revert-layer', 'unset', 'none'];
var processedName = cssWideKeywords.includes(this.name) ? '"' + this.name + '"' : this.name;
return "@" + (this._vendorPrefix || '') + "keyframes " + processedName + values;
}
});
/**
* Appends a new keyframe rule to the list of keyframes.
*
* @param {string} rule - The keyframe rule string to append (e.g., "50% { opacity: 0.5; }")
* @see https://www.w3.org/TR/css-animations-1/#dom-csskeyframesrule-appendrule
*/
CSSOM.CSSKeyframesRule.prototype.appendRule = function appendRule(rule) {
if (arguments.length === 0) {
errorUtils.throwMissingArguments(this, 'appendRule', 'CSSKeyframesRule');
}
var parsedRule;
try {
// Parse the rule string as a keyframe rule
var tempStyleSheet = CSSOM.parse("@keyframes temp { " + rule + " }");
if (tempStyleSheet.cssRules.length > 0 && tempStyleSheet.cssRules[0].cssRules.length > 0) {
parsedRule = tempStyleSheet.cssRules[0].cssRules[0];
} else {
throw new Error("Failed to parse keyframe rule");
}
} catch (e) {
errorUtils.throwParseError(this, 'appendRule', 'CSSKeyframesRule', rule);
}
parsedRule.__parentRule = this;
this.cssRules.push(parsedRule);
};
/**
* Deletes a keyframe rule that matches the specified key.
*
* @param {string} select - The keyframe selector to delete (e.g., "50%", "from", "to")
* @see https://www.w3.org/TR/css-animations-1/#dom-csskeyframesrule-deleterule
*/
CSSOM.CSSKeyframesRule.prototype.deleteRule = function deleteRule(select) {
if (arguments.length === 0) {
errorUtils.throwMissingArguments(this, 'deleteRule', 'CSSKeyframesRule');
}
var normalizedSelect = this._normalizeKeyText(select);
for (var i = 0; i < this.cssRules.length; i++) {
var rule = this.cssRules[i];
if (this._normalizeKeyText(rule.keyText) === normalizedSelect) {
rule.__parentRule = null;
this.cssRules.splice(i, 1);
return;
}
}
};
/**
* Finds and returns the keyframe rule that matches the specified key.
* When multiple rules have the same key, returns the last one.
*
* @param {string} select - The keyframe selector to find (e.g., "50%", "from", "to")
* @return {CSSKeyframeRule|null} The matching keyframe rule, or null if not found
* @see https://www.w3.org/TR/css-animations-1/#dom-csskeyframesrule-findrule
*/
CSSOM.CSSKeyframesRule.prototype.findRule = function findRule(select) {
if (arguments.length === 0) {
errorUtils.throwMissingArguments(this, 'findRule', 'CSSKeyframesRule');
}
var normalizedSelect = this._normalizeKeyText(select);
// Iterate backwards to find the last matching rule
for (var i = this.cssRules.length - 1; i >= 0; i--) {
var rule = this.cssRules[i];
if (this._normalizeKeyText(rule.keyText) === normalizedSelect) {
return rule;
}
}
return null;
};
/**
* Normalizes keyframe selector text for comparison.
* Handles "from" -> "0%" and "to" -> "100%" conversions and trims whitespace.
*
* @private
* @param {string} keyText - The keyframe selector text to normalize
* @return {string} The normalized keyframe selector text
*/
CSSOM.CSSKeyframesRule.prototype._normalizeKeyText = function _normalizeKeyText(keyText) {
if (!keyText) return '';
var normalized = keyText.toString().trim().toLowerCase();
// Convert keywords to percentages for comparison
if (normalized === 'from') {
return '0%';
} else if (normalized === 'to') {
return '100%';
}
return normalized;
};
/**
* Makes CSSKeyframesRule iterable over its cssRules.
* Allows for...of loops and other iterable methods.
*/
if (typeof Symbol !== 'undefined' && Symbol.iterator) {
CSSOM.CSSKeyframesRule.prototype[Symbol.iterator] = function() {
var index = 0;
var cssRules = this.cssRules;
return {
next: function() {
if (index < cssRules.length) {
return { value: cssRules[index++], done: false };
} else {
return { done: true };
}
}
};
};
}
/**
* Adds indexed getters for direct access to cssRules by index.
* This enables rule[0], rule[1], etc. access patterns.
* Works in environments where Proxy is not available (like jsdom).
*/
CSSOM.CSSKeyframesRule.prototype._setupIndexedAccess = function() {
// Remove any existing indexed properties
for (var i = 0; i < 1000; i++) { // reasonable upper limit
if (this.hasOwnProperty(i)) {
delete this[i];
} else {
break;
}
}
// Add indexed getters for current cssRules
for (var i = 0; i < this.cssRules.length; i++) {
(function(index) {
Object.defineProperty(this, index, {
get: function() {
return this.cssRules[index];
},
enumerable: false,
configurable: true
});
}.call(this, i));
}
// Update length property
Object.defineProperty(this, 'length', {
get: function() {
return this.cssRules.length;
},
enumerable: false,
configurable: true
});
};
//.CommonJS
exports.CSSKeyframesRule = CSSOM.CSSKeyframesRule;
///CommonJS
+49
View File
@@ -0,0 +1,49 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
CSSGroupingRule: require("./CSSGroupingRule").CSSGroupingRule,
};
///CommonJS
/**
* @constructor
* @see https://drafts.csswg.org/css-cascade-5/#csslayerblockrule
*/
CSSOM.CSSLayerBlockRule = function CSSLayerBlockRule() {
CSSOM.CSSGroupingRule.call(this);
this.name = "";
};
CSSOM.CSSLayerBlockRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
CSSOM.CSSLayerBlockRule.prototype.constructor = CSSOM.CSSLayerBlockRule;
Object.setPrototypeOf(CSSOM.CSSLayerBlockRule, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSLayerBlockRule.prototype, "type", {
value: 18,
writable: false
});
Object.defineProperties(CSSOM.CSSLayerBlockRule.prototype, {
cssText: {
get: function () {
var values = "";
var valuesArr = [" {"];
if (this.cssRules.length) {
valuesArr.push(this.cssRules.reduce(function(acc, rule){
if (rule.cssText !== "") {
acc.push(rule.cssText);
}
return acc;
}, []).join("\n "));
}
values = valuesArr.join("\n ") + "\n}";
return "@layer" + (this.name ? " " + this.name : "") + values;
}
},
});
//.CommonJS
exports.CSSLayerBlockRule = CSSOM.CSSLayerBlockRule;
///CommonJS
+36
View File
@@ -0,0 +1,36 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
};
///CommonJS
/**
* @constructor
* @see https://drafts.csswg.org/css-cascade-5/#csslayerstatementrule
*/
CSSOM.CSSLayerStatementRule = function CSSLayerStatementRule() {
CSSOM.CSSRule.call(this);
this.nameList = [];
};
CSSOM.CSSLayerStatementRule.prototype = Object.create(CSSOM.CSSRule.prototype);
CSSOM.CSSLayerStatementRule.prototype.constructor = CSSOM.CSSLayerStatementRule;
Object.setPrototypeOf(CSSOM.CSSLayerStatementRule, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSLayerStatementRule.prototype, "type", {
value: 0,
writable: false
});
Object.defineProperties(CSSOM.CSSLayerStatementRule.prototype, {
cssText: {
get: function () {
return "@layer " + this.nameList.join(", ") + ";";
}
},
});
//.CommonJS
exports.CSSLayerStatementRule = CSSOM.CSSLayerStatementRule;
///CommonJS
+74
View File
@@ -0,0 +1,74 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
CSSGroupingRule: require("./CSSGroupingRule").CSSGroupingRule,
CSSConditionRule: require("./CSSConditionRule").CSSConditionRule,
MediaList: require("./MediaList").MediaList
};
///CommonJS
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#cssmediarule
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSMediaRule
*/
CSSOM.CSSMediaRule = function CSSMediaRule() {
CSSOM.CSSConditionRule.call(this);
this.__media = new CSSOM.MediaList();
};
CSSOM.CSSMediaRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
CSSOM.CSSMediaRule.prototype.constructor = CSSOM.CSSMediaRule;
Object.setPrototypeOf(CSSOM.CSSMediaRule, CSSOM.CSSConditionRule);
Object.defineProperty(CSSOM.CSSMediaRule.prototype, "type", {
value: 4,
writable: false
});
// https://opensource.apple.com/source/WebCore/WebCore-7611.1.21.161.3/css/CSSMediaRule.cpp
Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
"media": {
get: function() {
return this.__media;
},
set: function(value) {
if (typeof value === "string") {
this.__media.mediaText = value;
} else {
this.__media = value;
}
},
configurable: true,
enumerable: true
},
"conditionText": {
get: function() {
return this.media.mediaText;
}
},
"cssText": {
get: function() {
var values = "";
var valuesArr = [" {"];
if (this.cssRules.length) {
valuesArr.push(this.cssRules.reduce(function(acc, rule){
if (rule.cssText !== "") {
acc.push(rule.cssText);
}
return acc;
}, []).join("\n "));
}
values = valuesArr.join("\n ") + "\n}";
return "@media " + this.media.mediaText + values;
}
}
});
//.CommonJS
exports.CSSMediaRule = CSSOM.CSSMediaRule;
///CommonJS
+103
View File
@@ -0,0 +1,103 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSStyleSheet: require("./CSSStyleSheet").CSSStyleSheet
};
///CommonJS
/**
* @constructor
* @see https://drafts.csswg.org/cssom/#the-cssnamespacerule-interface
*/
CSSOM.CSSNamespaceRule = function CSSNamespaceRule() {
CSSOM.CSSRule.call(this);
this.__prefix = "";
this.__namespaceURI = "";
};
CSSOM.CSSNamespaceRule.prototype = Object.create(CSSOM.CSSRule.prototype);
CSSOM.CSSNamespaceRule.prototype.constructor = CSSOM.CSSNamespaceRule;
Object.setPrototypeOf(CSSOM.CSSNamespaceRule, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "type", {
value: 10,
writable: false
});
Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
get: function() {
return "@namespace" + (this.prefix && " " + this.prefix) + " url(\"" + this.namespaceURI + "\");";
}
});
Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "prefix", {
get: function() {
return this.__prefix;
}
});
Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "namespaceURI", {
get: function() {
return this.__namespaceURI;
}
});
/**
* NON-STANDARD
* Rule text parser.
* @param {string} cssText
*/
Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "parse", {
value: function(cssText) {
var newPrefix = "";
var newNamespaceURI = "";
// Remove @namespace and trim
var text = cssText.trim();
if (text.indexOf('@namespace') === 0) {
text = text.slice('@namespace'.length).trim();
}
// Remove trailing semicolon if present
if (text.charAt(text.length - 1) === ';') {
text = text.slice(0, -1).trim();
}
// Regex to match valid namespace syntax:
// 1. [optional prefix] url("...") or [optional prefix] url('...') or [optional prefix] url() or [optional prefix] url(unquoted)
// 2. [optional prefix] "..." or [optional prefix] '...'
// The prefix must be a valid CSS identifier (letters, digits, hyphens, underscores, starting with letter or underscore)
var re = /^(?:([a-zA-Z_][a-zA-Z0-9_-]*)\s+)?(?:url\(\s*(?:(['"])(.*?)\2\s*|([^)]*?))\s*\)|(['"])(.*?)\5)$/;
var match = text.match(re);
if (match) {
// If prefix is present
if (match[1]) {
newPrefix = match[1];
}
// If url(...) form with quotes
if (typeof match[3] !== "undefined") {
newNamespaceURI = match[3];
}
// If url(...) form without quotes
else if (typeof match[4] !== "undefined") {
newNamespaceURI = match[4].trim();
}
// If quoted string form
else if (typeof match[6] !== "undefined") {
newNamespaceURI = match[6];
}
this.__prefix = newPrefix;
this.__namespaceURI = newNamespaceURI;
} else {
throw new DOMException("Invalid @namespace rule", "InvalidStateError");
}
}
});
//.CommonJS
exports.CSSNamespaceRule = CSSOM.CSSNamespaceRule;
///CommonJS
+56
View File
@@ -0,0 +1,56 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSStyleDeclaration: require('./CSSStyleDeclaration').CSSStyleDeclaration
};
// Use cssstyle if available
try {
CSSOM.CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration;
} catch (e) {
// ignore
}
///CommonJS
/**
* @constructor
* @see https://drafts.csswg.org/css-nesting-1/
*/
CSSOM.CSSNestedDeclarations = function CSSNestedDeclarations() {
CSSOM.CSSRule.call(this);
this.__style = new CSSOM.CSSStyleDeclaration();
this.__style.parentRule = this;
};
CSSOM.CSSNestedDeclarations.prototype = Object.create(CSSOM.CSSRule.prototype);
CSSOM.CSSNestedDeclarations.prototype.constructor = CSSOM.CSSNestedDeclarations;
Object.setPrototypeOf(CSSOM.CSSNestedDeclarations, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "type", {
value: 0,
writable: false
});
Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "style", {
get: function() {
return this.__style;
},
set: function(value) {
if (typeof value === "string") {
this.__style.cssText = value;
} else {
this.__style = value;
}
}
});
Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "cssText", {
get: function () {
return this.style.cssText;
}
});
//.CommonJS
exports.CSSNestedDeclarations = CSSOM.CSSNestedDeclarations;
///CommonJS
+58
View File
@@ -0,0 +1,58 @@
var CSSOM = {
/**
* Creates and configures a new CSSOM instance with the specified options.
*
* @param {Object} opts - Configuration options for the CSSOM instance
* @param {Object} [opts.globalObject] - Optional global object to be assigned to CSSOM objects prototype
* @returns {Object} A new CSSOM instance with the applied configuration
* @description
* This method creates a new instance of CSSOM and optionally
* configures CSSStyleSheet with a global object reference. When a globalObject is provided
* and CSSStyleSheet exists on the instance, it creates a new CSSStyleSheet constructor
* using a factory function and assigns the globalObject to its prototype's __globalObject property.
*/
setup: function (opts) {
var instance = Object.create(this);
if (opts.globalObject) {
if (instance.CSSStyleSheet) {
var factoryCSSStyleSheet = createFunctionFactory(instance.CSSStyleSheet);
var CSSStyleSheet = factoryCSSStyleSheet();
CSSStyleSheet.prototype.__globalObject = opts.globalObject;
instance.CSSStyleSheet = CSSStyleSheet;
}
}
return instance;
}
};
function createFunctionFactory(fn) {
return function() {
// Create a new function that delegates to the original
var newFn = function() {
return fn.apply(this, arguments);
};
// Copy prototype chain
Object.setPrototypeOf(newFn, Object.getPrototypeOf(fn));
// Copy own properties
for (var key in fn) {
if (Object.prototype.hasOwnProperty.call(fn, key)) {
newFn[key] = fn[key];
}
}
// Clone the .prototype object for constructor-like behavior
if (fn.prototype) {
newFn.prototype = Object.create(fn.prototype);
}
return newFn;
};
}
//.CommonJS
module.exports = CSSOM;
///CommonJS
+125
View File
@@ -0,0 +1,125 @@
//.CommonJS
var CSSOM = {
CSSStyleDeclaration: require("./CSSStyleDeclaration").CSSStyleDeclaration,
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
CSSGroupingRule: require("./CSSGroupingRule").CSSGroupingRule,
};
var regexPatterns = require("./regexPatterns").regexPatterns;
// Use cssstyle if available
try {
CSSOM.CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration;
} catch (e) {
// ignore
}
///CommonJS
/**
* @constructor
* @see https://drafts.csswg.org/cssom/#the-csspagerule-interface
*/
CSSOM.CSSPageRule = function CSSPageRule() {
CSSOM.CSSGroupingRule.call(this);
this.__style = new CSSOM.CSSStyleDeclaration();
this.__style.parentRule = this;
};
CSSOM.CSSPageRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
CSSOM.CSSPageRule.prototype.constructor = CSSOM.CSSPageRule;
Object.setPrototypeOf(CSSOM.CSSPageRule, CSSOM.CSSGroupingRule);
Object.defineProperty(CSSOM.CSSPageRule.prototype, "type", {
value: 6,
writable: false
});
Object.defineProperty(CSSOM.CSSPageRule.prototype, "selectorText", {
get: function() {
return this.__selectorText;
},
set: function(value) {
if (typeof value === "string") {
var trimmedValue = value.trim();
// Empty selector is valid for @page
if (trimmedValue === '') {
this.__selectorText = '';
return;
}
var atPageRuleSelectorRegExp = regexPatterns.atPageRuleSelectorRegExp;
var cssCustomIdentifierRegExp = regexPatterns.cssCustomIdentifierRegExp;
var match = trimmedValue.match(atPageRuleSelectorRegExp);
if (match) {
var pageName = match[1] || '';
var pseudoPages = match[2] || '';
// Validate page name if present
if (pageName) {
// Page name can be an identifier or a string
if (!cssCustomIdentifierRegExp.test(pageName)) {
return;
}
}
// Validate pseudo-pages if present
if (pseudoPages) {
var pseudos = pseudoPages.split(':').filter(function(p) { return p; });
var validPseudos = ['left', 'right', 'first', 'blank'];
var allValid = true;
for (var j = 0; j < pseudos.length; j++) {
if (validPseudos.indexOf(pseudos[j].toLowerCase()) === -1) {
allValid = false;
break;
}
}
if (!allValid) {
return; // Invalid pseudo-page, do nothing
}
}
this.__selectorText = pageName + pseudoPages.toLowerCase();
}
}
}
});
Object.defineProperty(CSSOM.CSSPageRule.prototype, "style", {
get: function() {
return this.__style;
},
set: function(value) {
if (typeof value === "string") {
this.__style.cssText = value;
} else {
this.__style = value;
}
}
});
Object.defineProperty(CSSOM.CSSPageRule.prototype, "cssText", {
get: function() {
var values = "";
if (this.cssRules.length) {
var valuesArr = [" {"];
this.style.cssText && valuesArr.push(this.style.cssText);
valuesArr.push(this.cssRules.reduce(function(acc, rule){
if (rule.cssText !== "") {
acc.push(rule.cssText);
}
return acc;
}, []).join("\n "));
values = valuesArr.join("\n ") + "\n}";
} else {
values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
}
return "@page" + (this.selectorText ? " " + this.selectorText : "") + values;
}
});
//.CommonJS
exports.CSSPageRule = CSSOM.CSSPageRule;
///CommonJS
+122
View File
@@ -0,0 +1,122 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule
};
///CommonJS
/**
* @constructor
* @see https://drafts.css-houdini.org/css-properties-values-api/#the-css-property-rule-interface
*/
CSSOM.CSSPropertyRule = function CSSPropertyRule() {
CSSOM.CSSRule.call(this);
this.__name = "";
this.__syntax = "";
this.__inherits = false;
this.__initialValue = null;
};
CSSOM.CSSPropertyRule.prototype = Object.create(CSSOM.CSSRule.prototype);
CSSOM.CSSPropertyRule.prototype.constructor = CSSOM.CSSPropertyRule;
Object.setPrototypeOf(CSSOM.CSSPropertyRule, CSSOM.CSSRule);
Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "type", {
value: 0,
writable: false
});
Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "cssText", {
get: function() {
var text = "@property " + this.name + " {";
if (this.syntax !== "") {
text += " syntax: \"" + this.syntax.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + "\";";
}
text += " inherits: " + (this.inherits ? "true" : "false") + ";";
if (this.initialValue !== null) {
text += " initial-value: " + this.initialValue + ";";
}
text += " }";
return text;
}
});
Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "name", {
get: function() {
return this.__name;
}
});
Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "syntax", {
get: function() {
return this.__syntax;
}
});
Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "inherits", {
get: function() {
return this.__inherits;
}
});
Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "initialValue", {
get: function() {
return this.__initialValue;
}
});
/**
* NON-STANDARD
* Rule text parser.
* @param {string} cssText
* @returns {boolean} True if the rule is valid and was parsed successfully
*/
Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "parse", {
value: function(cssText) {
// Extract the name from "@property <name> { ... }"
var match = cssText.match(/@property\s+(--[^\s{]+)\s*\{([^]*)\}/);
if (!match) {
return false;
}
this.__name = match[1];
var bodyText = match[2];
// Parse syntax descriptor (REQUIRED)
var syntaxMatch = bodyText.match(/syntax\s*:\s*(['"])([^]*?)\1\s*;/);
if (!syntaxMatch) {
return false; // syntax is required
}
this.__syntax = syntaxMatch[2];
// Syntax cannot be empty
if (this.__syntax === "") {
return false;
}
// Parse inherits descriptor (REQUIRED)
var inheritsMatch = bodyText.match(/inherits\s*:\s*(true|false)\s*;/);
if (!inheritsMatch) {
return false; // inherits is required
}
this.__inherits = inheritsMatch[1] === "true";
// Parse initial-value descriptor (OPTIONAL, but required if syntax is not "*")
var initialValueMatch = bodyText.match(/initial-value\s*:\s*([^;]+);/);
if (initialValueMatch) {
this.__initialValue = initialValueMatch[1].trim();
} else {
// If syntax is not "*", initial-value is required
if (this.__syntax !== "*") {
return false;
}
}
return true; // Successfully parsed
}
});
//.CommonJS
exports.CSSPropertyRule = CSSOM.CSSPropertyRule;
///CommonJS
+92
View File
@@ -0,0 +1,92 @@
//.CommonJS
var CSSOM = {};
///CommonJS
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#the-cssrule-interface
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSRule
*/
CSSOM.CSSRule = function CSSRule() {
this.__parentRule = null;
this.__parentStyleSheet = null;
};
CSSOM.CSSRule.UNKNOWN_RULE = 0; // obsolete
CSSOM.CSSRule.STYLE_RULE = 1;
CSSOM.CSSRule.CHARSET_RULE = 2; // obsolete
CSSOM.CSSRule.IMPORT_RULE = 3;
CSSOM.CSSRule.MEDIA_RULE = 4;
CSSOM.CSSRule.FONT_FACE_RULE = 5;
CSSOM.CSSRule.PAGE_RULE = 6;
CSSOM.CSSRule.KEYFRAMES_RULE = 7;
CSSOM.CSSRule.KEYFRAME_RULE = 8;
CSSOM.CSSRule.MARGIN_RULE = 9;
CSSOM.CSSRule.NAMESPACE_RULE = 10;
CSSOM.CSSRule.COUNTER_STYLE_RULE = 11;
CSSOM.CSSRule.SUPPORTS_RULE = 12;
CSSOM.CSSRule.DOCUMENT_RULE = 13;
CSSOM.CSSRule.FONT_FEATURE_VALUES_RULE = 14;
CSSOM.CSSRule.VIEWPORT_RULE = 15;
CSSOM.CSSRule.REGION_STYLE_RULE = 16;
CSSOM.CSSRule.CONTAINER_RULE = 17;
CSSOM.CSSRule.LAYER_BLOCK_RULE = 18;
CSSOM.CSSRule.STARTING_STYLE_RULE = 1002;
Object.defineProperties(CSSOM.CSSRule.prototype, {
constructor: { value: CSSOM.CSSRule },
cssRule: {
value: "",
configurable: true,
enumerable: true
},
cssText: {
get: function() {
// Default getter: subclasses should override this
return "";
},
set: function(cssText) {
return cssText;
}
},
parentRule: {
get: function() {
return this.__parentRule
}
},
parentStyleSheet: {
get: function() {
return this.__parentStyleSheet
}
},
UNKNOWN_RULE: { value: 0, enumerable: true }, // obsolet
STYLE_RULE: { value: 1, enumerable: true },
CHARSET_RULE: { value: 2, enumerable: true }, // obsolet
IMPORT_RULE: { value: 3, enumerable: true },
MEDIA_RULE: { value: 4, enumerable: true },
FONT_FACE_RULE: { value: 5, enumerable: true },
PAGE_RULE: { value: 6, enumerable: true },
KEYFRAMES_RULE: { value: 7, enumerable: true },
KEYFRAME_RULE: { value: 8, enumerable: true },
MARGIN_RULE: { value: 9, enumerable: true },
NAMESPACE_RULE: { value: 10, enumerable: true },
COUNTER_STYLE_RULE: { value: 11, enumerable: true },
SUPPORTS_RULE: { value: 12, enumerable: true },
DOCUMENT_RULE: { value: 13, enumerable: true },
FONT_FEATURE_VALUES_RULE: { value: 14, enumerable: true },
VIEWPORT_RULE: { value: 15, enumerable: true },
REGION_STYLE_RULE: { value: 16, enumerable: true },
CONTAINER_RULE: { value: 17, enumerable: true },
LAYER_BLOCK_RULE: { value: 18, enumerable: true },
STARTING_STYLE_RULE: { value: 1002, enumerable: true },
});
//.CommonJS
exports.CSSRule = CSSOM.CSSRule;
///CommonJS
+26
View File
@@ -0,0 +1,26 @@
//.CommonJS
var CSSOM = {};
///CommonJS
/**
* @constructor
* @see https://drafts.csswg.org/cssom/#the-cssrulelist-interface
*/
CSSOM.CSSRuleList = function CSSRuleList(){
var arr = new Array();
Object.setPrototypeOf(arr, CSSOM.CSSRuleList.prototype);
return arr;
};
CSSOM.CSSRuleList.prototype = Object.create(Array.prototype);
CSSOM.CSSRuleList.prototype.constructor = CSSOM.CSSRuleList;
CSSOM.CSSRuleList.prototype.item = function(index) {
return this[index] || null;
};
//.CommonJS
exports.CSSRuleList = CSSOM.CSSRuleList;
///CommonJS
+61
View File
@@ -0,0 +1,61 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
CSSGroupingRule: require("./CSSGroupingRule").CSSGroupingRule,
};
///CommonJS
/**
* @constructor
* @see https://drafts.csswg.org/css-cascade-6/#cssscoperule
*/
CSSOM.CSSScopeRule = function CSSScopeRule() {
CSSOM.CSSGroupingRule.call(this);
this.__start = null;
this.__end = null;
};
CSSOM.CSSScopeRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
CSSOM.CSSScopeRule.prototype.constructor = CSSOM.CSSScopeRule;
Object.setPrototypeOf(CSSOM.CSSScopeRule, CSSOM.CSSGroupingRule);
Object.defineProperties(CSSOM.CSSScopeRule.prototype, {
type: {
value: 0,
writable: false,
},
cssText: {
get: function () {
var values = "";
var valuesArr = [" {"];
if (this.cssRules.length) {
valuesArr.push(this.cssRules.reduce(function(acc, rule){
if (rule.cssText !== "") {
acc.push(rule.cssText);
}
return acc;
}, []).join("\n "));
}
values = valuesArr.join("\n ") + "\n}";
return "@scope" + (this.start ? " (" + this.start + ")" : "") + (this.end ? " to (" + this.end + ")" : "") + values;
},
configurable: true,
enumerable: true,
},
start: {
get: function () {
return this.__start;
}
},
end: {
get: function () {
return this.__end;
}
}
});
//.CommonJS
exports.CSSScopeRule = CSSOM.CSSScopeRule;
///CommonJS
+52
View File
@@ -0,0 +1,52 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
CSSGroupingRule: require("./CSSGroupingRule").CSSGroupingRule
};
///CommonJS
/**
* @constructor
* @see http://www.w3.org/TR/shadow-dom/#host-at-rule
*/
CSSOM.CSSStartingStyleRule = function CSSStartingStyleRule() {
CSSOM.CSSGroupingRule.call(this);
};
CSSOM.CSSStartingStyleRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
CSSOM.CSSStartingStyleRule.prototype.constructor = CSSOM.CSSStartingStyleRule;
Object.setPrototypeOf(CSSOM.CSSStartingStyleRule, CSSOM.CSSGroupingRule);
Object.defineProperty(CSSOM.CSSStartingStyleRule.prototype, "type", {
value: 1002,
writable: false
});
//FIXME
//CSSOM.CSSStartingStyleRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
//CSSOM.CSSStartingStyleRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
Object.defineProperty(CSSOM.CSSStartingStyleRule.prototype, "cssText", {
get: function() {
var values = "";
var valuesArr = [" {"];
if (this.cssRules.length) {
valuesArr.push(this.cssRules.reduce(function(acc, rule){
if (rule.cssText !== "") {
acc.push(rule.cssText);
}
return acc;
}, []).join("\n "));
}
values = valuesArr.join("\n ") + "\n}";
return "@starting-style" + values;
}
});
//.CommonJS
exports.CSSStartingStyleRule = CSSOM.CSSStartingStyleRule;
///CommonJS
+164
View File
@@ -0,0 +1,164 @@
//.CommonJS
var CSSOM = {};
var regexPatterns = require("./regexPatterns").regexPatterns;
///CommonJS
/**
* @constructor
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration
*/
CSSOM.CSSStyleDeclaration = function CSSStyleDeclaration(){
this.length = 0;
this.parentRule = null;
// NON-STANDARD
this._importants = {};
};
CSSOM.CSSStyleDeclaration.prototype = {
constructor: CSSOM.CSSStyleDeclaration,
/**
*
* @param {string} name
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-getPropertyValue
* @return {string} the value of the property if it has been explicitly set for this declaration block.
* Returns the empty string if the property has not been set.
*/
getPropertyValue: function(name) {
return this[name] || "";
},
/**
*
* @param {string} name
* @param {string} value
* @param {string} [priority=null] "important" or null
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty
*/
setProperty: function(name, value, priority, parseErrorHandler)
{
// NOTE: Check viability to add a validation for css values or use a dependency like csstree-validator
var basicStylePropertyValueValidationRegExp = regexPatterns.basicStylePropertyValueValidationRegExp
if (basicStylePropertyValueValidationRegExp.test(value)) {
parseErrorHandler && parseErrorHandler('Invalid CSSStyleDeclaration property (name = "' + name + '", value = "' + value + '")');
} else if (this[name]) {
// Property already exist. Overwrite it.
var index = Array.prototype.indexOf.call(this, name);
if (index < 0) {
this[this.length] = name;
this.length++;
}
// If the priority value of the incoming property is "important",
// or the value of the existing property is not "important",
// then remove the existing property and rewrite it.
if (priority || !this._importants[name]) {
this.removeProperty(name);
this[this.length] = name;
this.length++;
this[name] = value + '';
this._importants[name] = priority;
}
} else {
// New property.
this[this.length] = name;
this.length++;
this[name] = value + '';
this._importants[name] = priority;
}
},
/**
*
* @param {string} name
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-removeProperty
* @return {string} the value of the property if it has been explicitly set for this declaration block.
* Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property.
*/
removeProperty: function(name) {
if (!(name in this)) {
return "";
}
var index = Array.prototype.indexOf.call(this, name);
if (index < 0) {
return "";
}
var prevValue = this[name];
this[name] = "";
// That's what WebKit and Opera do
Array.prototype.splice.call(this, index, 1);
// That's what Firefox does
//this[index] = ""
return prevValue;
},
getPropertyCSSValue: function() {
//FIXME
},
/**
*
* @param {String} name
*/
getPropertyPriority: function(name) {
return this._importants[name] || "";
},
/**
* element.style.overflow = "auto"
* element.style.getPropertyShorthand("overflow-x")
* -> "overflow"
*/
getPropertyShorthand: function() {
//FIXME
},
isPropertyImplicit: function() {
//FIXME
},
// Doesn't work in IE < 9
get cssText(){
var properties = [];
for (var i=0, length=this.length; i < length; ++i) {
var name = this[i];
var value = this.getPropertyValue(name);
var priority = this.getPropertyPriority(name);
if (priority) {
priority = " !" + priority;
}
properties[i] = name + ": " + value + priority + ";";
}
return properties.join(" ");
},
set cssText(text){
var i, name;
for (i = this.length; i--;) {
name = this[i];
this[name] = "";
}
Array.prototype.splice.call(this, 0, this.length);
this._importants = {};
var dummyRule = CSSOM.parse('#bogus{' + text + '}').cssRules[0].style;
var length = dummyRule.length;
for (i = 0; i < length; ++i) {
name = dummyRule[i];
this.setProperty(dummyRule[i], dummyRule.getPropertyValue(name), dummyRule.getPropertyPriority(name));
}
}
};
//.CommonJS
exports.CSSStyleDeclaration = CSSOM.CSSStyleDeclaration;
CSSOM.parse = require('./parse').parse; // Cannot be included sooner due to the mutual dependency between parse.js and CSSStyleDeclaration.js
///CommonJS
+109
View File
@@ -0,0 +1,109 @@
//.CommonJS
var CSSOM = {
CSSStyleDeclaration: require("./CSSStyleDeclaration").CSSStyleDeclaration,
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
CSSGroupingRule: require("./CSSGroupingRule").CSSGroupingRule,
};
var regexPatterns = require("./regexPatterns").regexPatterns;
// Use cssstyle if available
try {
CSSOM.CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration;
} catch (e) {
// ignore
}
///CommonJS
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#cssstylerule
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleRule
*/
CSSOM.CSSStyleRule = function CSSStyleRule() {
CSSOM.CSSGroupingRule.call(this);
this.__selectorText = "";
this.__style = new CSSOM.CSSStyleDeclaration();
this.__style.parentRule = this;
};
CSSOM.CSSStyleRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
CSSOM.CSSStyleRule.prototype.constructor = CSSOM.CSSStyleRule;
Object.setPrototypeOf(CSSOM.CSSStyleRule, CSSOM.CSSGroupingRule);
Object.defineProperty(CSSOM.CSSStyleRule.prototype, "type", {
value: 1,
writable: false
});
Object.defineProperty(CSSOM.CSSStyleRule.prototype, "selectorText", {
get: function() {
return this.__selectorText;
},
set: function(value) {
if (typeof value === "string") {
// Don't trim if the value ends with a hex escape sequence followed by space
// (e.g., ".\31 " where the space is part of the escape terminator)
var endsWithHexEscapeRegExp = regexPatterns.endsWithHexEscapeRegExp;
var endsWithEscape = endsWithHexEscapeRegExp.test(value);
var trimmedValue = endsWithEscape ? value.replace(/\s+$/, ' ').trimStart() : value.trim();
if (trimmedValue === '') {
return;
}
// TODO: Setting invalid selectorText should be ignored
// There are some validations already on lib/parse.js
// but the same validations should be applied here.
// Check if we can move these validation logic to a shared function.
this.__selectorText = trimmedValue;
}
},
configurable: true
});
Object.defineProperty(CSSOM.CSSStyleRule.prototype, "style", {
get: function() {
return this.__style;
},
set: function(value) {
if (typeof value === "string") {
this.__style.cssText = value;
} else {
this.__style = value;
}
},
configurable: true
});
Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
get: function() {
var text;
if (this.selectorText) {
var values = "";
if (this.cssRules.length) {
var valuesArr = [" {"];
this.style.cssText && valuesArr.push(this.style.cssText);
valuesArr.push(this.cssRules.reduce(function(acc, rule){
if (rule.cssText !== "") {
acc.push(rule.cssText);
}
return acc;
}, []).join("\n "));
values = valuesArr.join("\n ") + "\n}";
} else {
values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
}
text = this.selectorText + values;
} else {
text = "";
}
return text;
}
});
//.CommonJS
exports.CSSStyleRule = CSSOM.CSSStyleRule;
///CommonJS
+371
View File
@@ -0,0 +1,371 @@
//.CommonJS
var CSSOM = {
MediaList: require("./MediaList").MediaList,
StyleSheet: require("./StyleSheet").StyleSheet,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
CSSStyleRule: require("./CSSStyleRule").CSSStyleRule,
};
var errorUtils = require("./errorUtils").errorUtils;
///CommonJS
/**
* @constructor
* @param {CSSStyleSheetInit} [opts] - CSSStyleSheetInit options.
* @param {string} [opts.baseURL] - The base URL of the stylesheet.
* @param {boolean} [opts.disabled] - The disabled attribute of the stylesheet.
* @param {MediaList | string} [opts.media] - The media attribute of the stylesheet.
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet
*/
CSSOM.CSSStyleSheet = function CSSStyleSheet(opts) {
CSSOM.StyleSheet.call(this);
this.__constructed = true;
this.__cssRules = new CSSOM.CSSRuleList();
this.__ownerRule = null;
if (opts && typeof opts === "object") {
if (opts.baseURL && typeof opts.baseURL === "string") {
this.__baseURL = opts.baseURL;
}
if (opts.media && typeof opts.media === "string") {
this.media.mediaText = opts.media;
}
if (typeof opts.disabled === "boolean") {
this.disabled = opts.disabled;
}
}
};
CSSOM.CSSStyleSheet.prototype = Object.create(CSSOM.StyleSheet.prototype);
CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
Object.setPrototypeOf(CSSOM.CSSStyleSheet, CSSOM.StyleSheet);
Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "cssRules", {
get: function() {
return this.__cssRules;
}
});
Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "rules", {
get: function() {
return this.__cssRules;
}
});
Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "ownerRule", {
get: function() {
return this.__ownerRule;
}
});
/**
* Used to insert a new rule into the style sheet. The new rule now becomes part of the cascade.
*
* sheet = new Sheet("body {margin: 0}")
* sheet.toString()
* -> "body{margin:0;}"
* sheet.insertRule("img {border: none}", 0)
* -> 0
* sheet.toString()
* -> "img{border:none;}body{margin:0;}"
*
* @param {string} rule
* @param {number} [index=0]
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-insertRule
* @return {number} The index within the style sheet's rule collection of the newly inserted rule.
*/
CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
if (rule === undefined && index === undefined) {
errorUtils.throwMissingArguments(this, 'insertRule', this.constructor.name);
}
if (index === void 0) {
index = 0;
}
index = Number(index);
if (index < 0) {
index = 4294967296 + index;
}
if (index > this.cssRules.length) {
errorUtils.throwIndexError(this, 'insertRule', this.constructor.name, index, this.cssRules.length);
}
var ruleToParse = String(rule);
var parseErrors = [];
var parsedSheet = CSSOM.parse(ruleToParse, undefined, function(err) {
parseErrors.push(err);
} );
if (parsedSheet.cssRules.length !== 1) {
errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
}
var cssRule = parsedSheet.cssRules[0];
// Helper function to find the last index of a specific rule constructor
function findLastIndexOfConstructor(rules, constructorName) {
for (var i = rules.length - 1; i >= 0; i--) {
if (rules[i].constructor.name === constructorName) {
return i;
}
}
return -1;
}
// Helper function to find the first index of a rule that's NOT of specified constructors
function findFirstNonConstructorIndex(rules, constructorNames) {
for (var i = 0; i < rules.length; i++) {
if (constructorNames.indexOf(rules[i].constructor.name) === -1) {
return i;
}
}
return rules.length;
}
// Validate rule ordering based on CSS specification
if (cssRule.constructor.name === 'CSSImportRule') {
if (this.__constructed === true) {
errorUtils.throwError(this, 'DOMException',
"Failed to execute 'insertRule' on '" + this.constructor.name + "': Can't insert @import rules into a constructed stylesheet.",
'SyntaxError');
}
// @import rules cannot be inserted after @layer rules that already exist
// They can only be inserted at the beginning or after other @import rules
var firstLayerIndex = findFirstNonConstructorIndex(this.cssRules, ['CSSImportRule']);
if (firstLayerIndex < this.cssRules.length && this.cssRules[firstLayerIndex].constructor.name === 'CSSLayerStatementRule' && index > firstLayerIndex) {
errorUtils.throwError(this, 'DOMException',
"Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
'HierarchyRequestError');
}
// Also cannot insert after @namespace or other rules
var firstNonImportIndex = findFirstNonConstructorIndex(this.cssRules, ['CSSImportRule']);
if (index > firstNonImportIndex && firstNonImportIndex < this.cssRules.length &&
this.cssRules[firstNonImportIndex].constructor.name !== 'CSSLayerStatementRule') {
errorUtils.throwError(this, 'DOMException',
"Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
'HierarchyRequestError');
}
} else if (cssRule.constructor.name === 'CSSNamespaceRule') {
// @namespace rules can come after @layer and @import, but before any other rules
// They cannot come before @import rules
var firstImportIndex = -1;
for (var i = 0; i < this.cssRules.length; i++) {
if (this.cssRules[i].constructor.name === 'CSSImportRule') {
firstImportIndex = i;
break;
}
}
var firstNonImportNamespaceIndex = findFirstNonConstructorIndex(this.cssRules, [
'CSSLayerStatementRule',
'CSSImportRule',
'CSSNamespaceRule'
]);
// Cannot insert before @import rules
if (firstImportIndex !== -1 && index <= firstImportIndex) {
errorUtils.throwError(this, 'DOMException',
"Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
'HierarchyRequestError');
}
// Cannot insert if there are already non-special rules
if (firstNonImportNamespaceIndex < this.cssRules.length) {
errorUtils.throwError(this, 'DOMException',
"Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
'InvalidStateError');
}
// Cannot insert after other types of rules
if (index > firstNonImportNamespaceIndex) {
errorUtils.throwError(this, 'DOMException',
"Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
'HierarchyRequestError');
}
} else if (cssRule.constructor.name === 'CSSLayerStatementRule') {
// @layer statement rules can be inserted anywhere before @import and @namespace
// No additional restrictions beyond what's already handled
} else {
// Any other rule cannot be inserted before @import and @namespace
var firstNonSpecialRuleIndex = findFirstNonConstructorIndex(this.cssRules, [
'CSSLayerStatementRule',
'CSSImportRule',
'CSSNamespaceRule'
]);
if (index < firstNonSpecialRuleIndex) {
errorUtils.throwError(this, 'DOMException',
"Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
'HierarchyRequestError');
}
if (parseErrors.filter(function(error) { return !error.isNested; }).length !== 0) {
errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
}
}
cssRule.__parentStyleSheet = this;
this.cssRules.splice(index, 0, cssRule);
return index;
};
CSSOM.CSSStyleSheet.prototype.addRule = function(selector, styleBlock, index) {
if (index === void 0) {
index = this.cssRules.length;
}
this.insertRule(selector + "{" + styleBlock + "}", index);
return -1;
};
/**
* Used to delete a rule from the style sheet.
*
* sheet = new Sheet("img{border:none} body{margin:0}")
* sheet.toString()
* -> "img{border:none;}body{margin:0;}"
* sheet.deleteRule(0)
* sheet.toString()
* -> "body{margin:0;}"
*
* @param {number} index within the style sheet's rule list of the rule to remove.
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-deleteRule
*/
CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
if (index === undefined) {
errorUtils.throwMissingArguments(this, 'deleteRule', this.constructor.name);
}
index = Number(index);
if (index < 0) {
index = 4294967296 + index;
}
if (index >= this.cssRules.length) {
errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
}
if (this.cssRules[index]) {
if (this.cssRules[index].constructor.name == "CSSNamespaceRule") {
var shouldContinue = this.cssRules.every(function (rule) {
return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
});
if (!shouldContinue) {
errorUtils.throwError(this, 'DOMException', "Failed to execute 'deleteRule' on '" + this.constructor.name + "': Failed to delete rule.", "InvalidStateError");
}
}
if (this.cssRules[index].constructor.name == "CSSImportRule") {
this.cssRules[index].styleSheet.__parentStyleSheet = null;
}
this.cssRules[index].__parentStyleSheet = null;
}
this.cssRules.splice(index, 1);
};
CSSOM.CSSStyleSheet.prototype.removeRule = function(index) {
if (index === void 0) {
index = 0;
}
this.deleteRule(index);
};
/**
* Replaces the rules of a {@link CSSStyleSheet}
*
* @returns a promise
* @see https://www.w3.org/TR/cssom-1/#dom-cssstylesheet-replace
*/
CSSOM.CSSStyleSheet.prototype.replace = function(text) {
var _Promise;
if (this.__globalObject && this.__globalObject['Promise']) {
_Promise = this.__globalObject['Promise'];
} else {
_Promise = Promise;
}
var _setTimeout;
if (this.__globalObject && this.__globalObject['setTimeout']) {
_setTimeout = this.__globalObject['setTimeout'];
} else {
_setTimeout = setTimeout;
}
var sheet = this;
return new _Promise(function (resolve, reject) {
// If the constructed flag is not set, or the disallow modification flag is set, throw a NotAllowedError DOMException.
if (!sheet.__constructed || sheet.__disallowModification) {
reject(errorUtils.createError(sheet, 'DOMException',
"Failed to execute 'replaceSync' on '" + sheet.constructor.name + "': Not allowed.",
'NotAllowedError'));
}
// Set the disallow modification flag.
sheet.__disallowModification = true;
// In parallel, do these steps:
_setTimeout(function() {
// Let rules be the result of running parse a stylesheet's contents from text.
var rules = new CSSOM.CSSRuleList();
CSSOM.parse(text, { styleSheet: sheet, cssRules: rules });
// If rules contains one or more @import rules, remove those rules from rules.
var i = 0;
while (i < rules.length) {
if (rules[i].constructor.name === 'CSSImportRule') {
rules.splice(i, 1);
} else {
i++;
}
}
// Set sheet's CSS rules to rules.
sheet.__cssRules.splice.apply(sheet.__cssRules, [0, sheet.__cssRules.length].concat(rules));
// Unset sheets disallow modification flag.
delete sheet.__disallowModification;
// Resolve promise with sheet.
resolve(sheet);
})
});
}
/**
* Synchronously replaces the rules of a {@link CSSStyleSheet}
*
* @see https://www.w3.org/TR/cssom-1/#dom-cssstylesheet-replacesync
*/
CSSOM.CSSStyleSheet.prototype.replaceSync = function(text) {
var sheet = this;
// If the constructed flag is not set, or the disallow modification flag is set, throw a NotAllowedError DOMException.
if (!sheet.__constructed || sheet.__disallowModification) {
errorUtils.throwError(sheet, 'DOMException',
"Failed to execute 'replaceSync' on '" + sheet.constructor.name + "': Not allowed.",
'NotAllowedError');
}
// Let rules be the result of running parse a stylesheet's contents from text.
var rules = new CSSOM.CSSRuleList();
CSSOM.parse(text, { styleSheet: sheet, cssRules: rules });
// If rules contains one or more @import rules, remove those rules from rules.
var i = 0;
while (i < rules.length) {
if (rules[i].constructor.name === 'CSSImportRule') {
rules.splice(i, 1);
} else {
i++;
}
}
// Set sheet's CSS rules to rules.
sheet.__cssRules.splice.apply(sheet.__cssRules, [0, sheet.__cssRules.length].concat(rules));
}
/**
* NON-STANDARD
* @return {string} serialize stylesheet
*/
CSSOM.CSSStyleSheet.prototype.toString = function() {
var result = "";
var rules = this.cssRules;
for (var i=0; i<rules.length; i++) {
result += rules[i].cssText + "\n";
}
return result;
};
//.CommonJS
exports.CSSStyleSheet = CSSOM.CSSStyleSheet;
CSSOM.parse = require('./parse').parse; // Cannot be included sooner due to the mutual dependency between parse.js and CSSStyleSheet.js
///CommonJS
+48
View File
@@ -0,0 +1,48 @@
//.CommonJS
var CSSOM = {
CSSRule: require("./CSSRule").CSSRule,
CSSRuleList: require("./CSSRuleList").CSSRuleList,
CSSGroupingRule: require("./CSSGroupingRule").CSSGroupingRule,
CSSConditionRule: require("./CSSConditionRule").CSSConditionRule
};
///CommonJS
/**
* @constructor
* @see https://drafts.csswg.org/css-conditional-3/#the-csssupportsrule-interface
*/
CSSOM.CSSSupportsRule = function CSSSupportsRule() {
CSSOM.CSSConditionRule.call(this);
};
CSSOM.CSSSupportsRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
CSSOM.CSSSupportsRule.prototype.constructor = CSSOM.CSSSupportsRule;
Object.setPrototypeOf(CSSOM.CSSSupportsRule, CSSOM.CSSConditionRule);
Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "type", {
value: 12,
writable: false
});
Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "cssText", {
get: function() {
var values = "";
var valuesArr = [" {"];
if (this.cssRules.length) {
valuesArr.push(this.cssRules.reduce(function(acc, rule){
if (rule.cssText !== "") {
acc.push(rule.cssText);
}
return acc;
}, []).join("\n "));
}
values = valuesArr.join("\n ") + "\n}";
return "@supports " + this.conditionText + values;
}
});
//.CommonJS
exports.CSSSupportsRule = CSSOM.CSSSupportsRule;
///CommonJS
+43
View File
@@ -0,0 +1,43 @@
//.CommonJS
var CSSOM = {};
///CommonJS
/**
* @constructor
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSValue
*
* TODO: add if needed
*/
CSSOM.CSSValue = function CSSValue() {
};
CSSOM.CSSValue.prototype = {
constructor: CSSOM.CSSValue,
// @see: http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSValue
set cssText(text) {
var name = this._getConstructorName();
throw new Error('DOMException: property "cssText" of "' + name + '" is readonly and can not be replaced with "' + text + '"!');
},
get cssText() {
var name = this._getConstructorName();
throw new Error('getter "cssText" of "' + name + '" is not implemented!');
},
_getConstructorName: function() {
var s = this.constructor.toString(),
c = s.match(/function\s([^\(]+)/),
name = c[1];
return name;
}
};
//.CommonJS
exports.CSSValue = CSSOM.CSSValue;
///CommonJS
+346
View File
@@ -0,0 +1,346 @@
//.CommonJS
var CSSOM = {
CSSValue: require('./CSSValue').CSSValue
};
///CommonJS
/**
* @constructor
* @see http://msdn.microsoft.com/en-us/library/ms537634(v=vs.85).aspx
*
*/
CSSOM.CSSValueExpression = function CSSValueExpression(token, idx) {
this._token = token;
this._idx = idx;
};
CSSOM.CSSValueExpression.prototype = Object.create(CSSOM.CSSValue.prototype);
CSSOM.CSSValueExpression.prototype.constructor = CSSOM.CSSValueExpression;
Object.setPrototypeOf(CSSOM.CSSValueExpression, CSSOM.CSSValue);
/**
* parse css expression() value
*
* @return {Object}
* - error:
* or
* - idx:
* - expression:
*
* Example:
*
* .selector {
* zoom: expression(documentElement.clientWidth > 1000 ? '1000px' : 'auto');
* }
*/
CSSOM.CSSValueExpression.prototype.parse = function() {
var token = this._token,
idx = this._idx;
var character = '',
expression = '',
error = '',
info,
paren = [];
for (; ; ++idx) {
character = token.charAt(idx);
// end of token
if (character === '') {
error = 'css expression error: unfinished expression!';
break;
}
switch(character) {
case '(':
paren.push(character);
expression += character;
break;
case ')':
paren.pop(character);
expression += character;
break;
case '/':
if ((info = this._parseJSComment(token, idx))) { // comment?
if (info.error) {
error = 'css expression error: unfinished comment in expression!';
} else {
idx = info.idx;
// ignore the comment
}
} else if ((info = this._parseJSRexExp(token, idx))) { // regexp
idx = info.idx;
expression += info.text;
} else { // other
expression += character;
}
break;
case "'":
case '"':
info = this._parseJSString(token, idx, character);
if (info) { // string
idx = info.idx;
expression += info.text;
} else {
expression += character;
}
break;
default:
expression += character;
break;
}
if (error) {
break;
}
// end of expression
if (paren.length === 0) {
break;
}
}
var ret;
if (error) {
ret = {
error: error
};
} else {
ret = {
idx: idx,
expression: expression
};
}
return ret;
};
/**
*
* @return {Object|false}
* - idx:
* - text:
* or
* - error:
* or
* false
*
*/
CSSOM.CSSValueExpression.prototype._parseJSComment = function(token, idx) {
var nextChar = token.charAt(idx + 1),
text;
if (nextChar === '/' || nextChar === '*') {
var startIdx = idx,
endIdx,
commentEndChar;
if (nextChar === '/') { // line comment
commentEndChar = '\n';
} else if (nextChar === '*') { // block comment
commentEndChar = '*/';
}
endIdx = token.indexOf(commentEndChar, startIdx + 1 + 1);
if (endIdx !== -1) {
endIdx = endIdx + commentEndChar.length - 1;
text = token.substring(idx, endIdx + 1);
return {
idx: endIdx,
text: text
};
} else {
var error = 'css expression error: unfinished comment in expression!';
return {
error: error
};
}
} else {
return false;
}
};
/**
*
* @return {Object|false}
* - idx:
* - text:
* or
* false
*
*/
CSSOM.CSSValueExpression.prototype._parseJSString = function(token, idx, sep) {
var endIdx = this._findMatchedIdx(token, idx, sep),
text;
if (endIdx === -1) {
return false;
} else {
text = token.substring(idx, endIdx + sep.length);
return {
idx: endIdx,
text: text
};
}
};
/**
* parse regexp in css expression
*
* @return {Object|false}
* - idx:
* - regExp:
* or
* false
*/
/*
all legal RegExp
/a/
(/a/)
[/a/]
[12, /a/]
!/a/
+/a/
-/a/
* /a/
/ /a/
%/a/
===/a/
!==/a/
==/a/
!=/a/
>/a/
>=/a/
</a/
<=/a/
&/a/
|/a/
^/a/
~/a/
<</a/
>>/a/
>>>/a/
&&/a/
||/a/
?/a/
=/a/
,/a/
delete /a/
in /a/
instanceof /a/
new /a/
typeof /a/
void /a/
*/
CSSOM.CSSValueExpression.prototype._parseJSRexExp = function(token, idx) {
var before = token.substring(0, idx).replace(/\s+$/, ""),
legalRegx = [
/^$/,
/\($/,
/\[$/,
/\!$/,
/\+$/,
/\-$/,
/\*$/,
/\/\s+/,
/\%$/,
/\=$/,
/\>$/,
/<$/,
/\&$/,
/\|$/,
/\^$/,
/\~$/,
/\?$/,
/\,$/,
/delete$/,
/in$/,
/instanceof$/,
/new$/,
/typeof$/,
/void$/
];
var isLegal = legalRegx.some(function(reg) {
return reg.test(before);
});
if (!isLegal) {
return false;
} else {
var sep = '/';
// same logic as string
return this._parseJSString(token, idx, sep);
}
};
/**
*
* find next sep(same line) index in `token`
*
* @return {Number}
*
*/
CSSOM.CSSValueExpression.prototype._findMatchedIdx = function(token, idx, sep) {
var startIdx = idx,
endIdx;
var NOT_FOUND = -1;
while(true) {
endIdx = token.indexOf(sep, startIdx + 1);
if (endIdx === -1) { // not found
endIdx = NOT_FOUND;
break;
} else {
var text = token.substring(idx + 1, endIdx),
matched = text.match(/\\+$/);
if (!matched || matched[0] % 2 === 0) { // not escaped
break;
} else {
startIdx = endIdx;
}
}
}
// boundary must be in the same line(js sting or regexp)
var nextNewLineIdx = token.indexOf('\n', idx + 1);
if (nextNewLineIdx < endIdx) {
endIdx = NOT_FOUND;
}
return endIdx;
};
//.CommonJS
exports.CSSValueExpression = CSSOM.CSSValueExpression;
///CommonJS
+62
View File
@@ -0,0 +1,62 @@
//.CommonJS
var CSSOM = {};
///CommonJS
/**
* @constructor
* @see https://developer.mozilla.org/en/CSS/@-moz-document
*/
CSSOM.MatcherList = function MatcherList(){
this.length = 0;
};
CSSOM.MatcherList.prototype = {
constructor: CSSOM.MatcherList,
/**
* @return {string}
*/
get matcherText() {
return Array.prototype.join.call(this, ", ");
},
/**
* @param {string} value
*/
set matcherText(value) {
// just a temporary solution, actually it may be wrong by just split the value with ',', because a url can include ','.
var values = value.split(",");
var length = this.length = values.length;
for (var i=0; i<length; i++) {
this[i] = values[i].trim();
}
},
/**
* @param {string} matcher
*/
appendMatcher: function(matcher) {
if (Array.prototype.indexOf.call(this, matcher) === -1) {
this[this.length] = matcher;
this.length++;
}
},
/**
* @param {string} matcher
*/
deleteMatcher: function(matcher) {
var index = Array.prototype.indexOf.call(this, matcher);
if (index !== -1) {
Array.prototype.splice.call(this, index, 1);
}
}
};
//.CommonJS
exports.MatcherList = CSSOM.MatcherList;
///CommonJS
+78
View File
@@ -0,0 +1,78 @@
//.CommonJS
var CSSOM = {};
///CommonJS
/**
* @constructor
* @see http://dev.w3.org/csswg/cssom/#the-medialist-interface
*/
CSSOM.MediaList = function MediaList(){
this.length = 0;
};
CSSOM.MediaList.prototype = {
constructor: CSSOM.MediaList,
/**
* @return {string}
*/
get mediaText() {
return Array.prototype.join.call(this, ", ");
},
/**
* @param {string} value
*/
set mediaText(value) {
if (typeof value === "string") {
var values = value.split(",").filter(function(text){
return !!text;
});
var length = this.length = values.length;
for (var i=0; i<length; i++) {
this[i] = values[i].trim();
}
} else if (value === null) {
var length = this.length;
for (var i = 0; i < length; i++) {
delete this[i];
}
this.length = 0;
}
},
/**
* @param {string} medium
*/
appendMedium: function(medium) {
if (Array.prototype.indexOf.call(this, medium) === -1) {
this[this.length] = medium;
this.length++;
}
},
/**
* @param {string} medium
*/
deleteMedium: function(medium) {
var index = Array.prototype.indexOf.call(this, medium);
if (index !== -1) {
Array.prototype.splice.call(this, index, 1);
}
},
item: function(index) {
return this[index] || null;
},
toString: function() {
return this.mediaText;
}
};
//.CommonJS
exports.MediaList = CSSOM.MediaList;
///CommonJS
+62
View File
@@ -0,0 +1,62 @@
//.CommonJS
var CSSOM = {
MediaList: require("./MediaList").MediaList
};
///CommonJS
/**
* @see http://dev.w3.org/csswg/cssom/#the-stylesheet-interface
*/
CSSOM.StyleSheet = function StyleSheet() {
this.__href = null;
this.__ownerNode = null;
this.__title = null;
this.__media = new CSSOM.MediaList();
this.__parentStyleSheet = null;
this.disabled = false;
};
Object.defineProperties(CSSOM.StyleSheet.prototype, {
type: {
get: function() {
return "text/css";
}
},
href: {
get: function() {
return this.__href;
}
},
ownerNode: {
get: function() {
return this.__ownerNode;
}
},
title: {
get: function() {
return this.__title;
}
},
media: {
get: function() {
return this.__media;
},
set: function(value) {
if (typeof value === "string") {
this.__media.mediaText = value;
} else {
this.__media = value;
}
}
},
parentStyleSheet: {
get: function() {
return this.__parentStyleSheet;
}
}
});
//.CommonJS
exports.StyleSheet = CSSOM.StyleSheet;
///CommonJS
+105
View File
@@ -0,0 +1,105 @@
//.CommonJS
var CSSOM = {
CSSStyleSheet: require("./CSSStyleSheet").CSSStyleSheet,
CSSRule: require("./CSSRule").CSSRule,
CSSNestedDeclarations: require("./CSSNestedDeclarations").CSSNestedDeclarations,
CSSStyleRule: require("./CSSStyleRule").CSSStyleRule,
CSSGroupingRule: require("./CSSGroupingRule").CSSGroupingRule,
CSSConditionRule: require("./CSSConditionRule").CSSConditionRule,
CSSMediaRule: require("./CSSMediaRule").CSSMediaRule,
CSSContainerRule: require("./CSSContainerRule").CSSContainerRule,
CSSSupportsRule: require("./CSSSupportsRule").CSSSupportsRule,
CSSStyleDeclaration: require("./CSSStyleDeclaration").CSSStyleDeclaration,
CSSKeyframeRule: require('./CSSKeyframeRule').CSSKeyframeRule,
CSSKeyframesRule: require('./CSSKeyframesRule').CSSKeyframesRule,
CSSScopeRule: require('./CSSScopeRule').CSSScopeRule,
CSSLayerBlockRule: require('./CSSLayerBlockRule').CSSLayerBlockRule,
CSSLayerStatementRule: require('./CSSLayerStatementRule').CSSLayerStatementRule
};
// Use cssstyle if available
try {
CSSOM.CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration;
} catch (e) {
// ignore
}
///CommonJS
/**
* Produces a deep copy of stylesheet — the instance variables of stylesheet are copied recursively.
* @param {CSSStyleSheet|CSSOM.CSSStyleSheet} stylesheet
* @nosideeffects
* @return {CSSOM.CSSStyleSheet}
*/
CSSOM.clone = function clone(stylesheet) {
var cloned = new CSSOM.CSSStyleSheet();
var rules = stylesheet.cssRules;
if (!rules) {
return cloned;
}
for (var i = 0, rulesLength = rules.length; i < rulesLength; i++) {
var rule = rules[i];
var ruleClone = cloned.cssRules[i] = new rule.constructor();
var style = rule.style;
if (style) {
var styleClone = ruleClone.style = new CSSOM.CSSStyleDeclaration();
for (var j = 0, styleLength = style.length; j < styleLength; j++) {
var name = styleClone[j] = style[j];
styleClone[name] = style[name];
styleClone._importants[name] = style.getPropertyPriority(name);
}
styleClone.length = style.length;
}
if (rule.hasOwnProperty('keyText')) {
ruleClone.keyText = rule.keyText;
}
if (rule.hasOwnProperty('selectorText')) {
ruleClone.selectorText = rule.selectorText;
}
if (rule.hasOwnProperty('mediaText')) {
ruleClone.mediaText = rule.mediaText;
}
if (rule.hasOwnProperty('supportsText')) {
ruleClone.supports = rule.supports;
}
if (rule.hasOwnProperty('conditionText')) {
ruleClone.conditionText = rule.conditionText;
}
if (rule.hasOwnProperty('layerName')) {
ruleClone.layerName = rule.layerName;
}
if (rule.hasOwnProperty('href')) {
ruleClone.href = rule.href;
}
if (rule.hasOwnProperty('name')) {
ruleClone.name = rule.name;
}
if (rule.hasOwnProperty('nameList')) {
ruleClone.nameList = rule.nameList;
}
if (rule.hasOwnProperty('cssRules')) {
ruleClone.cssRules = clone(rule).cssRules;
}
}
return cloned;
};
//.CommonJS
exports.clone = CSSOM.clone;
///CommonJS
+5
View File
@@ -0,0 +1,5 @@
try {
CSSOM.CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration;
} catch (e) {
// ignore
}
+119
View File
@@ -0,0 +1,119 @@
// Utility functions for CSSOM error handling
/**
* Gets the appropriate error constructor from the global object context.
* Tries to find the error constructor from parentStyleSheet.__globalObject,
* then from __globalObject, then falls back to the native constructor.
*
* @param {Object} context - The CSSOM object (rule, stylesheet, etc.)
* @param {string} errorType - The error type ('TypeError', 'RangeError', 'DOMException', etc.)
* @return {Function} The error constructor
*/
function getErrorConstructor(context, errorType) {
// Try parentStyleSheet.__globalObject first
if (context.parentStyleSheet && context.parentStyleSheet.__globalObject && context.parentStyleSheet.__globalObject[errorType]) {
return context.parentStyleSheet.__globalObject[errorType];
}
// Try __parentStyleSheet (alternative naming)
if (context.__parentStyleSheet && context.__parentStyleSheet.__globalObject && context.__parentStyleSheet.__globalObject[errorType]) {
return context.__parentStyleSheet.__globalObject[errorType];
}
// Try __globalObject on the context itself
if (context.__globalObject && context.__globalObject[errorType]) {
return context.__globalObject[errorType];
}
// Fall back to native constructor
return (typeof global !== 'undefined' && global[errorType]) ||
(typeof window !== 'undefined' && window[errorType]) ||
eval(errorType);
}
/**
* Creates an appropriate error with context-aware constructor.
*
* @param {Object} context - The CSSOM object (rule, stylesheet, etc.)
* @param {string} errorType - The error type ('TypeError', 'RangeError', 'DOMException', etc.)
* @param {string} message - The error message
* @param {string} [name] - Optional name for DOMException
*/
function createError(context, errorType, message, name) {
var ErrorConstructor = getErrorConstructor(context, errorType);
return new ErrorConstructor(message, name);
}
/**
* Creates and throws an appropriate error with context-aware constructor.
*
* @param {Object} context - The CSSOM object (rule, stylesheet, etc.)
* @param {string} errorType - The error type ('TypeError', 'RangeError', 'DOMException', etc.)
* @param {string} message - The error message
* @param {string} [name] - Optional name for DOMException
*/
function throwError(context, errorType, message, name) {
throw createError(context, errorType, message, name);
}
/**
* Throws a TypeError for missing required arguments.
*
* @param {Object} context - The CSSOM object
* @param {string} methodName - The method name (e.g., 'appendRule')
* @param {string} objectName - The object name (e.g., 'CSSKeyframesRule')
* @param {number} [required=1] - Number of required arguments
* @param {number} [provided=0] - Number of provided arguments
*/
function throwMissingArguments(context, methodName, objectName, required, provided) {
required = required || 1;
provided = provided || 0;
var message = "Failed to execute '" + methodName + "' on '" + objectName + "': " +
required + " argument" + (required > 1 ? "s" : "") + " required, but only " +
provided + " present.";
throwError(context, 'TypeError', message);
}
/**
* Throws a DOMException for parse errors.
*
* @param {Object} context - The CSSOM object
* @param {string} methodName - The method name
* @param {string} objectName - The object name
* @param {string} rule - The rule that failed to parse
* @param {string} [name='SyntaxError'] - The DOMException name
*/
function throwParseError(context, methodName, objectName, rule, name) {
var message = "Failed to execute '" + methodName + "' on '" + objectName + "': " +
"Failed to parse the rule '" + rule + "'.";
throwError(context, 'DOMException', message, name || 'SyntaxError');
}
/**
* Throws a DOMException for index errors.
*
* @param {Object} context - The CSSOM object
* @param {string} methodName - The method name
* @param {string} objectName - The object name
* @param {number} index - The invalid index
* @param {number} maxIndex - The maximum valid index
* @param {string} [name='IndexSizeError'] - The DOMException name
*/
function throwIndexError(context, methodName, objectName, index, maxIndex, name) {
var message = "Failed to execute '" + methodName + "' on '" + objectName + "': " +
"The index provided (" + index + ") is larger than the maximum index (" + maxIndex + ").";
throwError(context, 'DOMException', message, name || 'IndexSizeError');
}
var errorUtils = {
createError: createError,
getErrorConstructor: getErrorConstructor,
throwError: throwError,
throwMissingArguments: throwMissingArguments,
throwParseError: throwParseError,
throwIndexError: throwIndexError
};
//.CommonJS
exports.errorUtils = errorUtils;
///CommonJS
+42
View File
@@ -0,0 +1,42 @@
'use strict';
exports.setup = require('./CSSOM').setup;
require('./errorUtils');
require("./regexPatterns")
exports.CSSStyleDeclaration = require('./CSSStyleDeclaration').CSSStyleDeclaration;
require('./cssstyleTryCatchBlock');
exports.CSSRule = require('./CSSRule').CSSRule;
exports.CSSRuleList = require('./CSSRuleList').CSSRuleList;
exports.CSSNestedDeclarations = require('./CSSNestedDeclarations').CSSNestedDeclarations;
exports.CSSGroupingRule = require('./CSSGroupingRule').CSSGroupingRule;
exports.CSSCounterStyleRule = require('./CSSCounterStyleRule').CSSCounterStyleRule;
exports.CSSPropertyRule = require('./CSSPropertyRule').CSSPropertyRule;
exports.CSSConditionRule = require('./CSSConditionRule').CSSConditionRule;
exports.CSSStyleRule = require('./CSSStyleRule').CSSStyleRule;
exports.MediaList = require('./MediaList').MediaList;
exports.CSSMediaRule = require('./CSSMediaRule').CSSMediaRule;
exports.CSSContainerRule = require('./CSSContainerRule').CSSContainerRule;
exports.CSSSupportsRule = require('./CSSSupportsRule').CSSSupportsRule;
exports.CSSImportRule = require('./CSSImportRule').CSSImportRule;
exports.CSSNamespaceRule = require('./CSSNamespaceRule').CSSNamespaceRule;
exports.CSSFontFaceRule = require('./CSSFontFaceRule').CSSFontFaceRule;
exports.CSSHostRule = require('./CSSHostRule').CSSHostRule;
exports.CSSStartingStyleRule = require('./CSSStartingStyleRule').CSSStartingStyleRule;
exports.StyleSheet = require('./StyleSheet').StyleSheet;
exports.CSSStyleSheet = require('./CSSStyleSheet').CSSStyleSheet;
exports.CSSKeyframesRule = require('./CSSKeyframesRule').CSSKeyframesRule;
exports.CSSKeyframeRule = require('./CSSKeyframeRule').CSSKeyframeRule;
exports.MatcherList = require('./MatcherList').MatcherList;
exports.CSSDocumentRule = require('./CSSDocumentRule').CSSDocumentRule;
exports.CSSValue = require('./CSSValue').CSSValue;
exports.CSSValueExpression = require('./CSSValueExpression').CSSValueExpression;
exports.CSSScopeRule = require('./CSSScopeRule').CSSScopeRule;
exports.CSSLayerBlockRule = require('./CSSLayerBlockRule').CSSLayerBlockRule;
exports.CSSLayerStatementRule = require('./CSSLayerStatementRule').CSSLayerStatementRule;
exports.CSSPageRule = require('./CSSPageRule').CSSPageRule;
exports.parse = require('./parse').parse;
exports.clone = require('./clone').clone;
+3332
View File
File diff suppressed because it is too large Load Diff
+162
View File
@@ -0,0 +1,162 @@
// Shared regex patterns for CSS parsing and validation
// These patterns are compiled once and reused across multiple files for better performance
// Regex patterns for CSS parsing
var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g; // Match @keyframes and vendor-prefixed @keyframes
var beforeRulePortionRegExp = /{(?!.*{)|}(?!.*})|;(?!.*;)|\*\/(?!.*\*\/)/g; // Match the closest allowed character (a opening or closing brace, a semicolon or a comment ending) before the rule
var beforeRuleValidationRegExp = /^[\s{};]*(\*\/\s*)?$/; // Match that the portion before the rule is empty or contains only whitespace, semicolons, opening/closing braces, and optionally a comment ending (*/) followed by whitespace
var forwardRuleValidationRegExp = /(?:\s|\/\*|\{|\()/; // Match that the rule is followed by any whitespace, a opening comment, a condition opening parenthesis or a opening brace
var forwardImportRuleValidationRegExp = /(?:\s|\/\*|'|")/; // Match that the rule is followed by any whitespace, an opening comment, a single quote or double quote
var forwardRuleClosingBraceRegExp = /{[^{}]*}|}/; // Finds the next closing brace of a rule block
var forwardRuleSemicolonAndOpeningBraceRegExp = /^.*?({|;)/; // Finds the next semicolon or opening brace after the at-rule
// Regex patterns for CSS selector validation and parsing
var cssCustomIdentifierRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/; // Validates a css custom identifier
var startsWithCombinatorRegExp = /^\s*[>+~]/; // Checks if a selector starts with a CSS combinator (>, +, ~)
/**
* Parse `@page` selectorText for page name and pseudo-pages
* Valid formats:
* - (empty - no name, no pseudo-page)
* - `:left`, `:right`, `:first`, `:blank` (pseudo-page only)
* - `named` (named page only)
* - `named:first` (named page with single pseudo-page)
* - `named:first:left` (named page with multiple pseudo-pages)
*/
var atPageRuleSelectorRegExp = /^([^\s:]+)?((?::\w+)*)$/; // Validates @page rule selectors
// Regex patterns for CSSImportRule parsing
var layerRegExp = /layer\(([^)]*)\)/; // Matches layer() function in @import
var layerRuleNameRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/; // Validates layer name (same as custom identifier)
var doubleOrMoreSpacesRegExp = /\s{2,}/g; // Matches two or more consecutive whitespace characters
// Regex patterns for CSS escape sequences and identifiers
var startsWithHexEscapeRegExp = /^\\[0-9a-fA-F]/; // Checks if escape sequence starts with hex escape
var identStartCharRegExp = /[a-zA-Z_\u00A0-\uFFFF]/; // Valid identifier start character
var identCharRegExp = /^[a-zA-Z0-9_\-\u00A0-\uFFFF\\]/; // Valid identifier character
var specialCharsNeedEscapeRegExp = /[!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~\s]/; // Characters that need escaping
var combinatorOrSeparatorRegExp = /[\s>+~,()]/; // Selector boundaries and combinators
var afterHexEscapeSeparatorRegExp = /[\s>+~,(){}\[\]]/; // Characters that separate after hex escape
var trailingSpaceSeparatorRegExp = /[\s>+~,(){}]/; // Characters that allow trailing space
var endsWithHexEscapeRegExp = /\\[0-9a-fA-F]{1,6}\s+$/; // Matches selector ending with hex escape + space(s)
/**
* Regular expression to detect invalid characters in the value portion of a CSS style declaration.
*
* This regex matches a colon (:) that is not inside parentheses and not inside single or double quotes.
* It is used to ensure that the value part of a CSS property does not contain unexpected colons,
* which would indicate a malformed declaration (e.g., "color: foo:bar;" is invalid).
*
* The negative lookahead `(?![^(]*\))` ensures that the colon is not followed by a closing
* parenthesis without encountering an opening parenthesis, effectively ignoring colons inside
* function-like values (e.g., `url(data:image/png;base64,...)`).
*
* The lookahead `(?=(?:[^'"]|'[^']*'|"[^"]*")*$)` ensures that the colon is not inside single or double quotes,
* allowing colons within quoted strings (e.g., `content: ":";` or `background: url("foo:bar.png");`).
*
* Example:
* - `color: red;` // valid, does not match
* - `background: url(data:image/png;base64,...);` // valid, does not match
* - `content: ':';` // valid, does not match
* - `color: foo:bar;` // invalid, matches
*/
var basicStylePropertyValueValidationRegExp = /:(?![^(]*\))(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/;
// Attribute selector pattern: matches attribute-name operator value
// Operators: =, ~=, |=, ^=, $=, *=
// Rewritten to avoid ReDoS by using greedy match and trimming in JavaScript
var attributeSelectorContentRegExp = /^([^\s=~|^$*]+)\s*(~=|\|=|\^=|\$=|\*=|=)\s*(.+)$/;
// Selector validation patterns
var pseudoElementRegExp = /::[a-zA-Z][\w-]*|:(before|after|first-line|first-letter)(?![a-zA-Z0-9_-])/; // Matches pseudo-elements
var invalidCombinatorLtGtRegExp = /<>/; // Invalid <> combinator
var invalidCombinatorDoubleGtRegExp = />>/; // Invalid >> combinator
var consecutiveCombinatorsRegExp = /[>+~]\s*[>+~]/; // Invalid consecutive combinators
var invalidSlottedRegExp = /(?:^|[\s>+~,\[])slotted\s*\(/i; // Invalid slotted() without ::
var invalidPartRegExp = /(?:^|[\s>+~,\[])part\s*\(/i; // Invalid part() without ::
var invalidCueRegExp = /(?:^|[\s>+~,\[])cue\s*\(/i; // Invalid cue() without ::
var invalidCueRegionRegExp = /(?:^|[\s>+~,\[])cue-region\s*\(/i; // Invalid cue-region() without ::
var invalidNestingPattern = /&(?![.\#\[:>\+~\s])[a-zA-Z]/; // Invalid & followed by type selector
var emptyPseudoClassRegExp = /:(?:is|not|where|has)\(\s*\)/; // Empty pseudo-class like :is()
var whitespaceNormalizationRegExp = /(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g; // Normalize newlines outside quotes
var newlineRemovalRegExp = /\n/g; // Remove all newlines
var whitespaceAndDotRegExp = /[\s.]/; // Matches whitespace or dot
var declarationOrOpenBraceRegExp = /[{;}]/; // Matches declaration separator or open brace
var ampersandRegExp = /&/; // Matches nesting selector
var hexEscapeSequenceRegExp = /^([0-9a-fA-F]{1,6})[ \t\r\n\f]?/; // Matches hex escape sequence (1-6 hex digits optionally followed by whitespace)
var attributeCaseFlagRegExp = /^(.+?)\s+([is])$/i; // Matches case-sensitivity flag at end of attribute value
var prependedAmpersandRegExp = /^&\s+[:\\.]/; // Matches prepended ampersand pattern (& followed by space and : or .)
var openBraceGlobalRegExp = /{/g; // Matches opening braces (global)
var closeBraceGlobalRegExp = /}/g; // Matches closing braces (global)
var scopePreludeSplitRegExp = /\s*\)\s*to\s+\(/; // Splits scope prelude by ") to ("
var leadingWhitespaceRegExp = /^\s+/; // Matches leading whitespace (used to implement a ES5-compliant alternative to trimStart())
var doubleQuoteRegExp = /"/g; // Match all double quotes (for escaping in attribute values)
var backslashRegExp = /\\/g; // Match all backslashes (for escaping in attribute values)
var regexPatterns = {
// Parsing patterns
atKeyframesRegExp: atKeyframesRegExp,
beforeRulePortionRegExp: beforeRulePortionRegExp,
beforeRuleValidationRegExp: beforeRuleValidationRegExp,
forwardRuleValidationRegExp: forwardRuleValidationRegExp,
forwardImportRuleValidationRegExp: forwardImportRuleValidationRegExp,
forwardRuleClosingBraceRegExp: forwardRuleClosingBraceRegExp,
forwardRuleSemicolonAndOpeningBraceRegExp: forwardRuleSemicolonAndOpeningBraceRegExp,
// Selector validation patterns
cssCustomIdentifierRegExp: cssCustomIdentifierRegExp,
startsWithCombinatorRegExp: startsWithCombinatorRegExp,
atPageRuleSelectorRegExp: atPageRuleSelectorRegExp,
// Parsing patterns used in CSSImportRule
layerRegExp: layerRegExp,
layerRuleNameRegExp: layerRuleNameRegExp,
doubleOrMoreSpacesRegExp: doubleOrMoreSpacesRegExp,
// Escape sequence and identifier patterns
startsWithHexEscapeRegExp: startsWithHexEscapeRegExp,
identStartCharRegExp: identStartCharRegExp,
identCharRegExp: identCharRegExp,
specialCharsNeedEscapeRegExp: specialCharsNeedEscapeRegExp,
combinatorOrSeparatorRegExp: combinatorOrSeparatorRegExp,
afterHexEscapeSeparatorRegExp: afterHexEscapeSeparatorRegExp,
trailingSpaceSeparatorRegExp: trailingSpaceSeparatorRegExp,
endsWithHexEscapeRegExp: endsWithHexEscapeRegExp,
// Basic style property value validation
basicStylePropertyValueValidationRegExp: basicStylePropertyValueValidationRegExp,
// Attribute selector patterns
attributeSelectorContentRegExp: attributeSelectorContentRegExp,
// Selector validation patterns
pseudoElementRegExp: pseudoElementRegExp,
invalidCombinatorLtGtRegExp: invalidCombinatorLtGtRegExp,
invalidCombinatorDoubleGtRegExp: invalidCombinatorDoubleGtRegExp,
consecutiveCombinatorsRegExp: consecutiveCombinatorsRegExp,
invalidSlottedRegExp: invalidSlottedRegExp,
invalidPartRegExp: invalidPartRegExp,
invalidCueRegExp: invalidCueRegExp,
invalidCueRegionRegExp: invalidCueRegionRegExp,
invalidNestingPattern: invalidNestingPattern,
emptyPseudoClassRegExp: emptyPseudoClassRegExp,
whitespaceNormalizationRegExp: whitespaceNormalizationRegExp,
newlineRemovalRegExp: newlineRemovalRegExp,
whitespaceAndDotRegExp: whitespaceAndDotRegExp,
declarationOrOpenBraceRegExp: declarationOrOpenBraceRegExp,
ampersandRegExp: ampersandRegExp,
hexEscapeSequenceRegExp: hexEscapeSequenceRegExp,
attributeCaseFlagRegExp: attributeCaseFlagRegExp,
prependedAmpersandRegExp: prependedAmpersandRegExp,
openBraceGlobalRegExp: openBraceGlobalRegExp,
closeBraceGlobalRegExp: closeBraceGlobalRegExp,
scopePreludeSplitRegExp: scopePreludeSplitRegExp,
leadingWhitespaceRegExp: leadingWhitespaceRegExp,
doubleQuoteRegExp: doubleQuoteRegExp,
backslashRegExp: backslashRegExp
};
//.CommonJS
exports.regexPatterns = regexPatterns;
///CommonJS
+32
View File
@@ -0,0 +1,32 @@
{
"name": "@acemir/cssom",
"description": "CSS Object Model implementation and CSS parser",
"keywords": [
"CSS",
"CSSOM",
"parser",
"styleSheet"
],
"version": "0.9.31",
"author": "Nikita Vasilyev <me@elv1s.ru>",
"contributors": [
"Acemir Sousa Mendes <acemirsm@gmail.com>"
],
"repository": "acemir/CSSOM",
"files": [
"lib/",
"build/"
],
"browser": "./build/CSSOM.js",
"main": "./lib/index.js",
"license": "MIT",
"scripts": {
"build": "node build.js",
"release": "npm run build && changeset publish"
},
"devDependencies": {
"@changesets/changelog-github": "^0.5.2",
"@changesets/cli": "^2.29.8",
"@changesets/get-release-plan": "^4.0.14"
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 asamuzaK (Kazz)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
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 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.
+316
View File
@@ -0,0 +1,316 @@
# CSS color
[![build](https://github.com/asamuzaK/cssColor/actions/workflows/node.js.yml/badge.svg)](https://github.com/asamuzaK/cssColor/actions/workflows/node.js.yml)
[![CodeQL](https://github.com/asamuzaK/cssColor/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/asamuzaK/cssColor/actions/workflows/github-code-scanning/codeql)
[![npm (scoped)](https://img.shields.io/npm/v/@asamuzakjp/css-color)](https://www.npmjs.com/package/@asamuzakjp/css-color)
Resolve and convert CSS colors.
## Install
```console
npm i @asamuzakjp/css-color
```
## Usage
```javascript
import { convert, resolve, utils } from '@asamuzakjp/css-color';
const resolvedValue = resolve(
'color-mix(in oklab, lch(67.5345 42.5 258.2), color(srgb 0 0.5 0))'
);
// 'oklab(0.620754 -0.0931934 -0.00374881)'
const convertedValue = convert.colorToHex('lab(46.2775% -47.5621 48.5837)');
// '#008000'
const result = utils.isColor('green');
// true
```
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
### resolve(color, opt)
resolves CSS color
#### Parameters
- `color` **[string][133]** color value
- system colors are not supported
- `opt` **[object][135]?** options (optional, default `{}`)
- `opt.currentColor` **[string][133]?**
- color to use for `currentcolor` keyword
- if omitted, it will be treated as a missing color,
i.e. `rgb(none none none / none)`
- `opt.customProperty` **[object][135]?**
- custom properties
- pair of `--` prefixed property name as a key and it's value,
e.g.
```javascript
const opt = {
customProperty: {
'--some-color': '#008000',
'--some-length': '16px'
}
};
```
- and/or `callback` function to get the value of the custom property,
e.g.
```javascript
const node = document.getElementById('foo');
const opt = {
customProperty: {
callback: node.style.getPropertyValue
}
};
```
- `opt.dimension` **[object][135]?**
- dimension, e.g. for converting relative length to pixels
- pair of unit as a key and number in pixels as it's value,
e.g. suppose `1em === 12px`, `1rem === 16px` and `100vw === 1024px`, then
```javascript
const opt = {
dimension: {
em: 12,
rem: 16,
vw: 10.24
}
};
```
- and/or `callback` function to get the value as a number in pixels,
e.g.
```javascript
const opt = {
dimension: {
callback: unit => {
switch (unit) {
case 'em':
return 12;
case 'rem':
return 16;
case 'vw':
return 10.24;
default:
return;
}
}
}
};
```
- `opt.format` **[string][133]?**
- output format, one of below
- `computedValue` (default), [computed value][139] of the color
- `specifiedValue`, [specified value][140] of the color
- `hex`, hex color notation, i.e. `#rrggbb`
- `hexAlpha`, hex color notation with alpha channel, i.e. `#rrggbbaa`
Returns **[string][133]?** one of `rgba?()`, `#rrggbb(aa)?`, `color-name`, `color(color-space r g b / alpha)`, `color(color-space x y z / alpha)`, `(ok)?lab(l a b / alpha)`, `(ok)?lch(l c h / alpha)`, `'(empty-string)'`, `null`
- in `computedValue`, values are numbers, however `rgb()` values are integers
- in `specifiedValue`, returns `empty string` for unknown and/or invalid color
- in `hex`, returns `null` for `transparent`, and also returns `null` if any of `r`, `g`, `b`, `alpha` is not a number
- in `hexAlpha`, returns `#00000000` for `transparent`, however returns `null` if any of `r`, `g`, `b`, `alpha` is not a number
### convert
Contains various color conversion functions.
### convert.numberToHex(value)
convert number to hex string
#### Parameters
- `value` **[number][134]** color value
Returns **[string][133]** hex string: 00..ff
### convert.colorToHex(value, opt)
convert color to hex
#### Parameters
- `value` **[string][133]** color value
- `opt` **[object][135]?** options (optional, default `{}`)
- `opt.alpha` **[boolean][136]?** return in #rrggbbaa notation
- `opt.customProperty` **[object][135]?**
- custom properties, see `resolve()` function above
- `opt.dimension` **[object][135]?**
- dimension, see `resolve()` function above
Returns **[string][133]** #rrggbb(aa)?
### convert.colorToHsl(value, opt)
convert color to hsl
#### Parameters
- `value` **[string][133]** color value
- `opt` **[object][135]?** options (optional, default `{}`)
- `opt.customProperty` **[object][135]?**
- custom properties, see `resolve()` function above
- `opt.dimension` **[object][135]?**
- dimension, see `resolve()` function above
Returns **[Array][137]<[number][134]>** \[h, s, l, alpha]
### convert.colorToHwb(value, opt)
convert color to hwb
#### Parameters
- `value` **[string][133]** color value
- `opt` **[object][135]?** options (optional, default `{}`)
- `opt.customProperty` **[object][135]?**
- custom properties, see `resolve()` function above
- `opt.dimension` **[object][135]?**
- dimension, see `resolve()` function above
Returns **[Array][137]<[number][134]>** \[h, w, b, alpha]
### convert.colorToLab(value, opt)
convert color to lab
#### Parameters
- `value` **[string][133]** color value
- `opt` **[object][135]?** options (optional, default `{}`)
- `opt.customProperty` **[object][135]?**
- custom properties, see `resolve()` function above
- `opt.dimension` **[object][135]?**
- dimension, see `resolve()` function above
Returns **[Array][137]<[number][134]>** \[l, a, b, alpha]
### convert.colorToLch(value, opt)
convert color to lch
#### Parameters
- `value` **[string][133]** color value
- `opt` **[object][135]?** options (optional, default `{}`)
- `opt.customProperty` **[object][135]?**
- custom properties, see `resolve()` function above
- `opt.dimension` **[object][135]?**
- dimension, see `resolve()` function above
Returns **[Array][137]<[number][134]>** \[l, c, h, alpha]
### convert.colorToOklab(value, opt)
convert color to oklab
#### Parameters
- `value` **[string][133]** color value
- `opt` **[object][135]?** options (optional, default `{}`)
- `opt.customProperty` **[object][135]?**
- custom properties, see `resolve()` function above
- `opt.dimension` **[object][135]?**
- dimension, see `resolve()` function above
Returns **[Array][137]<[number][134]>** \[l, a, b, alpha]
### convert.colorToOklch(value, opt)
convert color to oklch
#### Parameters
- `value` **[string][133]** color value
- `opt` **[object][135]?** options (optional, default `{}`)
- `opt.customProperty` **[object][135]?**
- custom properties, see `resolve()` function above
- `opt.dimension` **[object][135]?**
- dimension, see `resolve()` function above
Returns **[Array][137]<[number][134]>** \[l, c, h, alpha]
### convert.colorToRgb(value, opt)
convert color to rgb
#### Parameters
- `value` **[string][133]** color value
- `opt` **[object][135]?** options (optional, default `{}`)
- `opt.customProperty` **[object][135]?**
- custom properties, see `resolve()` function above
- `opt.dimension` **[object][135]?**
- dimension, see `resolve()` function above
Returns **[Array][137]<[number][134]>** \[r, g, b, alpha]
### convert.colorToXyz(value, opt)
convert color to xyz
#### Parameters
- `value` **[string][133]** color value
- `opt` **[object][135]?** options (optional, default `{}`)
- `opt.customProperty` **[object][135]?**
- custom properties, see `resolve()` function above
- `opt.dimension` **[object][135]?**
- dimension, see `resolve()` function above
- `opt.d50` **[boolean][136]?** xyz in d50 white point
Returns **[Array][137]<[number][134]>** \[x, y, z, alpha]
### convert.colorToXyzD50(value, opt)
convert color to xyz-d50
#### Parameters
- `value` **[string][133]** color value
- `opt` **[object][135]?** options (optional, default `{}`)
- `opt.customProperty` **[object][135]?**
- custom properties, see `resolve()` function above
- `opt.dimension` **[object][135]?**
- dimension, see `resolve()` function above
Returns **[Array][137]<[number][134]>** \[x, y, z, alpha]
### utils
Contains utility functions.
### utils.isColor(color)
is valid color type
#### Parameters
- `color` **[string][133]** color value
- system colors are not supported
Returns **[boolean][136]**
## Acknowledgments
The following resources have been of great help in the development of the CSS color.
- [csstools/postcss-plugins](https://github.com/csstools/postcss-plugins)
- [lru-cache](https://github.com/isaacs/node-lru-cache)
---
Copyright (c) 2024 [asamuzaK (Kazz)](https://github.com/asamuzaK/)
[133]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[134]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[135]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[136]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[137]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[138]: https://w3c.github.io/csswg-drafts/css-color-4/#color-conversion-code
[139]: https://developer.mozilla.org/en-US/docs/Web/CSS/computed_value
[140]: https://developer.mozilla.org/en-US/docs/Web/CSS/specified_value
[141]: https://www.npmjs.com/package/@csstools/css-calc
+18
View File
@@ -0,0 +1,18 @@
/*!
* CSS color - Resolve, parse, convert CSS color.
* @license MIT
* @copyright asamuzaK (Kazz)
* @see {@link https://github.com/asamuzaK/cssColor/blob/main/LICENSE}
*/
export { convert } from './js/convert.js';
export { resolve } from './js/resolve.js';
export declare const utils: {
cssCalc: (value: string, opt?: import('./js/typedef.js').Options) => string;
cssVar: (value: string, opt?: import('./js/typedef.js').Options) => string;
extractDashedIdent: (value: string) => string[];
isColor: (value: unknown, opt?: import('./js/typedef.js').Options) => boolean;
isGradient: (value: string, opt?: import('./js/typedef.js').Options) => boolean;
resolveGradient: (value: string, opt?: import('./js/typedef.js').Options) => string;
resolveLengthInPixels: (value: number | string, unit: string | undefined, opt?: import('./js/typedef.js').Options) => number;
splitValue: (value: string, opt?: import('./js/typedef.js').Options) => string[];
};
+22
View File
@@ -0,0 +1,22 @@
import { cssCalc } from "./js/css-calc.js";
import { resolveGradient, isGradient } from "./js/css-gradient.js";
import { cssVar } from "./js/css-var.js";
import { splitValue, resolveLengthInPixels, isColor, extractDashedIdent } from "./js/util.js";
import { convert } from "./js/convert.js";
import { resolve } from "./js/resolve.js";
const utils = {
cssCalc,
cssVar,
extractDashedIdent,
isColor,
isGradient,
resolveGradient,
resolveLengthInPixels,
splitValue
};
export {
convert,
resolve,
utils
};
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["/*!\n * CSS color - Resolve, parse, convert CSS color.\n * @license MIT\n * @copyright asamuzaK (Kazz)\n * @see {@link https://github.com/asamuzaK/cssColor/blob/main/LICENSE}\n */\n\nimport { cssCalc } from './js/css-calc';\nimport { isGradient, resolveGradient } from './js/css-gradient';\nimport { cssVar } from './js/css-var';\nimport {\n extractDashedIdent,\n isColor,\n resolveLengthInPixels,\n splitValue\n} from './js/util';\n\nexport { convert } from './js/convert';\nexport { resolve } from './js/resolve';\n/* utils */\nexport const utils = {\n cssCalc,\n cssVar,\n extractDashedIdent,\n isColor,\n isGradient,\n resolveGradient,\n resolveLengthInPixels,\n splitValue\n};\n"],"names":[],"mappings":";;;;;;AAoBO,MAAM,QAAQ;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;"}
+44
View File
@@ -0,0 +1,44 @@
import { LRUCache } from 'lru-cache';
import { Options } from './typedef.js';
/**
* CacheItem
*/
export declare class CacheItem {
#private;
/**
* constructor
*/
constructor(item: unknown, isNull?: boolean);
get item(): unknown;
get isNull(): boolean;
}
/**
* NullObject
*/
export declare class NullObject extends CacheItem {
/**
* constructor
*/
constructor();
}
export declare const lruCache: LRUCache<{}, {}, unknown>;
/**
* set cache
* @param key - cache key
* @param value - value to cache
* @returns void
*/
export declare const setCache: (key: string, value: unknown) => void;
/**
* get cache
* @param key - cache key
* @returns cached item or false otherwise
*/
export declare const getCache: (key: string) => CacheItem | boolean;
/**
* create cache key
* @param keyData - key data
* @param [opt] - options
* @returns cache key
*/
export declare const createCacheKey: (keyData: Record<string, string>, opt?: Options) => string;
+72
View File
@@ -0,0 +1,72 @@
import { LRUCache } from "lru-cache";
import { valueToJsonString } from "./util.js";
const MAX_CACHE = 4096;
class CacheItem {
/* private */
#isNull;
#item;
/**
* constructor
*/
constructor(item, isNull = false) {
this.#item = item;
this.#isNull = !!isNull;
}
get item() {
return this.#item;
}
get isNull() {
return this.#isNull;
}
}
class NullObject extends CacheItem {
/**
* constructor
*/
constructor() {
super(/* @__PURE__ */ Symbol("null"), true);
}
}
const lruCache = new LRUCache({
max: MAX_CACHE
});
const setCache = (key, value) => {
if (key) {
if (value === null) {
lruCache.set(key, new NullObject());
} else if (value instanceof CacheItem) {
lruCache.set(key, value);
} else {
lruCache.set(key, new CacheItem(value));
}
}
};
const getCache = (key) => {
if (key && lruCache.has(key)) {
const item = lruCache.get(key);
if (item instanceof CacheItem) {
return item;
}
lruCache.delete(key);
return false;
}
return false;
};
const createCacheKey = (keyData, opt = {}) => {
const { customProperty = {}, dimension = {} } = opt;
let cacheKey = "";
if (keyData && Object.keys(keyData).length && typeof customProperty.callback !== "function" && typeof dimension.callback !== "function") {
keyData.opt = valueToJsonString(opt);
cacheKey = valueToJsonString(keyData);
}
return cacheKey;
};
export {
CacheItem,
NullObject,
createCacheKey,
getCache,
lruCache,
setCache
};
//# sourceMappingURL=cache.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"cache.js","sources":["../../../src/js/cache.ts"],"sourcesContent":["/**\n * cache\n */\n\nimport { LRUCache } from 'lru-cache';\nimport { Options } from './typedef';\nimport { valueToJsonString } from './util';\n\n/* numeric constants */\nconst MAX_CACHE = 4096;\n\n/**\n * CacheItem\n */\nexport class CacheItem {\n /* private */\n #isNull: boolean;\n #item: unknown;\n\n /**\n * constructor\n */\n constructor(item: unknown, isNull: boolean = false) {\n this.#item = item;\n this.#isNull = !!isNull;\n }\n\n get item() {\n return this.#item;\n }\n\n get isNull() {\n return this.#isNull;\n }\n}\n\n/**\n * NullObject\n */\nexport class NullObject extends CacheItem {\n /**\n * constructor\n */\n constructor() {\n super(Symbol('null'), true);\n }\n}\n\n/*\n * lru cache\n */\nexport const lruCache = new LRUCache({\n max: MAX_CACHE\n});\n\n/**\n * set cache\n * @param key - cache key\n * @param value - value to cache\n * @returns void\n */\nexport const setCache = (key: string, value: unknown): void => {\n if (key) {\n if (value === null) {\n lruCache.set(key, new NullObject());\n } else if (value instanceof CacheItem) {\n lruCache.set(key, value);\n } else {\n lruCache.set(key, new CacheItem(value));\n }\n }\n};\n\n/**\n * get cache\n * @param key - cache key\n * @returns cached item or false otherwise\n */\nexport const getCache = (key: string): CacheItem | boolean => {\n if (key && lruCache.has(key)) {\n const item = lruCache.get(key);\n if (item instanceof CacheItem) {\n return item;\n }\n // delete unexpected cached item\n lruCache.delete(key);\n return false;\n }\n return false;\n};\n\n/**\n * create cache key\n * @param keyData - key data\n * @param [opt] - options\n * @returns cache key\n */\nexport const createCacheKey = (\n keyData: Record<string, string>,\n opt: Options = {}\n): string => {\n const { customProperty = {}, dimension = {} } = opt;\n let cacheKey = '';\n if (\n keyData &&\n Object.keys(keyData).length &&\n typeof customProperty.callback !== 'function' &&\n typeof dimension.callback !== 'function'\n ) {\n keyData.opt = valueToJsonString(opt);\n cacheKey = valueToJsonString(keyData);\n }\n return cacheKey;\n};\n"],"names":[],"mappings":";;AASA,MAAM,YAAY;AAKX,MAAM,UAAU;AAAA;AAAA,EAErB;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,MAAe,SAAkB,OAAO;AAClD,SAAK,QAAQ;AACb,SAAK,UAAU,CAAC,CAAC;AAAA,EACnB;AAAA,EAEA,IAAI,OAAO;AACT,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK;AAAA,EACd;AACF;AAKO,MAAM,mBAAmB,UAAU;AAAA;AAAA;AAAA;AAAA,EAIxC,cAAc;AACZ,UAAM,uBAAO,MAAM,GAAG,IAAI;AAAA,EAC5B;AACF;AAKO,MAAM,WAAW,IAAI,SAAS;AAAA,EACnC,KAAK;AACP,CAAC;AAQM,MAAM,WAAW,CAAC,KAAa,UAAyB;AAC7D,MAAI,KAAK;AACP,QAAI,UAAU,MAAM;AAClB,eAAS,IAAI,KAAK,IAAI,WAAA,CAAY;AAAA,IACpC,WAAW,iBAAiB,WAAW;AACrC,eAAS,IAAI,KAAK,KAAK;AAAA,IACzB,OAAO;AACL,eAAS,IAAI,KAAK,IAAI,UAAU,KAAK,CAAC;AAAA,IACxC;AAAA,EACF;AACF;AAOO,MAAM,WAAW,CAAC,QAAqC;AAC5D,MAAI,OAAO,SAAS,IAAI,GAAG,GAAG;AAC5B,UAAM,OAAO,SAAS,IAAI,GAAG;AAC7B,QAAI,gBAAgB,WAAW;AAC7B,aAAO;AAAA,IACT;AAEA,aAAS,OAAO,GAAG;AACnB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAQO,MAAM,iBAAiB,CAC5B,SACA,MAAe,OACJ;AACX,QAAM,EAAE,iBAAiB,CAAA,GAAI,YAAY,CAAA,MAAO;AAChD,MAAI,WAAW;AACf,MACE,WACA,OAAO,KAAK,OAAO,EAAE,UACrB,OAAO,eAAe,aAAa,cACnC,OAAO,UAAU,aAAa,YAC9B;AACA,YAAQ,MAAM,kBAAkB,GAAG;AACnC,eAAW,kBAAkB,OAAO;AAAA,EACtC;AACA,SAAO;AACT;"}
+537
View File
@@ -0,0 +1,537 @@
import { NullObject } from './cache.js';
import { ColorChannels, Options, SpecifiedColorChannels } from './typedef.js';
/**
* @type TriColorChannels - color channels without alpha
*/
type TriColorChannels = [x: number, y: number, z: number];
/**
* @type ColorMatrix - color matrix
*/
type ColorMatrix = [
r1: TriColorChannels,
r2: TriColorChannels,
r3: TriColorChannels
];
/**
* named colors
*/
export declare const NAMED_COLORS: {
readonly aliceblue: [240, 248, 255];
readonly antiquewhite: [250, 235, 215];
readonly aqua: [0, 255, 255];
readonly aquamarine: [127, 255, 212];
readonly azure: [240, 255, 255];
readonly beige: [245, 245, 220];
readonly bisque: [255, 228, 196];
readonly black: [0, 0, 0];
readonly blanchedalmond: [255, 235, 205];
readonly blue: [0, 0, 255];
readonly blueviolet: [138, 43, 226];
readonly brown: [165, 42, 42];
readonly burlywood: [222, 184, 135];
readonly cadetblue: [95, 158, 160];
readonly chartreuse: [127, 255, 0];
readonly chocolate: [210, 105, 30];
readonly coral: [255, 127, 80];
readonly cornflowerblue: [100, 149, 237];
readonly cornsilk: [255, 248, 220];
readonly crimson: [220, 20, 60];
readonly cyan: [0, 255, 255];
readonly darkblue: [0, 0, 139];
readonly darkcyan: [0, 139, 139];
readonly darkgoldenrod: [184, 134, 11];
readonly darkgray: [169, 169, 169];
readonly darkgreen: [0, 100, 0];
readonly darkgrey: [169, 169, 169];
readonly darkkhaki: [189, 183, 107];
readonly darkmagenta: [139, 0, 139];
readonly darkolivegreen: [85, 107, 47];
readonly darkorange: [255, 140, 0];
readonly darkorchid: [153, 50, 204];
readonly darkred: [139, 0, 0];
readonly darksalmon: [233, 150, 122];
readonly darkseagreen: [143, 188, 143];
readonly darkslateblue: [72, 61, 139];
readonly darkslategray: [47, 79, 79];
readonly darkslategrey: [47, 79, 79];
readonly darkturquoise: [0, 206, 209];
readonly darkviolet: [148, 0, 211];
readonly deeppink: [255, 20, 147];
readonly deepskyblue: [0, 191, 255];
readonly dimgray: [105, 105, 105];
readonly dimgrey: [105, 105, 105];
readonly dodgerblue: [30, 144, 255];
readonly firebrick: [178, 34, 34];
readonly floralwhite: [255, 250, 240];
readonly forestgreen: [34, 139, 34];
readonly fuchsia: [255, 0, 255];
readonly gainsboro: [220, 220, 220];
readonly ghostwhite: [248, 248, 255];
readonly gold: [255, 215, 0];
readonly goldenrod: [218, 165, 32];
readonly gray: [128, 128, 128];
readonly green: [0, 128, 0];
readonly greenyellow: [173, 255, 47];
readonly grey: [128, 128, 128];
readonly honeydew: [240, 255, 240];
readonly hotpink: [255, 105, 180];
readonly indianred: [205, 92, 92];
readonly indigo: [75, 0, 130];
readonly ivory: [255, 255, 240];
readonly khaki: [240, 230, 140];
readonly lavender: [230, 230, 250];
readonly lavenderblush: [255, 240, 245];
readonly lawngreen: [124, 252, 0];
readonly lemonchiffon: [255, 250, 205];
readonly lightblue: [173, 216, 230];
readonly lightcoral: [240, 128, 128];
readonly lightcyan: [224, 255, 255];
readonly lightgoldenrodyellow: [250, 250, 210];
readonly lightgray: [211, 211, 211];
readonly lightgreen: [144, 238, 144];
readonly lightgrey: [211, 211, 211];
readonly lightpink: [255, 182, 193];
readonly lightsalmon: [255, 160, 122];
readonly lightseagreen: [32, 178, 170];
readonly lightskyblue: [135, 206, 250];
readonly lightslategray: [119, 136, 153];
readonly lightslategrey: [119, 136, 153];
readonly lightsteelblue: [176, 196, 222];
readonly lightyellow: [255, 255, 224];
readonly lime: [0, 255, 0];
readonly limegreen: [50, 205, 50];
readonly linen: [250, 240, 230];
readonly magenta: [255, 0, 255];
readonly maroon: [128, 0, 0];
readonly mediumaquamarine: [102, 205, 170];
readonly mediumblue: [0, 0, 205];
readonly mediumorchid: [186, 85, 211];
readonly mediumpurple: [147, 112, 219];
readonly mediumseagreen: [60, 179, 113];
readonly mediumslateblue: [123, 104, 238];
readonly mediumspringgreen: [0, 250, 154];
readonly mediumturquoise: [72, 209, 204];
readonly mediumvioletred: [199, 21, 133];
readonly midnightblue: [25, 25, 112];
readonly mintcream: [245, 255, 250];
readonly mistyrose: [255, 228, 225];
readonly moccasin: [255, 228, 181];
readonly navajowhite: [255, 222, 173];
readonly navy: [0, 0, 128];
readonly oldlace: [253, 245, 230];
readonly olive: [128, 128, 0];
readonly olivedrab: [107, 142, 35];
readonly orange: [255, 165, 0];
readonly orangered: [255, 69, 0];
readonly orchid: [218, 112, 214];
readonly palegoldenrod: [238, 232, 170];
readonly palegreen: [152, 251, 152];
readonly paleturquoise: [175, 238, 238];
readonly palevioletred: [219, 112, 147];
readonly papayawhip: [255, 239, 213];
readonly peachpuff: [255, 218, 185];
readonly peru: [205, 133, 63];
readonly pink: [255, 192, 203];
readonly plum: [221, 160, 221];
readonly powderblue: [176, 224, 230];
readonly purple: [128, 0, 128];
readonly rebeccapurple: [102, 51, 153];
readonly red: [255, 0, 0];
readonly rosybrown: [188, 143, 143];
readonly royalblue: [65, 105, 225];
readonly saddlebrown: [139, 69, 19];
readonly salmon: [250, 128, 114];
readonly sandybrown: [244, 164, 96];
readonly seagreen: [46, 139, 87];
readonly seashell: [255, 245, 238];
readonly sienna: [160, 82, 45];
readonly silver: [192, 192, 192];
readonly skyblue: [135, 206, 235];
readonly slateblue: [106, 90, 205];
readonly slategray: [112, 128, 144];
readonly slategrey: [112, 128, 144];
readonly snow: [255, 250, 250];
readonly springgreen: [0, 255, 127];
readonly steelblue: [70, 130, 180];
readonly tan: [210, 180, 140];
readonly teal: [0, 128, 128];
readonly thistle: [216, 191, 216];
readonly tomato: [255, 99, 71];
readonly turquoise: [64, 224, 208];
readonly violet: [238, 130, 238];
readonly wheat: [245, 222, 179];
readonly white: [255, 255, 255];
readonly whitesmoke: [245, 245, 245];
readonly yellow: [255, 255, 0];
readonly yellowgreen: [154, 205, 50];
};
/**
* cache invalid color value
* @param key - cache key
* @param nullable - is nullable
* @returns cached value
*/
export declare const cacheInvalidColorValue: (cacheKey: string, format: string, nullable?: boolean) => SpecifiedColorChannels | string | NullObject;
/**
* resolve invalid color value
* @param format - output format
* @param nullable - is nullable
* @returns resolved value
*/
export declare const resolveInvalidColorValue: (format: string, nullable?: boolean) => SpecifiedColorChannels | string | NullObject;
/**
* validate color components
* @param arr - color components
* @param [opt] - options
* @param [opt.alpha] - alpha channel
* @param [opt.minLength] - min length
* @param [opt.maxLength] - max length
* @param [opt.minRange] - min range
* @param [opt.maxRange] - max range
* @param [opt.validateRange] - validate range
* @returns result - validated color components
*/
export declare const validateColorComponents: (arr: ColorChannels | TriColorChannels, opt?: {
alpha?: boolean;
minLength?: number;
maxLength?: number;
minRange?: number;
maxRange?: number;
validateRange?: boolean;
}) => ColorChannels | TriColorChannels;
/**
* transform matrix
* @param mtx - 3 * 3 matrix
* @param vct - vector
* @param [skip] - skip validate
* @returns TriColorChannels - [p1, p2, p3]
*/
export declare const transformMatrix: (mtx: ColorMatrix, vct: TriColorChannels, skip?: boolean) => TriColorChannels;
/**
* normalize color components
* @param colorA - color components [v1, v2, v3, v4]
* @param colorB - color components [v1, v2, v3, v4]
* @param [skip] - skip validate
* @returns result - [colorA, colorB]
*/
export declare const normalizeColorComponents: (colorA: [number | string, number | string, number | string, number | string], colorB: [number | string, number | string, number | string, number | string], skip?: boolean) => [ColorChannels, ColorChannels];
/**
* number to hex string
* @param value - numeric value
* @returns hex string
*/
export declare const numberToHexString: (value: number) => string;
/**
* angle to deg
* @param angle
* @returns deg: 0..360
*/
export declare const angleToDeg: (angle: string) => number;
/**
* parse alpha
* @param [alpha] - alpha value
* @returns alpha: 0..1
*/
export declare const parseAlpha: (alpha?: string) => number;
/**
* parse hex alpha
* @param value - alpha value in hex string
* @returns alpha: 0..1
*/
export declare const parseHexAlpha: (value: string) => number;
/**
* transform rgb to linear rgb
* @param rgb - [r, g, b] r|g|b: 0..255
* @param [skip] - skip validate
* @returns TriColorChannels - [r, g, b] r|g|b: 0..1
*/
export declare const transformRgbToLinearRgb: (rgb: TriColorChannels, skip?: boolean) => TriColorChannels;
/**
* transform rgb to xyz
* @param rgb - [r, g, b] r|g|b: 0..255
* @param [skip] - skip validate
* @returns TriColorChannels - [x, y, z]
*/
export declare const transformRgbToXyz: (rgb: TriColorChannels, skip?: boolean) => TriColorChannels;
/**
* transform rgb to xyz-d50
* @param rgb - [r, g, b] r|g|b: 0..255 alpha: 0..1
* @returns TriColorChannels - [x, y, z]
*/
export declare const transformRgbToXyzD50: (rgb: TriColorChannels) => TriColorChannels;
/**
* transform linear rgb to rgb
* @param rgb - [r, g, b] r|g|b: 0..1
* @param [round] - round result
* @returns TriColorChannels - [r, g, b] r|g|b: 0..255
*/
export declare const transformLinearRgbToRgb: (rgb: TriColorChannels, round?: boolean) => TriColorChannels;
/**
* transform xyz to rgb
* @param xyz - [x, y, z]
* @param [skip] - skip validate
* @returns TriColorChannels - [r, g, b] r|g|b: 0..255
*/
export declare const transformXyzToRgb: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
/**
* transform xyz to xyz-d50
* @param xyz - [x, y, z]
* @returns TriColorChannels - [x, y, z]
*/
export declare const transformXyzToXyzD50: (xyz: TriColorChannels) => TriColorChannels;
/**
* transform xyz to hsl
* @param xyz - [x, y, z]
* @param [skip] - skip validate
* @returns TriColorChannels - [h, s, l]
*/
export declare const transformXyzToHsl: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
/**
* transform xyz to hwb
* @param xyz - [x, y, z]
* @param [skip] - skip validate
* @returns TriColorChannels - [h, w, b]
*/
export declare const transformXyzToHwb: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
/**
* transform xyz to oklab
* @param xyz - [x, y, z]
* @param [skip] - skip validate
* @returns TriColorChannels - [l, a, b]
*/
export declare const transformXyzToOklab: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
/**
* transform xyz to oklch
* @param xyz - [x, y, z]
* @param [skip] - skip validate
* @returns TriColorChannels - [l, c, h]
*/
export declare const transformXyzToOklch: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
/**
* transform xyz D50 to rgb
* @param xyz - [x, y, z]
* @param [skip] - skip validate
* @returns TriColorChannels - [r, g, b] r|g|b: 0..255
*/
export declare const transformXyzD50ToRgb: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
/**
* transform xyz-d50 to lab
* @param xyz - [x, y, z]
* @param [skip] - skip validate
* @returns TriColorChannels - [l, a, b]
*/
export declare const transformXyzD50ToLab: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
/**
* transform xyz-d50 to lch
* @param xyz - [x, y, z]
* @param [skip] - skip validate
* @returns TriColorChannels - [l, c, h]
*/
export declare const transformXyzD50ToLch: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
/**
* convert rgb to hex color
* @param rgb - [r, g, b, alpha] r|g|b: 0..255 alpha: 0..1
* @returns hex color
*/
export declare const convertRgbToHex: (rgb: ColorChannels) => string;
/**
* convert linear rgb to hex color
* @param rgb - [r, g, b, alpha] r|g|b|alpha: 0..1
* @param [skip] - skip validate
* @returns hex color
*/
export declare const convertLinearRgbToHex: (rgb: ColorChannels, skip?: boolean) => string;
/**
* convert xyz to hex color
* @param xyz - [x, y, z, alpha]
* @returns hex color
*/
export declare const convertXyzToHex: (xyz: ColorChannels) => string;
/**
* convert xyz D50 to hex color
* @param xyz - [x, y, z, alpha]
* @returns hex color
*/
export declare const convertXyzD50ToHex: (xyz: ColorChannels) => string;
/**
* convert hex color to rgb
* @param value - hex color value
* @returns ColorChannels - [r, g, b, alpha] r|g|b: 0..255 alpha: 0..1
*/
export declare const convertHexToRgb: (value: string) => ColorChannels;
/**
* convert hex color to linear rgb
* @param value - hex color value
* @returns ColorChannels - [r, g, b, alpha] r|g|b|alpha: 0..1
*/
export declare const convertHexToLinearRgb: (value: string) => ColorChannels;
/**
* convert hex color to xyz
* @param value - hex color value
* @returns ColorChannels - [x, y, z, alpha]
*/
export declare const convertHexToXyz: (value: string) => ColorChannels;
/**
* parse rgb()
* @param value - rgb color value
* @param [opt] - options
* @returns parsed color - ['rgb', r, g, b, alpha], '(empty)', NullObject
*/
export declare const parseRgb: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
/**
* parse hsl()
* @param value - hsl color value
* @param [opt] - options
* @returns parsed color - ['rgb', r, g, b, alpha], '(empty)', NullObject
*/
export declare const parseHsl: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
/**
* parse hwb()
* @param value - hwb color value
* @param [opt] - options
* @returns parsed color - ['rgb', r, g, b, alpha], '(empty)', NullObject
*/
export declare const parseHwb: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
/**
* parse lab()
* @param value - lab color value
* @param [opt] - options
* @returns parsed color
* - [xyz-d50, x, y, z, alpha], ['lab', l, a, b, alpha], '(empty)', NullObject
*/
export declare const parseLab: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
/**
* parse lch()
* @param value - lch color value
* @param [opt] - options
* @returns parsed color
* - ['xyz-d50', x, y, z, alpha], ['lch', l, c, h, alpha]
* - '(empty)', NullObject
*/
export declare const parseLch: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
/**
* parse oklab()
* @param value - oklab color value
* @param [opt] - options
* @returns parsed color
* - ['xyz-d65', x, y, z, alpha], ['oklab', l, a, b, alpha]
* - '(empty)', NullObject
*/
export declare const parseOklab: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
/**
* parse oklch()
* @param value - oklch color value
* @param [opt] - options
* @returns parsed color
* - ['xyz-d65', x, y, z, alpha], ['oklch', l, c, h, alpha]
* - '(empty)', NullObject
*/
export declare const parseOklch: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
/**
* parse color()
* @param value - color function value
* @param [opt] - options
* @returns parsed color
* - ['xyz-(d50|d65)', x, y, z, alpha], [cs, r, g, b, alpha]
* - '(empty)', NullObject
*/
export declare const parseColorFunc: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
/**
* parse color value
* @param value - CSS color value
* @param [opt] - options
* @returns parsed color
* - ['xyz-(d50|d65)', x, y, z, alpha], ['rgb', r, g, b, alpha]
* - value, '(empty)', NullObject
*/
export declare const parseColorValue: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
/**
* resolve color value
* @param value - CSS color value
* @param [opt] - options
* @returns resolved color
* - [cs, v1, v2, v3, alpha], value, '(empty)', NullObject
*/
export declare const resolveColorValue: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
/**
* resolve color()
* @param value - color function value
* @param [opt] - options
* @returns resolved color - [cs, v1, v2, v3, alpha], '(empty)', NullObject
*/
export declare const resolveColorFunc: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
/**
* convert color value to linear rgb
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels | NullObject - [r, g, b, alpha] r|g|b|alpha: 0..1
*/
export declare const convertColorToLinearRgb: (value: string, opt?: {
colorSpace?: string;
format?: string;
}) => ColorChannels | NullObject;
/**
* convert color value to rgb
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels | NullObject
* - [r, g, b, alpha] r|g|b: 0..255 alpha: 0..1
*/
export declare const convertColorToRgb: (value: string, opt?: Options) => ColorChannels | NullObject;
/**
* convert color value to xyz
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels | NullObject - [x, y, z, alpha]
*/
export declare const convertColorToXyz: (value: string, opt?: Options) => ColorChannels | NullObject;
/**
* convert color value to hsl
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels | NullObject - [h, s, l, alpha], hue may be powerless
*/
export declare const convertColorToHsl: (value: string, opt?: Options) => ColorChannels | [number | string, number, number, number] | NullObject;
/**
* convert color value to hwb
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels | NullObject - [h, w, b, alpha], hue may be powerless
*/
export declare const convertColorToHwb: (value: string, opt?: Options) => ColorChannels | [number | string, number, number, number] | NullObject;
/**
* convert color value to lab
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels | NullObject - [l, a, b, alpha]
*/
export declare const convertColorToLab: (value: string, opt?: Options) => ColorChannels | NullObject;
/**
* convert color value to lch
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels | NullObject - [l, c, h, alpha], hue may be powerless
*/
export declare const convertColorToLch: (value: string, opt?: Options) => ColorChannels | [number, number, number | string, number] | NullObject;
/**
* convert color value to oklab
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels | NullObject - [l, a, b, alpha]
*/
export declare const convertColorToOklab: (value: string, opt?: Options) => ColorChannels | NullObject;
/**
* convert color value to oklch
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels | NullObject - [l, c, h, alpha], hue may be powerless
*/
export declare const convertColorToOklch: (value: string, opt?: Options) => ColorChannels | [number, number, number | string, number] | NullObject;
/**
* resolve color-mix()
* @param value - color-mix color value
* @param [opt] - options
* @returns resolved color - [cs, v1, v2, v3, alpha], '(empty)'
*/
export declare const resolveColorMix: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
export {};
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+21
View File
@@ -0,0 +1,21 @@
/**
* common
*/
/**
* get type
* @param o - object to check
* @returns type of object
*/
export declare const getType: (o: unknown) => string;
/**
* is string
* @param o - object to check
* @returns result
*/
export declare const isString: (o: unknown) => o is string;
/**
* is string or number
* @param o - object to check
* @returns result
*/
export declare const isStringOrNumber: (o: unknown) => boolean;
+7
View File
@@ -0,0 +1,7 @@
const isString = (o) => typeof o === "string" || o instanceof String;
const isStringOrNumber = (o) => isString(o) || typeof o === "number";
export {
isString,
isStringOrNumber
};
//# sourceMappingURL=common.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"common.js","sources":["../../../src/js/common.ts"],"sourcesContent":["/**\n * common\n */\n\n/* numeric constants */\nconst TYPE_FROM = 8;\nconst TYPE_TO = -1;\n\n/**\n * get type\n * @param o - object to check\n * @returns type of object\n */\nexport const getType = (o: unknown): string =>\n Object.prototype.toString.call(o).slice(TYPE_FROM, TYPE_TO);\n\n/**\n * is string\n * @param o - object to check\n * @returns result\n */\nexport const isString = (o: unknown): o is string =>\n typeof o === 'string' || o instanceof String;\n\n/**\n * is string or number\n * @param o - object to check\n * @returns result\n */\nexport const isStringOrNumber = (o: unknown): boolean =>\n isString(o) || typeof o === 'number';\n"],"names":[],"mappings":"AAqBO,MAAM,WAAW,CAAC,MACvB,OAAO,MAAM,YAAY,aAAa;AAOjC,MAAM,mBAAmB,CAAC,MAC/B,SAAS,CAAC,KAAK,OAAO,MAAM;"}
File diff suppressed because one or more lines are too long
+101
View File
@@ -0,0 +1,101 @@
const _DIGIT = "(?:0|[1-9]\\d*)";
const _COMPARE = "clamp|max|min";
const _EXPO = "exp|hypot|log|pow|sqrt";
const _SIGN = "abs|sign";
const _STEP = "mod|rem|round";
const _TRIG = "a?(?:cos|sin|tan)|atan2";
const _MATH = `${_COMPARE}|${_EXPO}|${_SIGN}|${_STEP}|${_TRIG}`;
const _CALC = `calc|${_MATH}`;
const _VAR = `var|${_CALC}`;
const ANGLE = "deg|g?rad|turn";
const LENGTH = "[cm]m|[dls]?v(?:[bhiw]|max|min)|in|p[ctx]|q|r?(?:[cl]h|cap|e[mx]|ic)";
const NUM = `[+-]?(?:${_DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${_DIGIT})?`;
const NUM_POSITIVE = `\\+?(?:${_DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${_DIGIT})?`;
const NONE = "none";
const PCT = `${NUM}%`;
const SYN_FN_CALC = `^(?:${_CALC})\\(|(?<=[*\\/\\s\\(])(?:${_CALC})\\(`;
const SYN_FN_MATH_START = `^(?:${_MATH})\\($`;
const SYN_FN_VAR = "^var\\(|(?<=[*\\/\\s\\(])var\\(";
const SYN_FN_VAR_START = `^(?:${_VAR})\\(`;
const _ALPHA = `(?:\\s*\\/\\s*(?:${NUM}|${PCT}|${NONE}))?`;
const _ALPHA_LV3 = `(?:\\s*,\\s*(?:${NUM}|${PCT}))?`;
const _COLOR_FUNC = "(?:ok)?l(?:ab|ch)|color|hsla?|hwb|rgba?";
const _COLOR_KEY = "[a-z]+|#[\\da-f]{3}|#[\\da-f]{4}|#[\\da-f]{6}|#[\\da-f]{8}";
const _CS_HUE = "(?:ok)?lch|hsl|hwb";
const _CS_HUE_ARC = "(?:de|in)creasing|longer|shorter";
const _NUM_ANGLE = `${NUM}(?:${ANGLE})?`;
const _NUM_ANGLE_NONE = `(?:${NUM}(?:${ANGLE})?|${NONE})`;
const _NUM_PCT_NONE = `(?:${NUM}|${PCT}|${NONE})`;
const CS_HUE = `(?:${_CS_HUE})(?:\\s(?:${_CS_HUE_ARC})\\shue)?`;
const CS_HUE_CAPT = `(${_CS_HUE})(?:\\s(${_CS_HUE_ARC})\\shue)?`;
const CS_LAB = "(?:ok)?lab";
const CS_LCH = "(?:ok)?lch";
const CS_SRGB = "srgb(?:-linear)?";
const CS_RGB = `(?:a98|prophoto)-rgb|display-p3|rec2020|${CS_SRGB}`;
const CS_XYZ = "xyz(?:-d(?:50|65))?";
const CS_RECT = `${CS_LAB}|${CS_RGB}|${CS_XYZ}`;
const CS_MIX = `${CS_HUE}|${CS_RECT}`;
const FN_COLOR = "color(";
const FN_LIGHT_DARK = "light-dark(";
const FN_MIX = "color-mix(";
const FN_REL = `(?:${_COLOR_FUNC})\\(\\s*from\\s+`;
const FN_REL_CAPT = `(${_COLOR_FUNC})\\(\\s*from\\s+`;
const FN_VAR = "var(";
const SYN_FN_COLOR = `(?:${CS_RGB}|${CS_XYZ})(?:\\s+${_NUM_PCT_NONE}){3}${_ALPHA}`;
const SYN_FN_LIGHT_DARK = "^light-dark\\(";
const SYN_FN_REL = `^${FN_REL}|(?<=[\\s])${FN_REL}`;
const SYN_HSL = `${_NUM_ANGLE_NONE}(?:\\s+${_NUM_PCT_NONE}){2}${_ALPHA}`;
const SYN_HSL_LV3 = `${_NUM_ANGLE}(?:\\s*,\\s*${PCT}){2}${_ALPHA_LV3}`;
const SYN_LCH = `(?:${_NUM_PCT_NONE}\\s+){2}${_NUM_ANGLE_NONE}${_ALPHA}`;
const SYN_MOD = `${_NUM_PCT_NONE}(?:\\s+${_NUM_PCT_NONE}){2}${_ALPHA}`;
const SYN_RGB_LV3 = `(?:${NUM}(?:\\s*,\\s*${NUM}){2}|${PCT}(?:\\s*,\\s*${PCT}){2})${_ALPHA_LV3}`;
const SYN_COLOR_TYPE = `${_COLOR_KEY}|hsla?\\(\\s*${SYN_HSL_LV3}\\s*\\)|rgba?\\(\\s*${SYN_RGB_LV3}\\s*\\)|(?:hsla?|hwb)\\(\\s*${SYN_HSL}\\s*\\)|(?:(?:ok)?lab|rgba?)\\(\\s*${SYN_MOD}\\s*\\)|(?:ok)?lch\\(\\s*${SYN_LCH}\\s*\\)|color\\(\\s*${SYN_FN_COLOR}\\s*\\)`;
const SYN_MIX_PART = `(?:${SYN_COLOR_TYPE})(?:\\s+${PCT})?`;
const SYN_MIX = `color-mix\\(\\s*in\\s+(?:${CS_MIX})\\s*,\\s*${SYN_MIX_PART}\\s*,\\s*${SYN_MIX_PART}\\s*\\)`;
const SYN_MIX_CAPT = `color-mix\\(\\s*in\\s+(${CS_MIX})\\s*,\\s*(${SYN_MIX_PART})\\s*,\\s*(${SYN_MIX_PART})\\s*\\)`;
const VAL_COMP = "computedValue";
const VAL_MIX = "mixValue";
const VAL_SPEC = "specifiedValue";
export {
ANGLE,
CS_HUE,
CS_HUE_CAPT,
CS_LAB,
CS_LCH,
CS_MIX,
CS_RECT,
CS_RGB,
CS_SRGB,
CS_XYZ,
FN_COLOR,
FN_LIGHT_DARK,
FN_MIX,
FN_REL,
FN_REL_CAPT,
FN_VAR,
LENGTH,
NONE,
NUM,
NUM_POSITIVE,
PCT,
SYN_COLOR_TYPE,
SYN_FN_CALC,
SYN_FN_COLOR,
SYN_FN_LIGHT_DARK,
SYN_FN_MATH_START,
SYN_FN_REL,
SYN_FN_VAR,
SYN_FN_VAR_START,
SYN_HSL,
SYN_HSL_LV3,
SYN_LCH,
SYN_MIX,
SYN_MIX_CAPT,
SYN_MIX_PART,
SYN_MOD,
SYN_RGB_LV3,
VAL_COMP,
VAL_MIX,
VAL_SPEC
};
//# sourceMappingURL=constant.js.map
File diff suppressed because one or more lines are too long
+99
View File
@@ -0,0 +1,99 @@
import { NullObject } from './cache.js';
import { ColorChannels, Options } from './typedef.js';
/**
* pre process
* @param value - CSS color value
* @param [opt] - options
* @returns value
*/
export declare const preProcess: (value: string, opt?: Options) => string | NullObject;
/**
* convert number to hex string
* @param value - numeric value
* @returns hex string: 00..ff
*/
export declare const numberToHex: (value: number) => string;
/**
* convert color to hex
* @param value - CSS color value
* @param [opt] - options
* @param [opt.alpha] - enable alpha channel
* @returns #rrggbb | #rrggbbaa | null
*/
export declare const colorToHex: (value: string, opt?: Options) => string | null;
/**
* convert color to hsl
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels - [h, s, l, alpha]
*/
export declare const colorToHsl: (value: string, opt?: Options) => ColorChannels;
/**
* convert color to hwb
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels - [h, w, b, alpha]
*/
export declare const colorToHwb: (value: string, opt?: Options) => ColorChannels;
/**
* convert color to lab
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels - [l, a, b, alpha]
*/
export declare const colorToLab: (value: string, opt?: Options) => ColorChannels;
/**
* convert color to lch
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels - [l, c, h, alpha]
*/
export declare const colorToLch: (value: string, opt?: Options) => ColorChannels;
/**
* convert color to oklab
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels - [l, a, b, alpha]
*/
export declare const colorToOklab: (value: string, opt?: Options) => ColorChannels;
/**
* convert color to oklch
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels - [l, c, h, alpha]
*/
export declare const colorToOklch: (value: string, opt?: Options) => ColorChannels;
/**
* convert color to rgb
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels - [r, g, b, alpha]
*/
export declare const colorToRgb: (value: string, opt?: Options) => ColorChannels;
/**
* convert color to xyz
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels - [x, y, z, alpha]
*/
export declare const colorToXyz: (value: string, opt?: Options) => ColorChannels;
/**
* convert color to xyz-d50
* @param value - CSS color value
* @param [opt] - options
* @returns ColorChannels - [x, y, z, alpha]
*/
export declare const colorToXyzD50: (value: string, opt?: Options) => ColorChannels;
export declare const convert: {
colorToHex: (value: string, opt?: Options) => string | null;
colorToHsl: (value: string, opt?: Options) => ColorChannels;
colorToHwb: (value: string, opt?: Options) => ColorChannels;
colorToLab: (value: string, opt?: Options) => ColorChannels;
colorToLch: (value: string, opt?: Options) => ColorChannels;
colorToOklab: (value: string, opt?: Options) => ColorChannels;
colorToOklch: (value: string, opt?: Options) => ColorChannels;
colorToRgb: (value: string, opt?: Options) => ColorChannels;
colorToXyz: (value: string, opt?: Options) => ColorChannels;
colorToXyzD50: (value: string, opt?: Options) => ColorChannels;
numberToHex: (value: number) => string;
};
+361
View File
@@ -0,0 +1,361 @@
import { NullObject, createCacheKey, getCache, CacheItem, setCache } from "./cache.js";
import { numberToHexString, parseColorFunc, parseColorValue, convertColorToRgb, convertColorToOklch, convertColorToOklab, convertColorToLch, convertColorToLab, convertColorToHwb, convertColorToHsl } from "./color.js";
import { isString } from "./common.js";
import { cssCalc } from "./css-calc.js";
import { resolveVar } from "./css-var.js";
import { resolveRelativeColor } from "./relative-color.js";
import { resolveColor } from "./resolve.js";
import { VAL_COMP, SYN_FN_VAR, SYN_FN_REL, SYN_FN_CALC } from "./constant.js";
const NAMESPACE = "convert";
const REG_FN_CALC = new RegExp(SYN_FN_CALC);
const REG_FN_REL = new RegExp(SYN_FN_REL);
const REG_FN_VAR = new RegExp(SYN_FN_VAR);
const preProcess = (value, opt = {}) => {
if (isString(value)) {
value = value.trim();
if (!value) {
return new NullObject();
}
} else {
return new NullObject();
}
const cacheKey = createCacheKey(
{
namespace: NAMESPACE,
name: "preProcess",
value
},
opt
);
const cachedResult = getCache(cacheKey);
if (cachedResult instanceof CacheItem) {
if (cachedResult.isNull) {
return cachedResult;
}
return cachedResult.item;
}
if (REG_FN_VAR.test(value)) {
const resolvedValue = resolveVar(value, opt);
if (isString(resolvedValue)) {
value = resolvedValue;
} else {
setCache(cacheKey, null);
return new NullObject();
}
}
if (REG_FN_REL.test(value)) {
const resolvedValue = resolveRelativeColor(value, opt);
if (isString(resolvedValue)) {
value = resolvedValue;
} else {
setCache(cacheKey, null);
return new NullObject();
}
} else if (REG_FN_CALC.test(value)) {
value = cssCalc(value, opt);
}
if (value.startsWith("color-mix")) {
const clonedOpt = structuredClone(opt);
clonedOpt.format = VAL_COMP;
clonedOpt.nullable = true;
const resolvedValue = resolveColor(value, clonedOpt);
setCache(cacheKey, resolvedValue);
return resolvedValue;
}
setCache(cacheKey, value);
return value;
};
const numberToHex = (value) => {
const hex = numberToHexString(value);
return hex;
};
const colorToHex = (value, opt = {}) => {
if (isString(value)) {
const resolvedValue = preProcess(value, opt);
if (resolvedValue instanceof NullObject) {
return null;
}
value = resolvedValue.toLowerCase();
} else {
throw new TypeError(`${value} is not a string.`);
}
const { alpha = false } = opt;
const cacheKey = createCacheKey(
{
namespace: NAMESPACE,
name: "colorToHex",
value
},
opt
);
const cachedResult = getCache(cacheKey);
if (cachedResult instanceof CacheItem) {
if (cachedResult.isNull) {
return null;
}
return cachedResult.item;
}
let hex;
opt.nullable = true;
if (alpha) {
opt.format = "hexAlpha";
hex = resolveColor(value, opt);
} else {
opt.format = "hex";
hex = resolveColor(value, opt);
}
if (isString(hex)) {
setCache(cacheKey, hex);
return hex;
}
setCache(cacheKey, null);
return null;
};
const colorToHsl = (value, opt = {}) => {
if (isString(value)) {
const resolvedValue = preProcess(value, opt);
if (resolvedValue instanceof NullObject) {
return [0, 0, 0, 0];
}
value = resolvedValue.toLowerCase();
} else {
throw new TypeError(`${value} is not a string.`);
}
const cacheKey = createCacheKey(
{
namespace: NAMESPACE,
name: "colorToHsl",
value
},
opt
);
const cachedResult = getCache(cacheKey);
if (cachedResult instanceof CacheItem) {
return cachedResult.item;
}
opt.format = "hsl";
const hsl = convertColorToHsl(value, opt);
setCache(cacheKey, hsl);
return hsl;
};
const colorToHwb = (value, opt = {}) => {
if (isString(value)) {
const resolvedValue = preProcess(value, opt);
if (resolvedValue instanceof NullObject) {
return [0, 0, 0, 0];
}
value = resolvedValue.toLowerCase();
} else {
throw new TypeError(`${value} is not a string.`);
}
const cacheKey = createCacheKey(
{
namespace: NAMESPACE,
name: "colorToHwb",
value
},
opt
);
const cachedResult = getCache(cacheKey);
if (cachedResult instanceof CacheItem) {
return cachedResult.item;
}
opt.format = "hwb";
const hwb = convertColorToHwb(value, opt);
setCache(cacheKey, hwb);
return hwb;
};
const colorToLab = (value, opt = {}) => {
if (isString(value)) {
const resolvedValue = preProcess(value, opt);
if (resolvedValue instanceof NullObject) {
return [0, 0, 0, 0];
}
value = resolvedValue.toLowerCase();
} else {
throw new TypeError(`${value} is not a string.`);
}
const cacheKey = createCacheKey(
{
namespace: NAMESPACE,
name: "colorToLab",
value
},
opt
);
const cachedResult = getCache(cacheKey);
if (cachedResult instanceof CacheItem) {
return cachedResult.item;
}
const lab = convertColorToLab(value, opt);
setCache(cacheKey, lab);
return lab;
};
const colorToLch = (value, opt = {}) => {
if (isString(value)) {
const resolvedValue = preProcess(value, opt);
if (resolvedValue instanceof NullObject) {
return [0, 0, 0, 0];
}
value = resolvedValue.toLowerCase();
} else {
throw new TypeError(`${value} is not a string.`);
}
const cacheKey = createCacheKey(
{
namespace: NAMESPACE,
name: "colorToLch",
value
},
opt
);
const cachedResult = getCache(cacheKey);
if (cachedResult instanceof CacheItem) {
return cachedResult.item;
}
const lch = convertColorToLch(value, opt);
setCache(cacheKey, lch);
return lch;
};
const colorToOklab = (value, opt = {}) => {
if (isString(value)) {
const resolvedValue = preProcess(value, opt);
if (resolvedValue instanceof NullObject) {
return [0, 0, 0, 0];
}
value = resolvedValue.toLowerCase();
} else {
throw new TypeError(`${value} is not a string.`);
}
const cacheKey = createCacheKey(
{
namespace: NAMESPACE,
name: "colorToOklab",
value
},
opt
);
const cachedResult = getCache(cacheKey);
if (cachedResult instanceof CacheItem) {
return cachedResult.item;
}
const lab = convertColorToOklab(value, opt);
setCache(cacheKey, lab);
return lab;
};
const colorToOklch = (value, opt = {}) => {
if (isString(value)) {
const resolvedValue = preProcess(value, opt);
if (resolvedValue instanceof NullObject) {
return [0, 0, 0, 0];
}
value = resolvedValue.toLowerCase();
} else {
throw new TypeError(`${value} is not a string.`);
}
const cacheKey = createCacheKey(
{
namespace: NAMESPACE,
name: "colorToOklch",
value
},
opt
);
const cachedResult = getCache(cacheKey);
if (cachedResult instanceof CacheItem) {
return cachedResult.item;
}
const lch = convertColorToOklch(value, opt);
setCache(cacheKey, lch);
return lch;
};
const colorToRgb = (value, opt = {}) => {
if (isString(value)) {
const resolvedValue = preProcess(value, opt);
if (resolvedValue instanceof NullObject) {
return [0, 0, 0, 0];
}
value = resolvedValue.toLowerCase();
} else {
throw new TypeError(`${value} is not a string.`);
}
const cacheKey = createCacheKey(
{
namespace: NAMESPACE,
name: "colorToRgb",
value
},
opt
);
const cachedResult = getCache(cacheKey);
if (cachedResult instanceof CacheItem) {
return cachedResult.item;
}
const rgb = convertColorToRgb(value, opt);
setCache(cacheKey, rgb);
return rgb;
};
const colorToXyz = (value, opt = {}) => {
if (isString(value)) {
const resolvedValue = preProcess(value, opt);
if (resolvedValue instanceof NullObject) {
return [0, 0, 0, 0];
}
value = resolvedValue.toLowerCase();
} else {
throw new TypeError(`${value} is not a string.`);
}
const cacheKey = createCacheKey(
{
namespace: NAMESPACE,
name: "colorToXyz",
value
},
opt
);
const cachedResult = getCache(cacheKey);
if (cachedResult instanceof CacheItem) {
return cachedResult.item;
}
let xyz;
if (value.startsWith("color(")) {
[, ...xyz] = parseColorFunc(value, opt);
} else {
[, ...xyz] = parseColorValue(value, opt);
}
setCache(cacheKey, xyz);
return xyz;
};
const colorToXyzD50 = (value, opt = {}) => {
opt.d50 = true;
return colorToXyz(value, opt);
};
const convert = {
colorToHex,
colorToHsl,
colorToHwb,
colorToLab,
colorToLch,
colorToOklab,
colorToOklch,
colorToRgb,
colorToXyz,
colorToXyzD50,
numberToHex
};
export {
colorToHex,
colorToHsl,
colorToHwb,
colorToLab,
colorToLch,
colorToOklab,
colorToOklch,
colorToRgb,
colorToXyz,
colorToXyzD50,
convert,
numberToHex,
preProcess
};
//# sourceMappingURL=convert.js.map
File diff suppressed because one or more lines are too long
+89
View File
@@ -0,0 +1,89 @@
import { CSSToken } from '@csstools/css-tokenizer';
import { NullObject } from './cache.js';
import { Options } from './typedef.js';
/**
* Calclator
*/
export declare class Calculator {
#private;
/**
* constructor
*/
constructor();
get hasNum(): boolean;
set hasNum(value: boolean);
get numSum(): number[];
get numMul(): number[];
get hasPct(): boolean;
set hasPct(value: boolean);
get pctSum(): number[];
get pctMul(): number[];
get hasDim(): boolean;
set hasDim(value: boolean);
get dimSum(): string[];
get dimSub(): string[];
get dimMul(): string[];
get dimDiv(): string[];
get hasEtc(): boolean;
set hasEtc(value: boolean);
get etcSum(): string[];
get etcSub(): string[];
get etcMul(): string[];
get etcDiv(): string[];
/**
* clear values
* @returns void
*/
clear(): void;
/**
* sort values
* @param values - values
* @returns sorted values
*/
sort(values?: string[]): string[];
/**
* multiply values
* @returns resolved value
*/
multiply(): string;
/**
* sum values
* @returns resolved value
*/
sum(): string;
}
/**
* sort calc values
* @param values - values to sort
* @param [finalize] - finalize values
* @returns sorted values
*/
export declare const sortCalcValues: (values?: (number | string)[], finalize?: boolean) => string;
/**
* serialize calc
* @param value - CSS value
* @param [opt] - options
* @returns serialized value
*/
export declare const serializeCalc: (value: string, opt?: Options) => string;
/**
* resolve dimension
* @param token - CSS token
* @param [opt] - options
* @returns resolved value
*/
export declare const resolveDimension: (token: CSSToken, opt?: Options) => string | NullObject;
/**
* parse tokens
* @param tokens - CSS tokens
* @param [opt] - options
* @returns parsed tokens
*/
export declare const parseTokens: (tokens: CSSToken[], opt?: Options) => string[];
/**
* CSS calc()
* @param value - CSS value including calc()
* @param [opt] - options
* @returns resolved value
*/
export declare const cssCalc: (value: string, opt?: Options) => string;
+826
View File
@@ -0,0 +1,826 @@
import { calc } from "@csstools/css-calc";
import { TokenType, tokenize } from "@csstools/css-tokenizer";
import { createCacheKey, getCache, CacheItem, setCache, NullObject } from "./cache.js";
import { isString, isStringOrNumber } from "./common.js";
import { resolveVar } from "./css-var.js";
import { roundToPrecision, resolveLengthInPixels } from "./util.js";
import { VAL_SPEC, SYN_FN_VAR, SYN_FN_CALC, SYN_FN_VAR_START, NUM, ANGLE, LENGTH, SYN_FN_MATH_START } from "./constant.js";
const {
CloseParen: PAREN_CLOSE,
Comment: COMMENT,
Dimension: DIM,
EOF,
Function: FUNC,
OpenParen: PAREN_OPEN,
Whitespace: W_SPACE
} = TokenType;
const NAMESPACE = "css-calc";
const TRIA = 3;
const HEX = 16;
const MAX_PCT = 100;
const REG_FN_CALC = new RegExp(SYN_FN_CALC);
const REG_FN_CALC_NUM = new RegExp(`^calc\\((${NUM})\\)$`);
const REG_FN_MATH_START = new RegExp(SYN_FN_MATH_START);
const REG_FN_VAR = new RegExp(SYN_FN_VAR);
const REG_FN_VAR_START = new RegExp(SYN_FN_VAR_START);
const REG_OPERATOR = /\s[*+/-]\s/;
const REG_TYPE_DIM = new RegExp(`^(${NUM})(${ANGLE}|${LENGTH})$`);
const REG_TYPE_DIM_PCT = new RegExp(`^(${NUM})(${ANGLE}|${LENGTH}|%)$`);
const REG_TYPE_PCT = new RegExp(`^(${NUM})%$`);
class Calculator {
/* private */
// number
#hasNum;
#numSum;
#numMul;
// percentage
#hasPct;
#pctSum;
#pctMul;
// dimension
#hasDim;
#dimSum;
#dimSub;
#dimMul;
#dimDiv;
// et cetra
#hasEtc;
#etcSum;
#etcSub;
#etcMul;
#etcDiv;
/**
* constructor
*/
constructor() {
this.#hasNum = false;
this.#numSum = [];
this.#numMul = [];
this.#hasPct = false;
this.#pctSum = [];
this.#pctMul = [];
this.#hasDim = false;
this.#dimSum = [];
this.#dimSub = [];
this.#dimMul = [];
this.#dimDiv = [];
this.#hasEtc = false;
this.#etcSum = [];
this.#etcSub = [];
this.#etcMul = [];
this.#etcDiv = [];
}
get hasNum() {
return this.#hasNum;
}
set hasNum(value) {
this.#hasNum = !!value;
}
get numSum() {
return this.#numSum;
}
get numMul() {
return this.#numMul;
}
get hasPct() {
return this.#hasPct;
}
set hasPct(value) {
this.#hasPct = !!value;
}
get pctSum() {
return this.#pctSum;
}
get pctMul() {
return this.#pctMul;
}
get hasDim() {
return this.#hasDim;
}
set hasDim(value) {
this.#hasDim = !!value;
}
get dimSum() {
return this.#dimSum;
}
get dimSub() {
return this.#dimSub;
}
get dimMul() {
return this.#dimMul;
}
get dimDiv() {
return this.#dimDiv;
}
get hasEtc() {
return this.#hasEtc;
}
set hasEtc(value) {
this.#hasEtc = !!value;
}
get etcSum() {
return this.#etcSum;
}
get etcSub() {
return this.#etcSub;
}
get etcMul() {
return this.#etcMul;
}
get etcDiv() {
return this.#etcDiv;
}
/**
* clear values
* @returns void
*/
clear() {
this.#hasNum = false;
this.#numSum = [];
this.#numMul = [];
this.#hasPct = false;
this.#pctSum = [];
this.#pctMul = [];
this.#hasDim = false;
this.#dimSum = [];
this.#dimSub = [];
this.#dimMul = [];
this.#dimDiv = [];
this.#hasEtc = false;
this.#etcSum = [];
this.#etcSub = [];
this.#etcMul = [];
this.#etcDiv = [];
}
/**
* sort values
* @param values - values
* @returns sorted values
*/
sort(values = []) {
const arr = [...values];
if (arr.length > 1) {
arr.sort((a, b) => {
let res;
if (REG_TYPE_DIM_PCT.test(a) && REG_TYPE_DIM_PCT.test(b)) {
const [, valA, unitA] = a.match(REG_TYPE_DIM_PCT);
const [, valB, unitB] = b.match(REG_TYPE_DIM_PCT);
if (unitA === unitB) {
if (Number(valA) === Number(valB)) {
res = 0;
} else if (Number(valA) > Number(valB)) {
res = 1;
} else {
res = -1;
}
} else if (unitA > unitB) {
res = 1;
} else {
res = -1;
}
} else {
if (a === b) {
res = 0;
} else if (a > b) {
res = 1;
} else {
res = -1;
}
}
return res;
});
}
return arr;
}
/**
* multiply values
* @returns resolved value
*/
multiply() {
const value = [];
let num;
if (this.#hasNum) {
num = 1;
for (const i of this.#numMul) {
num *= i;
if (num === 0 || !Number.isFinite(num) || Number.isNaN(num)) {
break;
}
}
if (!this.#hasPct && !this.#hasDim && !this.hasEtc) {
if (Number.isFinite(num)) {
num = roundToPrecision(num, HEX);
}
value.push(num);
}
}
if (this.#hasPct) {
if (typeof num !== "number") {
num = 1;
}
for (const i of this.#pctMul) {
num *= i;
if (num === 0 || !Number.isFinite(num) || Number.isNaN(num)) {
break;
}
}
if (Number.isFinite(num)) {
num = `${roundToPrecision(num, HEX)}%`;
}
if (!this.#hasDim && !this.hasEtc) {
value.push(num);
}
}
if (this.#hasDim) {
let dim = "";
let mul = "";
let div = "";
if (this.#dimMul.length) {
if (this.#dimMul.length === 1) {
[mul] = this.#dimMul;
} else {
mul = `${this.sort(this.#dimMul).join(" * ")}`;
}
}
if (this.#dimDiv.length) {
if (this.#dimDiv.length === 1) {
[div] = this.#dimDiv;
} else {
div = `${this.sort(this.#dimDiv).join(" * ")}`;
}
}
if (Number.isFinite(num)) {
if (mul) {
if (div) {
if (div.includes("*")) {
dim = calc(`calc(${num} * ${mul} / (${div}))`, {
toCanonicalUnits: true
});
} else {
dim = calc(`calc(${num} * ${mul} / ${div})`, {
toCanonicalUnits: true
});
}
} else {
dim = calc(`calc(${num} * ${mul})`, {
toCanonicalUnits: true
});
}
} else if (div.includes("*")) {
dim = calc(`calc(${num} / (${div}))`, {
toCanonicalUnits: true
});
} else {
dim = calc(`calc(${num} / ${div})`, {
toCanonicalUnits: true
});
}
value.push(dim.replace(/^calc/, ""));
} else {
if (!value.length && num !== void 0) {
value.push(num);
}
if (mul) {
if (div) {
if (div.includes("*")) {
dim = calc(`calc(${mul} / (${div}))`, {
toCanonicalUnits: true
});
} else {
dim = calc(`calc(${mul} / ${div})`, {
toCanonicalUnits: true
});
}
} else {
dim = calc(`calc(${mul})`, {
toCanonicalUnits: true
});
}
if (value.length) {
value.push("*", dim.replace(/^calc/, ""));
} else {
value.push(dim.replace(/^calc/, ""));
}
} else {
dim = calc(`calc(${div})`, {
toCanonicalUnits: true
});
if (value.length) {
value.push("/", dim.replace(/^calc/, ""));
} else {
value.push("1", "/", dim.replace(/^calc/, ""));
}
}
}
}
if (this.#hasEtc) {
if (this.#etcMul.length) {
if (!value.length && num !== void 0) {
value.push(num);
}
const mul = this.sort(this.#etcMul).join(" * ");
if (value.length) {
value.push(`* ${mul}`);
} else {
value.push(`${mul}`);
}
}
if (this.#etcDiv.length) {
const div = this.sort(this.#etcDiv).join(" * ");
if (div.includes("*")) {
if (value.length) {
value.push(`/ (${div})`);
} else {
value.push(`1 / (${div})`);
}
} else if (value.length) {
value.push(`/ ${div}`);
} else {
value.push(`1 / ${div}`);
}
}
}
if (value.length) {
return value.join(" ");
}
return "";
}
/**
* sum values
* @returns resolved value
*/
sum() {
const value = [];
if (this.#hasNum) {
let num = 0;
for (const i of this.#numSum) {
num += i;
if (!Number.isFinite(num) || Number.isNaN(num)) {
break;
}
}
value.push(num);
}
if (this.#hasPct) {
let num = 0;
for (const i of this.#pctSum) {
num += i;
if (!Number.isFinite(num)) {
break;
}
}
if (Number.isFinite(num)) {
num = `${num}%`;
}
if (value.length) {
value.push(`+ ${num}`);
} else {
value.push(num);
}
}
if (this.#hasDim) {
let dim, sum, sub;
if (this.#dimSum.length) {
sum = this.sort(this.#dimSum).join(" + ");
}
if (this.#dimSub.length) {
sub = this.sort(this.#dimSub).join(" + ");
}
if (sum) {
if (sub) {
if (sub.includes("-")) {
dim = calc(`calc(${sum} - (${sub}))`, {
toCanonicalUnits: true
});
} else {
dim = calc(`calc(${sum} - ${sub})`, {
toCanonicalUnits: true
});
}
} else {
dim = calc(`calc(${sum})`, {
toCanonicalUnits: true
});
}
} else {
dim = calc(`calc(-1 * (${sub}))`, {
toCanonicalUnits: true
});
}
if (value.length) {
value.push("+", dim.replace(/^calc/, ""));
} else {
value.push(dim.replace(/^calc/, ""));
}
}
if (this.#hasEtc) {
if (this.#etcSum.length) {
const sum = this.sort(this.#etcSum).map((item) => {
let res;
if (REG_OPERATOR.test(item) && !item.startsWith("(") && !item.endsWith(")")) {
res = `(${item})`;
} else {
res = item;
}
return res;
}).join(" + ");
if (value.length) {
if (this.#etcSum.length > 1) {
value.push(`+ (${sum})`);
} else {
value.push(`+ ${sum}`);
}
} else {
value.push(`${sum}`);
}
}
if (this.#etcSub.length) {
const sub = this.sort(this.#etcSub).map((item) => {
let res;
if (REG_OPERATOR.test(item) && !item.startsWith("(") && !item.endsWith(")")) {
res = `(${item})`;
} else {
res = item;
}
return res;
}).join(" + ");
if (value.length) {
if (this.#etcSub.length > 1) {
value.push(`- (${sub})`);
} else {
value.push(`- ${sub}`);
}
} else if (this.#etcSub.length > 1) {
value.push(`-1 * (${sub})`);
} else {
value.push(`-1 * ${sub}`);
}
}
}
if (value.length) {
return value.join(" ");
}
return "";
}
}
const sortCalcValues = (values = [], finalize = false) => {
if (values.length < TRIA) {
throw new Error(`Unexpected array length ${values.length}.`);
}
const start = values.shift();
if (!isString(start) || !start.endsWith("(")) {
throw new Error(`Unexpected token ${start}.`);
}
const end = values.pop();
if (end !== ")") {
throw new Error(`Unexpected token ${end}.`);
}
if (values.length === 1) {
const [value] = values;
if (!isStringOrNumber(value)) {
throw new Error(`Unexpected token ${value}.`);
}
return `${start}${value}${end}`;
}
const sortedValues = [];
const cal = new Calculator();
let operator = "";
const l = values.length;
for (let i = 0; i < l; i++) {
const value = values[i];
if (!isStringOrNumber(value)) {
throw new Error(`Unexpected token ${value}.`);
}
if (value === "*" || value === "/") {
operator = value;
} else if (value === "+" || value === "-") {
const sortedValue = cal.multiply();
if (sortedValue) {
sortedValues.push(sortedValue, value);
}
cal.clear();
operator = "";
} else {
const numValue = Number(value);
const strValue = `${value}`;
switch (operator) {
case "/": {
if (Number.isFinite(numValue)) {
cal.hasNum = true;
cal.numMul.push(1 / numValue);
} else if (REG_TYPE_PCT.test(strValue)) {
const [, val] = strValue.match(REG_TYPE_PCT);
cal.hasPct = true;
cal.pctMul.push(MAX_PCT * MAX_PCT / Number(val));
} else if (REG_TYPE_DIM.test(strValue)) {
cal.hasDim = true;
cal.dimDiv.push(strValue);
} else {
cal.hasEtc = true;
cal.etcDiv.push(strValue);
}
break;
}
case "*":
default: {
if (Number.isFinite(numValue)) {
cal.hasNum = true;
cal.numMul.push(numValue);
} else if (REG_TYPE_PCT.test(strValue)) {
const [, val] = strValue.match(REG_TYPE_PCT);
cal.hasPct = true;
cal.pctMul.push(Number(val));
} else if (REG_TYPE_DIM.test(strValue)) {
cal.hasDim = true;
cal.dimMul.push(strValue);
} else {
cal.hasEtc = true;
cal.etcMul.push(strValue);
}
}
}
}
if (i === l - 1) {
const sortedValue = cal.multiply();
if (sortedValue) {
sortedValues.push(sortedValue);
}
cal.clear();
operator = "";
}
}
let resolvedValue = "";
if (finalize && (sortedValues.includes("+") || sortedValues.includes("-"))) {
const finalizedValues = [];
cal.clear();
operator = "";
const l2 = sortedValues.length;
for (let i = 0; i < l2; i++) {
const value = sortedValues[i];
if (isStringOrNumber(value)) {
if (value === "+" || value === "-") {
operator = value;
} else {
const numValue = Number(value);
const strValue = `${value}`;
switch (operator) {
case "-": {
if (Number.isFinite(numValue)) {
cal.hasNum = true;
cal.numSum.push(-1 * numValue);
} else if (REG_TYPE_PCT.test(strValue)) {
const [, val] = strValue.match(REG_TYPE_PCT);
cal.hasPct = true;
cal.pctSum.push(-1 * Number(val));
} else if (REG_TYPE_DIM.test(strValue)) {
cal.hasDim = true;
cal.dimSub.push(strValue);
} else {
cal.hasEtc = true;
cal.etcSub.push(strValue);
}
break;
}
case "+":
default: {
if (Number.isFinite(numValue)) {
cal.hasNum = true;
cal.numSum.push(numValue);
} else if (REG_TYPE_PCT.test(strValue)) {
const [, val] = strValue.match(REG_TYPE_PCT);
cal.hasPct = true;
cal.pctSum.push(Number(val));
} else if (REG_TYPE_DIM.test(strValue)) {
cal.hasDim = true;
cal.dimSum.push(strValue);
} else {
cal.hasEtc = true;
cal.etcSum.push(strValue);
}
}
}
}
}
if (i === l2 - 1) {
const sortedValue = cal.sum();
if (sortedValue) {
finalizedValues.push(sortedValue);
}
cal.clear();
operator = "";
}
}
resolvedValue = finalizedValues.join(" ").replace(/\+\s-/g, "- ");
} else {
resolvedValue = sortedValues.join(" ").replace(/\+\s-/g, "- ");
}
if (resolvedValue.startsWith("(") && resolvedValue.endsWith(")") && resolvedValue.lastIndexOf("(") === 0 && resolvedValue.indexOf(")") === resolvedValue.length - 1) {
resolvedValue = resolvedValue.replace(/^\(/, "").replace(/\)$/, "");
}
return `${start}${resolvedValue}${end}`;
};
const serializeCalc = (value, opt = {}) => {
const { format = "" } = opt;
if (isString(value)) {
if (!REG_FN_VAR_START.test(value) || format !== VAL_SPEC) {
return value;
}
value = value.toLowerCase().trim();
} else {
throw new TypeError(`${value} is not a string.`);
}
const cacheKey = createCacheKey(
{
namespace: NAMESPACE,
name: "serializeCalc",
value
},
opt
);
const cachedResult = getCache(cacheKey);
if (cachedResult instanceof CacheItem) {
return cachedResult.item;
}
const items = tokenize({ css: value }).map((token) => {
const [type, value2] = token;
let res = "";
if (type !== W_SPACE && type !== COMMENT) {
res = value2;
}
return res;
}).filter((v) => v);
let startIndex = items.findLastIndex((item) => /\($/.test(item));
while (startIndex) {
const endIndex = items.findIndex((item, index) => {
return item === ")" && index > startIndex;
});
const slicedValues = items.slice(startIndex, endIndex + 1);
let serializedValue = sortCalcValues(slicedValues);
if (REG_FN_VAR_START.test(serializedValue)) {
serializedValue = calc(serializedValue, {
toCanonicalUnits: true
});
}
items.splice(startIndex, endIndex - startIndex + 1, serializedValue);
startIndex = items.findLastIndex((item) => /\($/.test(item));
}
const serializedCalc = sortCalcValues(items, true);
setCache(cacheKey, serializedCalc);
return serializedCalc;
};
const resolveDimension = (token, opt = {}) => {
if (!Array.isArray(token)) {
throw new TypeError(`${token} is not an array.`);
}
const [, , , , detail = {}] = token;
const { unit, value } = detail;
if (unit === "px") {
return `${value}${unit}`;
}
const pixelValue = resolveLengthInPixels(Number(value), unit, opt);
if (Number.isFinite(pixelValue)) {
return `${roundToPrecision(pixelValue, HEX)}px`;
}
return new NullObject();
};
const parseTokens = (tokens, opt = {}) => {
if (!Array.isArray(tokens)) {
throw new TypeError(`${tokens} is not an array.`);
}
const { format = "" } = opt;
const mathFunc = /* @__PURE__ */ new Set();
let nest = 0;
const res = [];
while (tokens.length) {
const token = tokens.shift();
if (!Array.isArray(token)) {
throw new TypeError(`${token} is not an array.`);
}
const [type = "", value = ""] = token;
switch (type) {
case DIM: {
if (format === VAL_SPEC && !mathFunc.has(nest)) {
res.push(value);
} else {
const resolvedValue = resolveDimension(token, opt);
if (isString(resolvedValue)) {
res.push(resolvedValue);
} else {
res.push(value);
}
}
break;
}
case FUNC:
case PAREN_OPEN: {
res.push(value);
nest++;
if (REG_FN_MATH_START.test(value)) {
mathFunc.add(nest);
}
break;
}
case PAREN_CLOSE: {
if (res.length) {
const lastValue = res[res.length - 1];
if (lastValue === " ") {
res.splice(-1, 1, value);
} else {
res.push(value);
}
} else {
res.push(value);
}
if (mathFunc.has(nest)) {
mathFunc.delete(nest);
}
nest--;
break;
}
case W_SPACE: {
if (res.length) {
const lastValue = res[res.length - 1];
if (isString(lastValue) && !lastValue.endsWith("(") && lastValue !== " ") {
res.push(value);
}
}
break;
}
default: {
if (type !== COMMENT && type !== EOF) {
res.push(value);
}
}
}
}
return res;
};
const cssCalc = (value, opt = {}) => {
const { format = "" } = opt;
if (isString(value)) {
if (REG_FN_VAR.test(value)) {
if (format === VAL_SPEC) {
return value;
} else {
const resolvedValue2 = resolveVar(value, opt);
if (isString(resolvedValue2)) {
return resolvedValue2;
} else {
return "";
}
}
} else if (!REG_FN_CALC.test(value)) {
return value;
}
value = value.toLowerCase().trim();
} else {
throw new TypeError(`${value} is not a string.`);
}
const cacheKey = createCacheKey(
{
namespace: NAMESPACE,
name: "cssCalc",
value
},
opt
);
const cachedResult = getCache(cacheKey);
if (cachedResult instanceof CacheItem) {
return cachedResult.item;
}
const tokens = tokenize({ css: value });
const values = parseTokens(tokens, opt);
let resolvedValue = calc(values.join(""), {
toCanonicalUnits: true
});
if (REG_FN_VAR_START.test(value)) {
if (REG_TYPE_DIM_PCT.test(resolvedValue)) {
const [, val, unit] = resolvedValue.match(
REG_TYPE_DIM_PCT
);
resolvedValue = `${roundToPrecision(Number(val), HEX)}${unit}`;
}
if (resolvedValue && !REG_FN_VAR_START.test(resolvedValue) && format === VAL_SPEC) {
resolvedValue = `calc(${resolvedValue})`;
}
}
if (format === VAL_SPEC) {
if (/\s[-+*/]\s/.test(resolvedValue) && !resolvedValue.includes("NaN")) {
resolvedValue = serializeCalc(resolvedValue, opt);
} else if (REG_FN_CALC_NUM.test(resolvedValue)) {
const [, val] = resolvedValue.match(REG_FN_CALC_NUM);
resolvedValue = `calc(${roundToPrecision(Number(val), HEX)})`;
}
}
setCache(cacheKey, resolvedValue);
return resolvedValue;
};
export {
Calculator,
cssCalc,
parseTokens,
resolveDimension,
serializeCalc,
sortCalcValues
};
//# sourceMappingURL=css-calc.js.map
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More