├── .gitignore ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── config.yml └── CONTRIBUTING.md ├── themes ├── Dracula │ ├── main.css │ ├── index.js │ └── tailwind.config.js ├── Github │ ├── main.css │ ├── index.js │ └── tailwind.config.js ├── default │ ├── main.css │ ├── index.js │ └── tailwind.config.js └── Hacktoberfest2020 │ ├── main.css │ ├── index.js │ └── tailwind.config.js ├── .editorconfig ├── LICENSE.md ├── rollup.config.js ├── package.json ├── README.md ├── src ├── DevTools.js ├── Loader.js └── Viewer.js ├── CHANGELOG.md ├── example.html ├── index.html └── dist ├── Dracula.js └── Hacktoberfest2020.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [kevinbatdorf] -------------------------------------------------------------------------------- /themes/Dracula/main.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss/base"; 2 | @import "tailwindcss/components"; 3 | @import "tailwindcss/utilities"; 4 | -------------------------------------------------------------------------------- /themes/Github/main.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss/base"; 2 | @import "tailwindcss/components"; 3 | @import "tailwindcss/utilities"; 4 | -------------------------------------------------------------------------------- /themes/default/main.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss/base"; 2 | @import "tailwindcss/components"; 3 | @import "tailwindcss/utilities"; 4 | -------------------------------------------------------------------------------- /themes/Hacktoberfest2020/main.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss/base"; 2 | @import "tailwindcss/components"; 3 | @import "tailwindcss/utilities"; 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /themes/Dracula/index.js: -------------------------------------------------------------------------------- 1 | import DevTools from '../../src/DevTools' 2 | import Loader from '../../src/Loader.js' 3 | import Viewer from '../../src/Viewer.js' 4 | import theme from './main.css' 5 | 6 | window.alpineDevToolsThemeDracula = theme 7 | const alpine = window.deferLoadingAlpine || ((alpine) => alpine()) 8 | window.deferLoadingAlpine = callback => { 9 | alpine(callback) 10 | DevTools.start(Loader, Viewer, theme) 11 | } 12 | -------------------------------------------------------------------------------- /themes/Github/index.js: -------------------------------------------------------------------------------- 1 | import DevTools from '../../src/DevTools' 2 | import Loader from '../../src/Loader.js' 3 | import Viewer from '../../src/Viewer.js' 4 | import theme from './main.css' 5 | 6 | window.alpineDevToolsThemeGitHub = theme 7 | const alpine = window.deferLoadingAlpine || ((alpine) => alpine()) 8 | window.deferLoadingAlpine = callback => { 9 | alpine(callback) 10 | DevTools.start(Loader, Viewer, theme) 11 | } 12 | -------------------------------------------------------------------------------- /themes/default/index.js: -------------------------------------------------------------------------------- 1 | import DevTools from '../../src/DevTools' 2 | import Loader from '../../src/Loader.js' 3 | import Viewer from '../../src/Viewer.js' 4 | import theme from './main.css' 5 | 6 | window.alpineDevToolsThemeDefault = theme 7 | const alpine = window.deferLoadingAlpine || ((alpine) => alpine()) 8 | window.deferLoadingAlpine = callback => { 9 | alpine(callback) 10 | DevTools.start(Loader, Viewer, theme) 11 | } 12 | -------------------------------------------------------------------------------- /themes/Hacktoberfest2020/index.js: -------------------------------------------------------------------------------- 1 | import DevTools from '../../src/DevTools' 2 | import Loader from '../../src/Loader.js' 3 | import Viewer from '../../src/Viewer.js' 4 | import theme from './main.css' 5 | 6 | window.alpineDevToolsThemeHacktoberfest2020 = theme 7 | const alpine = window.deferLoadingAlpine || ((alpine) => alpine()) 8 | window.deferLoadingAlpine = callback => { 9 | alpine(callback) 10 | DevTools.start(Loader, Viewer, theme) 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Bug Report 4 | url: https://github.com/KevinBatdorf/alpine-inline-devtools/issues/new 5 | about: Submit a GitHub issue 6 | # - name: Feature Request 7 | # url: https://github.com/KevinBatdorf/alpine-inline-devtools/discussions/new 8 | # about: Request a feature to be added to the platform 9 | # - name: Ask a Question 10 | # url: https://github.com/KevinBatdorf/alpine-inline-devtools/discussions/new 11 | # about: Ask questions and discuss with other community members 12 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your interest in contributing to this project! Please take a moment to review this document **before submitting a pull request**. 4 | 5 | ## Pull requests 6 | 7 | **Please ask first before starting work on any significant new features.** 8 | 9 | It's never a fun experience to have your pull request declined after investing a lot of time and effort into a new feature. To avoid this from happening, we request that contributors create [an issue](https://github.com/KevinBatdorf/alpine-inline-devtools/issues) to first discuss any significant new features. This includes bug fixes, features, themes, etc. 10 | 11 | ## Theming 12 | 13 | Currently, adding a theme involves duplicating an existing theme and updating the Tailwind config file inside the theme folder. This will later be expanded to override classnames entirely for more custom theming. 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright © 2020Kevin Batdorf and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel' 2 | import commonjs from '@rollup/plugin-commonjs' 3 | import filesize from 'rollup-plugin-filesize' 4 | import fs from 'fs' 5 | import json from '@rollup/plugin-json' 6 | import postcss from "rollup-plugin-postcss"; 7 | import resolve from '@rollup/plugin-node-resolve' 8 | 9 | const createConfig = (themeName) => ({ 10 | input: `themes/${themeName}/index.js`, 11 | output: [ 12 | { 13 | file: `dist/${themeName}.js`, 14 | format: 'umd', 15 | name: `${themeName}`, 16 | } 17 | ], 18 | external: false, 19 | treeshake: { 20 | propertyReadSideEffects: false, 21 | }, 22 | plugins: [ 23 | babel({ 24 | babelHelpers: 'bundled', 25 | exclude: 'node_modules/**', 26 | }), 27 | resolve({ 28 | mainFields: ['module', 'jsnext', 'main'], 29 | browser: true, 30 | extensions: ['.mjs', '.js', '.jsx', '.json', '.node'], 31 | preferBuiltins: false, 32 | }), 33 | commonjs({ 34 | include: /\/node_modules\//, 35 | }), 36 | postcss({ 37 | extensions: [".css"], 38 | plugins: [ 39 | require("tailwindcss")(`themes/${themeName}/tailwind.config.js`), 40 | require("autoprefixer"), 41 | ] 42 | }), 43 | json(), 44 | filesize(), 45 | ] 46 | }) 47 | 48 | export default process.argv.includes('dev') 49 | ? ['Dracula'].map(createConfig) 50 | : fs.readdirSync('themes').map(createConfig) 51 | -------------------------------------------------------------------------------- /themes/Hacktoberfest2020/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: { 3 | enabled: !process.argv.includes('dev'), 4 | content: [ 5 | 'src/Viewer.js', 6 | 'src/Loader.js', 7 | ], 8 | }, 9 | theme: { 10 | fontFamily: { 11 | mono: ['Fira Code', 'monospace'], 12 | }, 13 | extend: { 14 | colors: { 15 | 'background': '#072540', 16 | 'container-border': 'transparent', 17 | 'component-divider': '#183d5d', 18 | 'component-title': '#ffffff', 19 | 'status-text': '#93c2db', 20 | 'status-text-hover': '#ff8ae2', 21 | 'property-name-color': '#ff8ae2', 22 | 'property-seperator-color': '#0069ff', 23 | 'typeof-color': '#93c2db', 24 | 'typeof-bg': 'transparent', 25 | 'value-color': '#ffffff', 26 | 'string-editor-color': '#ffffff', 27 | 'string-editor-bg': '#183d5d', 28 | 'icon-color': '#93c2db', 29 | }, 30 | } 31 | }, 32 | variants: { 33 | opacity: ['responsive', 'hover', 'focus', 'active', 'group-hover'], 34 | }, 35 | plugins: [], 36 | future: { 37 | removeDeprecatedGapUtilities: true, 38 | }, 39 | experimental: { 40 | extendedSpacingScale: true, 41 | extendedFontSizeScale: true, 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /themes/default/tailwind.config.js: -------------------------------------------------------------------------------- 1 | console.log(process.argv.includes('dev')) 2 | module.exports = { 3 | purge: { 4 | enabled: !process.argv.includes('dev'), 5 | content: [ 6 | 'src/Viewer.js', 7 | 'src/Loader.js', 8 | ], 9 | }, 10 | theme: { 11 | fontFamily: { 12 | mono: ['Fira Code', 'monospace'], 13 | }, 14 | extend: { 15 | colors: { 16 | 'background': '#1a202c', 17 | 'container-border': 'transparent', 18 | 'component-divider': '#2d3748', 19 | 'component-title': '#d8dee9', 20 | 'status-text': '#8ac0cf', 21 | 'status-text-hover': '#4299e1', 22 | 'property-name-color': '#4aea8b', 23 | 'property-seperator-color': '#ffffff', 24 | 'typeof-color': '#8ac0cf', 25 | 'typeof-bg': '#2d3748', 26 | 'value-color': '#d8dee9', 27 | 'string-editor-color': '#000', 28 | 'string-editor-bg': '#edf2f7', 29 | 'icon-color': '#edf2f7', 30 | }, 31 | } 32 | }, 33 | variants: { 34 | opacity: ['responsive', 'hover', 'focus', 'active', 'group-hover'], 35 | }, 36 | plugins: [], 37 | future: { 38 | removeDeprecatedGapUtilities: true, 39 | }, 40 | experimental: { 41 | extendedSpacingScale: true, 42 | extendedFontSizeScale: true, 43 | }, 44 | } 45 | -------------------------------------------------------------------------------- /themes/Dracula/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: { 3 | enabled: !process.argv.includes('dev'), 4 | content: [ 5 | 'src/Viewer.js', 6 | 'src/Loader.js', 7 | ], 8 | }, 9 | theme: { 10 | fontFamily: { 11 | mono: ['Fira Code', 'monospace'], 12 | }, 13 | extend: { 14 | colors: { 15 | // Color palette: https://draculatheme.com/contribute 16 | 'background': '#282a36', 17 | 'container-border': 'transparent', 18 | 'component-divider': '#44475a', 19 | 'component-title': '#ff79c6', 20 | 'status-text': '#6272a4', 21 | 'status-text-hover': '#8be9fd', 22 | 'property-name-color': '#50fa7b', 23 | 'property-seperator-color': '#8be9fd', 24 | 'typeof-color': '#bd93f9', 25 | 'typeof-bg': 'transparent', 26 | 'value-color': '#f8f8f2', 27 | 'string-editor-color': '#f8f8f2', 28 | 'string-editor-bg': '#44475a', 29 | 'icon-color': '#f1fa8c', 30 | }, 31 | } 32 | }, 33 | variants: { 34 | opacity: ['responsive', 'hover', 'focus', 'active', 'group-hover'], 35 | }, 36 | plugins: [], 37 | future: { 38 | removeDeprecatedGapUtilities: true, 39 | }, 40 | experimental: { 41 | extendedSpacingScale: true, 42 | extendedFontSizeScale: true, 43 | }, 44 | } 45 | -------------------------------------------------------------------------------- /themes/Github/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: { 3 | enabled: !process.argv.includes('dev'), 4 | content: [ 5 | 'src/Viewer.js', 6 | 'src/Loader.js', 7 | ], 8 | }, 9 | theme: { 10 | fontFamily: { 11 | mono: ['Fira Code', 'monospace'], 12 | }, 13 | extend: { 14 | colors: { 15 | // Color palette: https://primer.style/css/support/color-system 16 | 'background': '#ffffff', 17 | 'container-border': '#f0f0f0', 18 | 'component-divider': '#d1d5da', 19 | 'component-title': '#24292e', 20 | 'status-text': '#6a737d', 21 | 'status-text-hover': '#ea4aaa', 22 | 'property-name-color': '#0366d6', 23 | 'property-seperator-color': '#0069ff', 24 | 'typeof-color': '#2ea44f', 25 | 'typeof-bg': 'transparent', 26 | 'value-color': '#24292e', 27 | 'string-editor-color': '#24292e', 28 | 'string-editor-bg': '#d1d5da', 29 | 'icon-color': '#6a737d', 30 | }, 31 | } 32 | }, 33 | variants: { 34 | opacity: ['responsive', 'hover', 'focus', 'active', 'group-hover'], 35 | }, 36 | plugins: [], 37 | future: { 38 | removeDeprecatedGapUtilities: true, 39 | }, 40 | experimental: { 41 | extendedSpacingScale: true, 42 | extendedFontSizeScale: true, 43 | }, 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kevinbatdorf/alpine-inline-devtools", 3 | "version": "0.1.0", 4 | "description": "An easy way to monitor your state while developing with Alpine", 5 | "main": "dist/default.js", 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "scripts": { 10 | "build": "rollup -c", 11 | "watch": "concurrently \"rollup -c -w dev\" \"browser-sync --no-open ./ -f index.html example.html dist\"", 12 | "test": "jest", 13 | "test-watch": "jest --watch" 14 | }, 15 | "husky": { 16 | "hooks": { 17 | "pre-commit": "npm run build" 18 | } 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/kevinbatdorf/alpine-inline-devtools.git" 23 | }, 24 | "keywords": [ 25 | "alpinejs" 26 | ], 27 | "author": "Kevin Batdorf", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/kevinbatdorf/alpine-inline-devtools/issues" 31 | }, 32 | "homepage": "https://github.com/kevinbatdorf/alpine-inline-devtools#readme", 33 | "dependencies": {}, 34 | "peerDependencies": { 35 | "alpinejs": "^2.7" 36 | }, 37 | "devDependencies": { 38 | "@babel/preset-env": "^7.12.1", 39 | "@rollup/plugin-babel": "^5.2.1", 40 | "@rollup/plugin-commonjs": "^15.1.0", 41 | "@rollup/plugin-json": "^4.1.0", 42 | "@rollup/plugin-node-resolve": "^9.0.0", 43 | "@testing-library/dom": "^7.26.6", 44 | "@testing-library/jest-dom": "^5.11.6", 45 | "alpinejs": "^2.7.3", 46 | "autoprefixer": "9.8.6", 47 | "browser-sync": "^2.26.13", 48 | "concurrently": "^5.3.0", 49 | "husky": "^4.3.0", 50 | "jest": "^26.6.3", 51 | "jsdom-simulant": "^1.1.2", 52 | "rollup": "^2.33.2", 53 | "rollup-plugin-filesize": "^9.0.2", 54 | "rollup-plugin-postcss": "^3.1.8", 55 | "tailwindcss": "^1.9.6" 56 | }, 57 | "files": [ 58 | "dist/*", 59 | "package.json", 60 | "LICENSE.md", 61 | "README.md" 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Alpine Inline DevTools 2 | Monitor and update your component state while developing with Alpine JS 3 | 4 | [Live Demo](https://kevinbatdorf.github.io/alpine-inline-devtools/) 5 | 6 | ## Installation 7 | 8 | Include the following ` 14 | @endif 15 | ``` 16 | 17 | ## Coming soon 18 | - Currently from the Dev Tools, you can update strings, booleans, and arrays, but not numbers and objects. 19 | 20 | ## Themes 21 | Choose from a variety of themes. ([Demo](https://kevinbatdorf.github.io/alpine-inline-devtools/)) 22 | 23 | My current favorite is Dracula: 24 | ```html 25 | 26 | ``` 27 | 28 | ## Tips 29 | - Add `x-devtools-ignore` to instruct the DevTools to ignore specific components. 30 | - Add `x-title` to set the title (will default to the `aria-label`, `x-id` then `id` otherwise). 31 | - Add your own button with an `id` of `alpine-devtools-button` to prevent the iframe from loading (will load a popup when pressed) 32 | - Add your own iframe with an `id` of `alpine-devtools-iframe` to position it where you like (see [demo](https://kevinbatdorf.github.io/alpine-inline-devtools/)) 33 | - Click the status bar on the iframe to collapse it. It will remember this on page reload. 34 | 35 | ## Wrapped up as a browser extension 36 | If there's enough interest I will look into packaging this up as a browser extension so you can run it on any page whether in development or not 37 | 38 | ## Contributing 39 | If you're interested in contributing to this project, please read our [contributing docs](https://github.com/KevinBatdorf/alpine-inline-devtools/blob/master/.github/CONTRIBUTING.md) **before submitting a pull request**. 40 | 41 | ## License 42 | 43 | Copyright (c) 2020 Kevin Batdorf 44 | 45 | Licensed under the MIT license, see [LICENSE.md](LICENSE.md) for details. 46 | -------------------------------------------------------------------------------- /src/DevTools.js: -------------------------------------------------------------------------------- 1 | const DevTools = { 2 | start(Loader, Viewer, theme) { 3 | window.alpineDevTools = { 4 | version: '0.11.1', 5 | Viewer: Viewer, 6 | Loader: Loader, 7 | theme: theme, 8 | } 9 | window.addEventListener('DOMContentLoaded', () => { 10 | this.injectDevToolsHandler() 11 | 12 | // A button is on the page already. use that instead 13 | const button = document.getElementById('alpine-devtools-button') 14 | if (button) { 15 | button.addEventListener('click', () => { 16 | window.dispatchEvent(new CustomEvent('open-alpine-devtools-popup', { 17 | bubbles: true, 18 | })) 19 | }) 20 | } 21 | if (sessionStorage.getItem('alpine-devtools') !== 'Popup') { 22 | setTimeout(() => { 23 | window.dispatchEvent(new CustomEvent('open-alpine-devtools', { 24 | bubbles: true, 25 | })) 26 | }, 0) 27 | } 28 | }) 29 | }, 30 | injectDevToolsHandler() { 31 | const alpineDevToolsComponent = document.createElement('div') 32 | alpineDevToolsComponent.id = 'alpine-devtools' 33 | alpineDevToolsComponent.setAttribute('x-data', 'window.alpineDevTools.Loader(window.alpineDevTools.Viewer, window.alpineDevTools.theme)') 34 | alpineDevToolsComponent.setAttribute('x-devtools-ignore', '') 35 | alpineDevToolsComponent.setAttribute('x-on:open-alpine-devtools.window', 'openIframe()') 36 | alpineDevToolsComponent.setAttribute('x-on:open-alpine-devtools-popup.window', 'openPopup()') 37 | alpineDevToolsComponent.setAttribute('x-on:focus-alpine-devtools.window', 'focusDevTools()') 38 | alpineDevToolsComponent.setAttribute('x-on:alpine-devtools-switch-theme.window', 'setTheme($event.detail)') 39 | alpineDevToolsComponent.setAttribute('x-init', '$nextTick(() => { start() })') 40 | alpineDevToolsComponent.style.cssText = "display:none!important" 41 | document.body.appendChild(alpineDevToolsComponent) 42 | } 43 | } 44 | 45 | export default DevTools 46 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.12.0] - 2020-10-07 10 | ### Fixed 11 | - Shortened the iframe collapsed height to avoid overflow 12 | - Removed double scrollbars in Windows. 13 | - Made strings scrollable instead of a long paragraph. 14 | ### Added 15 | - You can now edit strings to that are on arrays 16 | - You can now append strings to arrays 17 | - You can now delete items from arrays 18 | - You can now collapse arrays (it doesn't persist but I might make that an option later on) 19 | 20 | ## [0.11.1] - 2020-10-07 21 | ### Fixed 22 | - Fix min height issue for the inner container 23 | - Fix loading the viewer twice 24 | 25 | ## [0.11.0] - 2020-10-07 26 | ### Added 27 | - Added true inline DevTools via an iframe (default configuration) 28 | - Added a button so you can open the iframe in a tab 29 | - Added a border class for themes 30 | - If you add your own iframe or button to the page, it will override the default loading 31 | - The iframe can be collapsable, even between sessions (click the status bar) 32 | ### Changed 33 | - Simplified the landing page to be more interactive and removed most the text. 34 | 35 | ## [0.10.1] - 2020-10-04 36 | ### Fixed 37 | - Fixed typo `x-ignore` to `x-devtools-ignore` 38 | 39 | ## [0.10.0] - 2020-10-04 40 | ### Fixed 41 | - Bumped up gutter button padding 1px 42 | ### Added 43 | - Added an `x-devtools-ignore` attribute to hide components from DevTools 44 | 45 | ## [0.9.2] - 2020-10-03 46 | ### Fixed 47 | - Updated build files with proper version numbers (TODO: automate that!) 48 | 49 | ## [0.9.1] - 2020-10-03 50 | ### Fixed 51 | - Update build file name case in git (ie. `Github.js` -> `GitHub.js`) 52 | 53 | ## [0.9.0] - 2020-10-03 54 | ### Added 55 | - Added live demo page 56 | - Added version number that you can access from a component 57 | - Added a way to live swap a theme (probably only useful for the live demo) 58 | - Added a way to focus the DevTools (also probably only useful for the demo) 59 | ### Changed 60 | - Updated the default theme spelling to Default to match others 61 | - Hide functions (maybe a regression as I thought this was already happening) 62 | ### Fixed 63 | - Updated the color of the text when no data was found 64 | - Fixed bug where strings would infinitely nest quotes 65 | 66 | ## [0.8.1] - 2020-10-03 67 | ### Fixed 68 | - Waits for `DOMContentLoaded` before injecting the button 69 | 70 | ## [0.8.0] - 2020-10-03 71 | ### Added 72 | - Added a theme based on Github (https://primer.style/css/support/color-system) 73 | - Added a theme based on Hacktoberfest 2020 (http://web.archive.org/web/20200924003932/https://embed-ssl.wistia.com/deliveries/49bd387c40e2c5aada92abdf973bc46d.webp) 74 | - Added a theme based on Dracula (https://draculatheme.com/contribute) 75 | ### Changed 76 | - Updated font to Fira Code (Will eventually make font customizable) 77 | ### Fixed 78 | - Fixed scoping issue where the scope label was updated twice 79 | 80 | ## [0.7.0] - 2020-09-28 81 | ### Added 82 | - Improved how the script gets injected into the popup 83 | - Includes Tailwind config with purging of the CSS instead of loading the CDN 84 | - Added a theming setup based on a few Tailwind config items. Will add in more themes soon 85 | - Improved development of this project with reloading popup instead of previously having to open/close the popup 86 | - A default status message shows how many components are being watched 87 | - Added internal way to track the nested scope (i.e. determine whether a string is inside an array) 88 | ### Changed 89 | - Extracted functions into separate files for better loading 90 | - Made the status icons hopefully less distracting 91 | ### Removed 92 | - Removes the string label and puts the text in quotes (For now. Might consider adding this back) 93 | ### Fixed 94 | - Fixed bug where you could attempt to edit strings in objects 95 | 96 | ## [0.6.0] - 2020-09-08 97 | ### Added 98 | - Discovers new components added after page load 99 | - Adds refresh attribute to elements to trigger external mutation watcher 100 | - You can now edit strings from the dev tools 101 | - Adds a status bar for important info (ex during string update) 102 | ### Removed 103 | - No longer registers component events on the window 104 | -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 24 |
25 | 26 |
27 |
28 |
29 |
30 |
31 |

Boolean

32 |
33 |
34 |
35 |
41 |
42 |

String simple

43 |
44 |
45 |
46 |

String HTML

47 |
48 |
49 |
50 |

String Paragraph

51 |
52 |
53 |
54 |
58 |
59 |

Fruits

60 |
61 |
62 |
63 |
76 |
77 |

Friends

78 | 84 |
85 |
86 |
87 |
88 | 89 | -------------------------------------------------------------------------------- /src/Loader.js: -------------------------------------------------------------------------------- 1 | const Loader = function (Viewer, theme) { 2 | return { 3 | alpines: [], 4 | open: false, 5 | observer: null, 6 | windowRef: null, 7 | viewerScript: Viewer, 8 | viewer: null, 9 | theme: theme, 10 | iframe: null, 11 | type: 'Iframe', 12 | sessionId : Date.now() + Math.floor(Math.random() * 1000000), 13 | start() { 14 | this.alpines = document.querySelectorAll('[x-data]:not([x-devtools-ignore])') 15 | if (!this.alpines) return 16 | this.registerAlpines(this.alpines) 17 | 18 | // If the window is already open, refresh it 19 | if (sessionStorage.getItem('alpine-devtools')) { 20 | this.type = sessionStorage.getItem('alpine-devtools') 21 | // If viewer is already there, only update 22 | this.viewer || this.load() 23 | this.updateAlpines() 24 | } 25 | }, 26 | focusDevTools() { 27 | if (!this.windowRef) { 28 | this[`open${this.type}`]() 29 | } 30 | this.windowRef.focus() 31 | }, 32 | registerAlpines(alpines) { 33 | this.observer = new MutationObserver(mutations => { 34 | this.updateAlpines() 35 | }) 36 | alpines.forEach(alpine => { 37 | // This will trigger a mutation when internal data changes but no visual mutation is observed 38 | alpine.setAttribute('x-bind:data-last-refresh', 'Date.now()') 39 | this.observer.observe(alpine, { 40 | attributes: true, 41 | childList: true, 42 | characterData: true, 43 | subtree: true, 44 | }) 45 | }) 46 | }, 47 | updateAlpines() { 48 | this.checkIfNewAlpinesWereAddedAndRegisterThem() 49 | if (this.windowRef) { 50 | this.windowRef.alpines = this.alpines 51 | const viewer = this.windowRef.document.querySelector('#alpine-devtools-viewer') 52 | if (!viewer) return 53 | if (viewer.__x.$data.editing.length) return 54 | typeof viewer.__x !== 'undefined' && viewer.__x.updateElements(viewer) 55 | } 56 | }, 57 | checkIfNewAlpinesWereAddedAndRegisterThem() { 58 | const fresh = [...document.querySelectorAll('[x-data]:not([x-devtools-ignore])')] 59 | const newAlpines = fresh.filter(alpine => { 60 | return ![...this.alpines].includes(alpine) 61 | }) 62 | if (newAlpines) { 63 | this.alpines = document.querySelectorAll('[x-data]:not([x-devtools-ignore])') 64 | this.registerAlpines(newAlpines) 65 | } 66 | }, 67 | openIframe() { 68 | this.iframe = document.getElementById('alpine-devtools-iframe') 69 | if (!this.iframe) { 70 | const state = sessionStorage.getItem('alpine-devtools-iframe-state') 71 | this.iframe = document.createElement('iframe') 72 | this.iframe.id = 'alpine-devtools-iframe' 73 | this.iframe.width = 450 74 | this.iframe.height = 650 75 | this.iframe.setAttribute('x-data', state ? state : '{height: 650, margin: "0.75rem"}') 76 | this.iframe.setAttribute('x-devtools-ignore', '') 77 | this.iframe.setAttribute('x-on:collapse-devtools.window', 'height = height === 650 ? 25 : 650;margin = margin === "0.75rem" ? "0" : "0.75rem";sessionStorage.setItem("alpine-devtools-iframe-state", `{height: ${height}, margin: "${margin}"}`)') 78 | this.iframe.setAttribute('x-bind:style', '`position:fixed;box-shadow:0 25px 50px -12px rgba(0,0,0,.25)!important;bottom:0!important;right:0!important;margin:${margin}!important;background-color:#252f3f!important;height: ${height}px!important`') 79 | document.body.appendChild(this.iframe) 80 | } 81 | this.windowRef = this.iframe.contentWindow 82 | this.type = 'Iframe' 83 | this.load() 84 | }, 85 | openPopup() { 86 | if (this.iframe) { 87 | this.iframe.parentNode.removeChild(this.iframe) 88 | this.iframe = null 89 | } 90 | this.windowRef = window.open('', 'alpine-devtools', 'width=450, height=650, left=100, top=100') 91 | this.type = 'Popup' 92 | this.load() 93 | }, 94 | load() { 95 | if (!this.windowRef) { 96 | sessionStorage.removeItem('alpine-devtools') 97 | this[`open${this.type}`]() 98 | } 99 | 100 | // Add the reference to the session so we don't need to reopen the popup everytime 101 | sessionStorage.setItem('alpine-devtools', this.type) 102 | 103 | // Starting color. Consider some loading animation 104 | this.windowRef.document.body.style.backgroundColor = '#1a202c' 105 | this.windowRef.document.body.style.height = '100%' 106 | this.windowRef.document.body.innerHTML = '' 107 | this.windowRef.document.title = 'Alpine DevTools' 108 | this.windowRef.alpines = this.alpines 109 | 110 | // TODO: take in AlpineJS version option 111 | if (!this.windowRef.document.getElementById('alpine-devtools-script')) { 112 | const alpineScript = this.windowRef.document.createElement('script') 113 | alpineScript.id = 'alpine-devtools-script' 114 | const version = window.Alpine.version || '2.x.x' 115 | alpineScript.src = `https://cdn.jsdelivr.net/gh/alpinejs/alpine@v${version}/dist/alpine.min.js` 116 | this.windowRef.document.head.appendChild(alpineScript) 117 | } 118 | 119 | // TODO: This should eventually be settable somehow 120 | if (!this.windowRef.document.getElementById('alpine-devtools-font')) { 121 | const font = this.windowRef.document.createElement('link') 122 | font.id = 'alpine-devtools-font' 123 | font.rel = 'stylesheet' 124 | font.href = "https://fonts.googleapis.com/css2?family=Fira+Code&display=swap" 125 | this.windowRef.document.head.appendChild(font) 126 | } 127 | 128 | this.setTheme(this.theme) 129 | 130 | // Add the dev tools component function and remove any previous 131 | const oldScript = this.windowRef.document.getElementById('devtools-script') 132 | oldScript && oldScript.remove() 133 | const devtoolsScript = this.windowRef.document.createElement('script') 134 | devtoolsScript.id = 'devtools-script' 135 | devtoolsScript.textContent = `window.Viewer = ${this.viewerScript}` 136 | this.windowRef.document.head.appendChild(devtoolsScript) 137 | 138 | this.loadView() 139 | }, 140 | setTheme(theme) { 141 | if (!this.windowRef) { 142 | this[`open${this.type}`]() 143 | } 144 | this.theme = theme ? theme : this.theme 145 | // Add the theme and remove any previous (Can possibly support theme swapping) 146 | const oldTheme = this.windowRef.document.getElementById('alpine-devtools-theme') 147 | oldTheme && oldTheme.remove() 148 | const devtoolsTheme = this.windowRef.document.createElement('style') 149 | devtoolsTheme.id = 'alpine-devtools-theme' 150 | devtoolsTheme.textContent = this.theme 151 | this.windowRef.document.head.appendChild(devtoolsTheme) 152 | }, 153 | loadView() { 154 | this.viewer = this.windowRef.document.createElement('div') 155 | this.viewer.id = 'alpine-devtools-viewer' 156 | this.viewer.setAttribute('x-data', `window.Viewer('${this.type}')`) 157 | this.viewer.setAttribute('x-init', 'setup()') 158 | this.viewer.setAttribute('x-on:open-alpine-devtools-popup.window', ` 159 | window.parent.dispatchEvent(new CustomEvent('open-alpine-devtools-popup', { 160 | bubbles: true, 161 | event: 'Popup' 162 | }))`) 163 | this.viewer.setAttribute('x-init', 'setup()') 164 | 165 | this.open = true 166 | window.alpineDevTools.open = true 167 | setTimeout(() => { 168 | this.windowRef.document.body.appendChild(this.viewer) 169 | }, 500) 170 | 171 | this.windowRef.addEventListener('beforeunload', () => { 172 | sessionStorage.removeItem('alpine-devtools') 173 | this.type === 'popup' && this.windowRef.close() 174 | this.windowRef = null 175 | this.open = false 176 | window.alpineDevTools.open = false 177 | }) 178 | }, 179 | } 180 | } 181 | 182 | export default Loader 183 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 29 | 59 | 60 | 61 | 62 | 63 |
64 | 98 | 99 |
100 |
101 |
102 |
103 |
104 |
105 | 108 | 111 | 112 | 113 |
114 |
115 |
116 |
117 |

118 | What is it? 119 |

120 |

Alpine Inline DevTools is an obverser tool that's useful for developing with Alpine.js.

121 |

By default, the DevTools will automatically load on your page at the bottom right corner ().

122 | 130 |

To load in a popup window, press the small square on the bottom right See the full documentation on GitHub.

131 |
132 |
133 |
134 |
135 |
136 | 238 |
239 |
240 |
241 | 242 | 260 | 261 | -------------------------------------------------------------------------------- /src/Viewer.js: -------------------------------------------------------------------------------- 1 | const Viewer = function(type) { 2 | return { 3 | editing: '', 4 | type: type, 5 | collapsedArrays: [], // TODO: persist this?? 6 | setup() { 7 | this.$el.innerHTML = `
11 | 15 |
17 | 34 |
35 |
41 |
42 |
` 43 | }, 44 | computeTitle(alpine) { 45 | return alpine.getAttribute('x-title') 46 | || alpine.getAttribute('aria-label') 47 | || alpine.getAttribute('x-id') 48 | || alpine.id 49 | || this.convertFunctionName(alpine.getAttribute('x-data')) 50 | || `<${alpine.tagName.toLowerCase()}>` 51 | }, 52 | getAlpineData(alpine) { 53 | if (!alpine.__x) return [] 54 | return Object.entries(alpine.__x.getUnobservedData()) 55 | }, 56 | getType(value) { 57 | // Leave as object for now until we need it 58 | // if (value === null) { 59 | // return 'null' 60 | // } 61 | if (Array.isArray(value)) { 62 | return 'array' 63 | } 64 | if (typeof value === 'function') { 65 | return 'function' 66 | } 67 | return typeof value 68 | }, 69 | updateAlpine(type, alpineIndex, key, value, scope, context = '') { 70 | let scopes = scope.split('.') // Just focus on the last one 71 | switch (scopes[scopes.length - 1]) { 72 | case 'boolean': 73 | return this.objectSetDeep(window.alpines[alpineIndex].__x.$data, context, (value !== 'true')) 74 | case 'string': 75 | return this.objectSetDeep(window.alpines[alpineIndex].__x.$data, context, value) 76 | } 77 | }, 78 | removeItemFromArray(type, alpineIndex, key, value, scope, context = '') { 79 | return this.objectSpliceDeep(window.alpines[alpineIndex].__x.$data, context) 80 | }, 81 | getItem(key, value, alpineIndex = null, scope = '', context = '') { 82 | const id = Date.now() + Math.floor(Math.random() * 1000000) 83 | const type = this.getType(value) 84 | scope = scope ? `${scope}.${type}` : type 85 | context = context ? `${context}.${key}` : key 86 | return ` 87 | 88 | ${this.getGutterAction(id, type, alpineIndex, key, value, scope, context)} 89 | 90 | 91 | : 92 | ${this.getProperyTypeMessage(id, type, alpineIndex, key, value, scope, context)} 93 | 94 | ${this.getGutterExtraRight(id, type, alpineIndex, key, value, scope, context)} 95 | 96 | 99 | 106 | ${this.getValue(id, type, alpineIndex, key, value, scope, context)} 107 | 108 | ${this.getEditField(id, type, alpineIndex, key, value, scope, context)} 109 | 110 | ` 111 | }, 112 | getItemRowStyling(id, type, alpineIndex, key, value, scope, context = '') { 113 | switch (type) { 114 | case 'string': 115 | return `relative py-1 pl-1.5 flex items-start` 116 | case 'array': 117 | return `relative py-1 pl-1.5 inline-block` 118 | } 119 | return `relative py-1 pl-1.5 inline-block` 120 | }, 121 | getEditField(id, type, alpineIndex, key, value, scope, context = '') { 122 | if (scope.endsWith('array.string') || scope === 'string') { 123 | return `` 141 | } 142 | return '' 143 | }, 144 | getGutterAction(id, type, alpineIndex, key, value, scope, context = '') { 145 | const wrap = (content) => `${content}` 146 | const wrapTight = (content) => `${content}` 147 | switch (type) { 148 | case 'boolean': 149 | return wrap(` 150 | `) 156 | case 'string': 157 | if ('string' !== scope && !scope.endsWith('array.string')) return '' 158 | const deleteButton = ` 159 | ` 168 | const editButton = ` 169 | ` 178 | return scope.endsWith('array.string') ? wrapTight(deleteButton + editButton) : wrap(editButton) 179 | case 'array': 180 | return wrap(` 181 | `) 198 | default: 199 | return '' 200 | } 201 | }, 202 | getGutterExtraRight(id, type, alpineIndex, key, value, scope, context = '') { 203 | switch (type) { 204 | case 'array': 205 | return ` 206 | 212 | 223 | ` 224 | case 'string': 225 | if (!scope.endsWith('array.string')) return '' 226 | return '' 227 | } 228 | return '' 229 | }, 230 | getProperyTypeMessage(id, type, alpineIndex, key, value, scope) { 231 | let wrap = (content) => `${content}` 232 | switch (type) { 233 | case 'string': 234 | return '' 235 | case 'array': 236 | if (this.collapsedArrays.includes(`${alpineIndex}:${key}`)) { 237 | return wrap(`array[${value.length}]`) 238 | } 239 | return wrap`array` 240 | } 241 | return wrap(type) 242 | }, 243 | openEditorAndSelectText(id, type, alpineIndex, key, value, scope, context) { 244 | // If the editor is already open and they press the edit icon again, commit the edit 245 | if (this.editing === `${alpineIndex}-${key}`) { 246 | this.updateAlpine('string', alpineIndex, key, this.$refs[`editor-${alpineIndex}-${key}`].textContent.trim(), scope, context) 247 | this.editing = '' 248 | return 249 | } 250 | this.editing = `${alpineIndex}-${key}` 251 | this.$nextTick(() => { 252 | this.editing && this.$refs[`editor-${alpineIndex}-${key}`].focus() 253 | this.editing && document.execCommand('selectAll', false, null) 254 | }) 255 | }, 256 | getValue(id, type, alpineIndex, key, value, scope, context = '') { 257 | switch (type) { 258 | case 'function': 259 | return '' 260 | case 'boolean': 261 | return value 262 | case 'number': 263 | return value 264 | case 'string': 265 | if (!['string', 'array.string'].includes(scope)){ 266 | return `"${this.escapeHTML(value)}"` 267 | } 268 | return ` 271 | "${this.escapeHTML(value)}" 272 | ` 273 | case 'array': 274 | if (!value) return value 275 | return Object.entries(value).map(([...item]) => { 276 | return `
${this.getItem(item[0], item[1], alpineIndex, scope, context)}
` 277 | }).join('') 278 | case 'object': 279 | if (!value) return value 280 | return Object.entries(value).map(([...item]) => { 281 | return `
${this.getItem(item[0], item[1], alpineIndex, scope, context)}
` 282 | }).join('') 283 | } 284 | return value 285 | }, 286 | getStatusMessage() { 287 | if (this.editing.length) { 288 | return 'Press Enter or click away to finish. Press Esc to cancel.' 289 | } 290 | return ` 291 | Watching ${window.alpines.length} components... 292 |
293 | 294 | 295 | 296 | 297 | 298 | 299 | 302 |
303 | ` 304 | }, 305 | convertFunctionName(functionName) { 306 | if (functionName.indexOf('{') === 0) return 307 | let name = functionName.replace(/\(([^\)]+)\)/, '').replace('()', '').replace(/([A-Z])/g, " $1") 308 | return name ? name.charAt(0).toUpperCase() + name.slice(1) : '' 309 | }, 310 | escapeHTML(html) { 311 | let div = document.createElement('div') 312 | div.innerText = html 313 | return div.innerHTML 314 | }, 315 | // Borrowed from https://stackoverflow.com/a/54733755/1437789 316 | objectSetDeep(object, path, value) { 317 | path = path.toString().match(/[^.[\]]+/g) || [] 318 | // Iterate all of them except the last one 319 | path.slice(0, -1).reduce((a, currentKey, index) => { 320 | // If the key does not exist or its value is not an object, create/override the key 321 | if (Object(a[currentKey]) !== a[currentKey]) { 322 | // Is the next key a potential array-index? 323 | a[currentKey] = Math.abs(path[index + 1]) >> 0 === +path[index + 1] 324 | ? [] // Yes: assign a new array object 325 | : {} // No: assign a new plain object 326 | } 327 | return a[currentKey] 328 | }, object)[path[path.length - 1]] = value // Finally assign the value to the last key 329 | return object 330 | }, 331 | objectSpliceDeep(object, path) { 332 | path = path.toString().match(/[^.[\]]+/g) || [] 333 | path.slice(0, -1).reduce((a, currentKey, index) => { 334 | return a[currentKey] 335 | }, object).splice(path[path.length - 1], 1) 336 | return object 337 | }, 338 | } 339 | } 340 | 341 | export default Viewer 342 | -------------------------------------------------------------------------------- /dist/Dracula.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | typeof define === 'function' && define.amd ? define(factory) : 3 | factory(); 4 | }((function () { 'use strict'; 5 | 6 | const DevTools = { 7 | start(Loader, Viewer, theme) { 8 | window.alpineDevTools = { 9 | version: '0.11.1', 10 | Viewer: Viewer, 11 | Loader: Loader, 12 | theme: theme 13 | }; 14 | window.addEventListener('DOMContentLoaded', () => { 15 | this.injectDevToolsHandler(); // A button is on the page already. use that instead 16 | 17 | const button = document.getElementById('alpine-devtools-button'); 18 | 19 | if (button) { 20 | button.addEventListener('click', () => { 21 | window.dispatchEvent(new CustomEvent('open-alpine-devtools-popup', { 22 | bubbles: true 23 | })); 24 | }); 25 | } 26 | 27 | if (sessionStorage.getItem('alpine-devtools') !== 'Popup') { 28 | setTimeout(() => { 29 | window.dispatchEvent(new CustomEvent('open-alpine-devtools', { 30 | bubbles: true 31 | })); 32 | }, 0); 33 | } 34 | }); 35 | }, 36 | 37 | injectDevToolsHandler() { 38 | const alpineDevToolsComponent = document.createElement('div'); 39 | alpineDevToolsComponent.id = 'alpine-devtools'; 40 | alpineDevToolsComponent.setAttribute('x-data', 'window.alpineDevTools.Loader(window.alpineDevTools.Viewer, window.alpineDevTools.theme)'); 41 | alpineDevToolsComponent.setAttribute('x-devtools-ignore', ''); 42 | alpineDevToolsComponent.setAttribute('x-on:open-alpine-devtools.window', 'openIframe()'); 43 | alpineDevToolsComponent.setAttribute('x-on:open-alpine-devtools-popup.window', 'openPopup()'); 44 | alpineDevToolsComponent.setAttribute('x-on:focus-alpine-devtools.window', 'focusDevTools()'); 45 | alpineDevToolsComponent.setAttribute('x-on:alpine-devtools-switch-theme.window', 'setTheme($event.detail)'); 46 | alpineDevToolsComponent.setAttribute('x-init', '$nextTick(() => { start() })'); 47 | alpineDevToolsComponent.style.cssText = "display:none!important"; 48 | document.body.appendChild(alpineDevToolsComponent); 49 | } 50 | 51 | }; 52 | 53 | const Loader = function (Viewer, theme) { 54 | return { 55 | alpines: [], 56 | open: false, 57 | observer: null, 58 | windowRef: null, 59 | viewerScript: Viewer, 60 | viewer: null, 61 | theme: theme, 62 | iframe: null, 63 | type: 'Iframe', 64 | sessionId: Date.now() + Math.floor(Math.random() * 1000000), 65 | 66 | start() { 67 | this.alpines = document.querySelectorAll('[x-data]:not([x-devtools-ignore])'); 68 | if (!this.alpines) return; 69 | this.registerAlpines(this.alpines); // If the window is already open, refresh it 70 | 71 | if (sessionStorage.getItem('alpine-devtools')) { 72 | this.type = sessionStorage.getItem('alpine-devtools'); // If viewer is already there, only update 73 | 74 | this.viewer || this.load(); 75 | this.updateAlpines(); 76 | } 77 | }, 78 | 79 | focusDevTools() { 80 | if (!this.windowRef) { 81 | this[`open${this.type}`](); 82 | } 83 | 84 | this.windowRef.focus(); 85 | }, 86 | 87 | registerAlpines(alpines) { 88 | this.observer = new MutationObserver(mutations => { 89 | this.updateAlpines(); 90 | }); 91 | alpines.forEach(alpine => { 92 | // This will trigger a mutation when internal data changes but no visual mutation is observed 93 | alpine.setAttribute('x-bind:data-last-refresh', 'Date.now()'); 94 | this.observer.observe(alpine, { 95 | attributes: true, 96 | childList: true, 97 | characterData: true, 98 | subtree: true 99 | }); 100 | }); 101 | }, 102 | 103 | updateAlpines() { 104 | this.checkIfNewAlpinesWereAddedAndRegisterThem(); 105 | 106 | if (this.windowRef) { 107 | this.windowRef.alpines = this.alpines; 108 | const viewer = this.windowRef.document.querySelector('#alpine-devtools-viewer'); 109 | if (!viewer) return; 110 | if (viewer.__x.$data.editing.length) return; 111 | typeof viewer.__x !== 'undefined' && viewer.__x.updateElements(viewer); 112 | } 113 | }, 114 | 115 | checkIfNewAlpinesWereAddedAndRegisterThem() { 116 | const fresh = [...document.querySelectorAll('[x-data]:not([x-devtools-ignore])')]; 117 | const newAlpines = fresh.filter(alpine => { 118 | return ![...this.alpines].includes(alpine); 119 | }); 120 | 121 | if (newAlpines) { 122 | this.alpines = document.querySelectorAll('[x-data]:not([x-devtools-ignore])'); 123 | this.registerAlpines(newAlpines); 124 | } 125 | }, 126 | 127 | openIframe() { 128 | this.iframe = document.getElementById('alpine-devtools-iframe'); 129 | 130 | if (!this.iframe) { 131 | const state = sessionStorage.getItem('alpine-devtools-iframe-state'); 132 | this.iframe = document.createElement('iframe'); 133 | this.iframe.id = 'alpine-devtools-iframe'; 134 | this.iframe.width = 450; 135 | this.iframe.height = 650; 136 | this.iframe.setAttribute('x-data', state ? state : '{height: 650, margin: "0.75rem"}'); 137 | this.iframe.setAttribute('x-devtools-ignore', ''); 138 | this.iframe.setAttribute('x-on:collapse-devtools.window', 'height = height === 650 ? 25 : 650;margin = margin === "0.75rem" ? "0" : "0.75rem";sessionStorage.setItem("alpine-devtools-iframe-state", `{height: ${height}, margin: "${margin}"}`)'); 139 | this.iframe.setAttribute('x-bind:style', '`position:fixed;box-shadow:0 25px 50px -12px rgba(0,0,0,.25)!important;bottom:0!important;right:0!important;margin:${margin}!important;background-color:#252f3f!important;height: ${height}px!important`'); 140 | document.body.appendChild(this.iframe); 141 | } 142 | 143 | this.windowRef = this.iframe.contentWindow; 144 | this.type = 'Iframe'; 145 | this.load(); 146 | }, 147 | 148 | openPopup() { 149 | if (this.iframe) { 150 | this.iframe.parentNode.removeChild(this.iframe); 151 | this.iframe = null; 152 | } 153 | 154 | this.windowRef = window.open('', 'alpine-devtools', 'width=450, height=650, left=100, top=100'); 155 | this.type = 'Popup'; 156 | this.load(); 157 | }, 158 | 159 | load() { 160 | if (!this.windowRef) { 161 | sessionStorage.removeItem('alpine-devtools'); 162 | this[`open${this.type}`](); 163 | } // Add the reference to the session so we don't need to reopen the popup everytime 164 | 165 | 166 | sessionStorage.setItem('alpine-devtools', this.type); // Starting color. Consider some loading animation 167 | 168 | this.windowRef.document.body.style.backgroundColor = '#1a202c'; 169 | this.windowRef.document.body.style.height = '100%'; 170 | this.windowRef.document.body.innerHTML = ''; 171 | this.windowRef.document.title = 'Alpine DevTools'; 172 | this.windowRef.alpines = this.alpines; // TODO: take in AlpineJS version option 173 | 174 | if (!this.windowRef.document.getElementById('alpine-devtools-script')) { 175 | const alpineScript = this.windowRef.document.createElement('script'); 176 | alpineScript.id = 'alpine-devtools-script'; 177 | const version = window.Alpine.version || '2.x.x'; 178 | alpineScript.src = `https://cdn.jsdelivr.net/gh/alpinejs/alpine@v${version}/dist/alpine.min.js`; 179 | this.windowRef.document.head.appendChild(alpineScript); 180 | } // TODO: This should eventually be settable somehow 181 | 182 | 183 | if (!this.windowRef.document.getElementById('alpine-devtools-font')) { 184 | const font = this.windowRef.document.createElement('link'); 185 | font.id = 'alpine-devtools-font'; 186 | font.rel = 'stylesheet'; 187 | font.href = "https://fonts.googleapis.com/css2?family=Fira+Code&display=swap"; 188 | this.windowRef.document.head.appendChild(font); 189 | } 190 | 191 | this.setTheme(this.theme); // Add the dev tools component function and remove any previous 192 | 193 | const oldScript = this.windowRef.document.getElementById('devtools-script'); 194 | oldScript && oldScript.remove(); 195 | const devtoolsScript = this.windowRef.document.createElement('script'); 196 | devtoolsScript.id = 'devtools-script'; 197 | devtoolsScript.textContent = `window.Viewer = ${this.viewerScript}`; 198 | this.windowRef.document.head.appendChild(devtoolsScript); 199 | this.loadView(); 200 | }, 201 | 202 | setTheme(theme) { 203 | if (!this.windowRef) { 204 | this[`open${this.type}`](); 205 | } 206 | 207 | this.theme = theme ? theme : this.theme; // Add the theme and remove any previous (Can possibly support theme swapping) 208 | 209 | const oldTheme = this.windowRef.document.getElementById('alpine-devtools-theme'); 210 | oldTheme && oldTheme.remove(); 211 | const devtoolsTheme = this.windowRef.document.createElement('style'); 212 | devtoolsTheme.id = 'alpine-devtools-theme'; 213 | devtoolsTheme.textContent = this.theme; 214 | this.windowRef.document.head.appendChild(devtoolsTheme); 215 | }, 216 | 217 | loadView() { 218 | this.viewer = this.windowRef.document.createElement('div'); 219 | this.viewer.id = 'alpine-devtools-viewer'; 220 | this.viewer.setAttribute('x-data', `window.Viewer('${this.type}')`); 221 | this.viewer.setAttribute('x-init', 'setup()'); 222 | this.viewer.setAttribute('x-on:open-alpine-devtools-popup.window', ` 223 | window.parent.dispatchEvent(new CustomEvent('open-alpine-devtools-popup', { 224 | bubbles: true, 225 | event: 'Popup' 226 | }))`); 227 | this.viewer.setAttribute('x-init', 'setup()'); 228 | this.open = true; 229 | window.alpineDevTools.open = true; 230 | setTimeout(() => { 231 | this.windowRef.document.body.appendChild(this.viewer); 232 | }, 500); 233 | this.windowRef.addEventListener('beforeunload', () => { 234 | sessionStorage.removeItem('alpine-devtools'); 235 | this.type === 'popup' && this.windowRef.close(); 236 | this.windowRef = null; 237 | this.open = false; 238 | window.alpineDevTools.open = false; 239 | }); 240 | } 241 | 242 | }; 243 | }; 244 | 245 | const Viewer = function (type) { 246 | return { 247 | editing: '', 248 | type: type, 249 | collapsedArrays: [], 250 | 251 | // TODO: persist this?? 252 | setup() { 253 | this.$el.innerHTML = `
257 | 261 |
263 | 280 |
281 |
287 |
288 |
`; 289 | }, 290 | 291 | computeTitle(alpine) { 292 | return alpine.getAttribute('x-title') || alpine.getAttribute('aria-label') || alpine.getAttribute('x-id') || alpine.id || this.convertFunctionName(alpine.getAttribute('x-data')) || `<${alpine.tagName.toLowerCase()}>`; 293 | }, 294 | 295 | getAlpineData(alpine) { 296 | if (!alpine.__x) return []; 297 | return Object.entries(alpine.__x.getUnobservedData()); 298 | }, 299 | 300 | getType(value) { 301 | // Leave as object for now until we need it 302 | // if (value === null) { 303 | // return 'null' 304 | // } 305 | if (Array.isArray(value)) { 306 | return 'array'; 307 | } 308 | 309 | if (typeof value === 'function') { 310 | return 'function'; 311 | } 312 | 313 | return typeof value; 314 | }, 315 | 316 | updateAlpine(type, alpineIndex, key, value, scope, context = '') { 317 | let scopes = scope.split('.'); // Just focus on the last one 318 | 319 | switch (scopes[scopes.length - 1]) { 320 | case 'boolean': 321 | return this.objectSetDeep(window.alpines[alpineIndex].__x.$data, context, value !== 'true'); 322 | 323 | case 'string': 324 | return this.objectSetDeep(window.alpines[alpineIndex].__x.$data, context, value); 325 | } 326 | }, 327 | 328 | removeItemFromArray(type, alpineIndex, key, value, scope, context = '') { 329 | return this.objectSpliceDeep(window.alpines[alpineIndex].__x.$data, context); 330 | }, 331 | 332 | getItem(key, value, alpineIndex = null, scope = '', context = '') { 333 | const id = Date.now() + Math.floor(Math.random() * 1000000); 334 | const type = this.getType(value); 335 | scope = scope ? `${scope}.${type}` : type; 336 | context = context ? `${context}.${key}` : key; 337 | return ` 338 | 339 | ${this.getGutterAction(id, type, alpineIndex, key, value, scope, context)} 340 | 341 | 342 | : 343 | ${this.getProperyTypeMessage(id, type, alpineIndex, key, value, scope, context)} 344 | 345 | ${this.getGutterExtraRight(id, type, alpineIndex, key, value, scope, context)} 346 | 347 | 350 | 357 | ${this.getValue(id, type, alpineIndex, key, value, scope, context)} 358 | 359 | ${this.getEditField(id, type, alpineIndex, key, value, scope, context)} 360 | 361 | `; 362 | }, 363 | 364 | getItemRowStyling(id, type, alpineIndex, key, value, scope, context = '') { 365 | switch (type) { 366 | case 'string': 367 | return `relative py-1 pl-1.5 flex items-start`; 368 | 369 | case 'array': 370 | return `relative py-1 pl-1.5 inline-block`; 371 | } 372 | 373 | return `relative py-1 pl-1.5 inline-block`; 374 | }, 375 | 376 | getEditField(id, type, alpineIndex, key, value, scope, context = '') { 377 | if (scope.endsWith('array.string') || scope === 'string') { 378 | return ``; 396 | } 397 | 398 | return ''; 399 | }, 400 | 401 | getGutterAction(id, type, alpineIndex, key, value, scope, context = '') { 402 | const wrap = content => `${content}`; 403 | 404 | const wrapTight = content => `${content}`; 405 | 406 | switch (type) { 407 | case 'boolean': 408 | return wrap(` 409 | `); 415 | 416 | case 'string': 417 | if ('string' !== scope && !scope.endsWith('array.string')) return ''; 418 | const deleteButton = ` 419 | `; 428 | const editButton = ` 429 | `; 438 | return scope.endsWith('array.string') ? wrapTight(deleteButton + editButton) : wrap(editButton); 439 | 440 | case 'array': 441 | return wrap(` 442 | `); 459 | 460 | default: 461 | return ''; 462 | } 463 | }, 464 | 465 | getGutterExtraRight(id, type, alpineIndex, key, value, scope, context = '') { 466 | switch (type) { 467 | case 'array': 468 | return ` 469 | 475 | 486 | `; 487 | 488 | case 'string': 489 | if (!scope.endsWith('array.string')) return ''; 490 | return ''; 491 | } 492 | 493 | return ''; 494 | }, 495 | 496 | getProperyTypeMessage(id, type, alpineIndex, key, value, scope) { 497 | let wrap = content => `${content}`; 498 | 499 | switch (type) { 500 | case 'string': 501 | return ''; 502 | 503 | case 'array': 504 | if (this.collapsedArrays.includes(`${alpineIndex}:${key}`)) { 505 | return wrap(`array[${value.length}]`); 506 | } 507 | 508 | return wrap`array`; 509 | } 510 | 511 | return wrap(type); 512 | }, 513 | 514 | openEditorAndSelectText(id, type, alpineIndex, key, value, scope, context) { 515 | // If the editor is already open and they press the edit icon again, commit the edit 516 | if (this.editing === `${alpineIndex}-${key}`) { 517 | this.updateAlpine('string', alpineIndex, key, this.$refs[`editor-${alpineIndex}-${key}`].textContent.trim(), scope, context); 518 | this.editing = ''; 519 | return; 520 | } 521 | 522 | this.editing = `${alpineIndex}-${key}`; 523 | this.$nextTick(() => { 524 | this.editing && this.$refs[`editor-${alpineIndex}-${key}`].focus(); 525 | this.editing && document.execCommand('selectAll', false, null); 526 | }); 527 | }, 528 | 529 | getValue(id, type, alpineIndex, key, value, scope, context = '') { 530 | switch (type) { 531 | case 'function': 532 | return ''; 533 | 534 | case 'boolean': 535 | return value; 536 | 537 | case 'number': 538 | return value; 539 | 540 | case 'string': 541 | if (!['string', 'array.string'].includes(scope)) { 542 | return `"${this.escapeHTML(value)}"`; 543 | } 544 | 545 | return ` 548 | "${this.escapeHTML(value)}" 549 | `; 550 | 551 | case 'array': 552 | if (!value) return value; 553 | return Object.entries(value).map(([...item]) => { 554 | return `
${this.getItem(item[0], item[1], alpineIndex, scope, context)}
`; 555 | }).join(''); 556 | 557 | case 'object': 558 | if (!value) return value; 559 | return Object.entries(value).map(([...item]) => { 560 | return `
${this.getItem(item[0], item[1], alpineIndex, scope, context)}
`; 561 | }).join(''); 562 | } 563 | 564 | return value; 565 | }, 566 | 567 | getStatusMessage() { 568 | if (this.editing.length) { 569 | return 'Press Enter or click away to finish. Press Esc to cancel.'; 570 | } 571 | 572 | return ` 573 | Watching ${window.alpines.length} components... 574 |
575 | 576 | 577 | 578 | 579 | 580 | 581 | 584 |
585 | `; 586 | }, 587 | 588 | convertFunctionName(functionName) { 589 | if (functionName.indexOf('{') === 0) return; 590 | let name = functionName.replace(/\(([^\)]+)\)/, '').replace('()', '').replace(/([A-Z])/g, " $1"); 591 | return name ? name.charAt(0).toUpperCase() + name.slice(1) : ''; 592 | }, 593 | 594 | escapeHTML(html) { 595 | let div = document.createElement('div'); 596 | div.innerText = html; 597 | return div.innerHTML; 598 | }, 599 | 600 | // Borrowed from https://stackoverflow.com/a/54733755/1437789 601 | objectSetDeep(object, path, value) { 602 | path = path.toString().match(/[^.[\]]+/g) || []; // Iterate all of them except the last one 603 | 604 | path.slice(0, -1).reduce((a, currentKey, index) => { 605 | // If the key does not exist or its value is not an object, create/override the key 606 | if (Object(a[currentKey]) !== a[currentKey]) { 607 | // Is the next key a potential array-index? 608 | a[currentKey] = Math.abs(path[index + 1]) >> 0 === +path[index + 1] ? [] // Yes: assign a new array object 609 | : {}; // No: assign a new plain object 610 | } 611 | 612 | return a[currentKey]; 613 | }, object)[path[path.length - 1]] = value; // Finally assign the value to the last key 614 | 615 | return object; 616 | }, 617 | 618 | objectSpliceDeep(object, path) { 619 | path = path.toString().match(/[^.[\]]+/g) || []; 620 | path.slice(0, -1).reduce((a, currentKey, index) => { 621 | return a[currentKey]; 622 | }, object).splice(path[path.length - 1], 1); 623 | return object; 624 | } 625 | 626 | }; 627 | }; 628 | 629 | function styleInject(css, ref) { 630 | if ( ref === void 0 ) ref = {}; 631 | var insertAt = ref.insertAt; 632 | 633 | if (!css || typeof document === 'undefined') { return; } 634 | 635 | var head = document.head || document.getElementsByTagName('head')[0]; 636 | var style = document.createElement('style'); 637 | style.type = 'text/css'; 638 | 639 | if (insertAt === 'top') { 640 | if (head.firstChild) { 641 | head.insertBefore(style, head.firstChild); 642 | } else { 643 | head.appendChild(style); 644 | } 645 | } else { 646 | head.appendChild(style); 647 | } 648 | 649 | if (style.styleSheet) { 650 | style.styleSheet.cssText = css; 651 | } else { 652 | style.appendChild(document.createTextNode(css)); 653 | } 654 | } 655 | 656 | var css_248z = "/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n line-height: 1.15; /* 1 */\n -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n margin: 0;\n}\n\n/**\n * Render the `main` element consistently in IE.\n */\n\nmain {\n display: block;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n/* Grouping content\n ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n box-sizing: content-box; /* 1 */\n height: 0; /* 1 */\n overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n border-bottom: none; /* 1 */\n text-decoration: underline; /* 2 */\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted; /* 2 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\n/* Embedded content\n ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n border-style: none;\n}\n\n/* Forms\n ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n font-family: inherit; /* 1 */\n font-size: 100%; /* 1 */\n line-height: 1.15; /* 1 */\n margin: 0; /* 2 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput { /* 1 */\n overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n border-style: none;\n padding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n * `fieldset` elements in all browsers.\n */\n\nlegend {\n box-sizing: border-box; /* 1 */\n color: inherit; /* 2 */\n display: table; /* 1 */\n max-width: 100%; /* 1 */\n padding: 0; /* 3 */\n white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n box-sizing: border-box; /* 1 */\n padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type=\"search\"] {\n -webkit-appearance: textfield; /* 1 */\n outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n -webkit-appearance: button; /* 1 */\n font: inherit; /* 2 */\n}\n\n/* Interactive\n ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n display: list-item;\n}\n\n/* Misc\n ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n display: none;\n}\n\n/**\n * Manually forked from SUIT CSS Base: https://github.com/suitcss/base\n * A thin layer on top of normalize.css that provides a starting point more\n * suitable for web applications.\n */\n\n/**\n * Removes the default spacing and border for appropriate elements.\n */\n\nblockquote,\ndl,\ndd,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nhr,\nfigure,\np,\npre {\n margin: 0;\n}\n\nbutton {\n background-color: transparent;\n background-image: none;\n}\n\n/**\n * Work around a Firefox/IE bug where the transparent `button` background\n * results in a loss of the default `button` focus styles.\n */\n\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\nfieldset {\n margin: 0;\n padding: 0;\n}\n\nol,\nul {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n/**\n * Tailwind custom reset styles\n */\n\n/**\n * 1. Use the user's configured `sans` font-family (with Tailwind's default\n * sans-serif font stack as a fallback) as a sane default.\n * 2. Use Tailwind's default \"normal\" line-height so the user isn't forced\n * to override it to ensure consistency even when using the default theme.\n */\n\nhtml {\n font-family: system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"; /* 1 */\n line-height: 1.5; /* 2 */\n}\n\n/**\n * 1. Prevent padding and border from affecting element width.\n *\n * We used to set this in the html element and inherit from\n * the parent element for everything else. This caused issues\n * in shadow-dom-enhanced elements like
where the content\n * is wrapped by a div with box-sizing set to `content-box`.\n *\n * https://github.com/mozdevs/cssremedy/issues/4\n *\n *\n * 2. Allow adding a border to an element by just adding a border-width.\n *\n * By default, the way the browser specifies that an element should have no\n * border is by setting it's border-style to `none` in the user-agent\n * stylesheet.\n *\n * In order to easily add borders to elements by just setting the `border-width`\n * property, we change the default border-style for all elements to `solid`, and\n * use border-width to hide them instead. This way our `border` utilities only\n * need to set the `border-width` property instead of the entire `border`\n * shorthand, making our border utilities much more straightforward to compose.\n *\n * https://github.com/tailwindcss/tailwindcss/pull/116\n */\n\n*,\n::before,\n::after {\n box-sizing: border-box; /* 1 */\n border-width: 0; /* 2 */\n border-style: solid; /* 2 */\n border-color: #e2e8f0; /* 2 */\n}\n\n/*\n * Ensure horizontal rules are visible by default\n */\n\nhr {\n border-top-width: 1px;\n}\n\n/**\n * Undo the `border-style: none` reset that Normalize applies to images so that\n * our `border-{width}` utilities have the expected effect.\n *\n * The Normalize reset is unnecessary for us since we default the border-width\n * to 0 on all elements.\n *\n * https://github.com/tailwindcss/tailwindcss/issues/362\n */\n\nimg {\n border-style: solid;\n}\n\ntextarea {\n resize: vertical;\n}\n\ninput::-moz-placeholder, textarea::-moz-placeholder {\n color: #a0aec0;\n}\n\ninput:-ms-input-placeholder, textarea:-ms-input-placeholder {\n color: #a0aec0;\n}\n\ninput::-ms-input-placeholder, textarea::-ms-input-placeholder {\n color: #a0aec0;\n}\n\ninput::placeholder,\ntextarea::placeholder {\n color: #a0aec0;\n}\n\nbutton,\n[role=\"button\"] {\n cursor: pointer;\n}\n\ntable {\n border-collapse: collapse;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n font-size: inherit;\n font-weight: inherit;\n}\n\n/**\n * Reset links to optimize for opt-in styling instead of\n * opt-out.\n */\n\na {\n color: inherit;\n text-decoration: inherit;\n}\n\n/**\n * Reset form element properties that are easy to forget to\n * style explicitly so you don't inadvertently introduce\n * styles that deviate from your design system. These styles\n * supplement a partial reset that is already applied by\n * normalize.css.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n padding: 0;\n line-height: inherit;\n color: inherit;\n}\n\n/**\n * Use the configured 'mono' font family for elements that\n * are expected to be rendered with a monospace font, falling\n * back to the system monospace stack if there is no configured\n * 'mono' font family.\n */\n\npre,\ncode,\nkbd,\nsamp {\n font-family: Fira Code, monospace;\n}\n\n/**\n * Make replaced elements `display: block` by default as that's\n * the behavior you want almost all of the time. Inspired by\n * CSS Remedy, with `svg` added as well.\n *\n * https://github.com/mozdevs/cssremedy/issues/14\n */\n\nimg,\nsvg,\nvideo,\ncanvas,\naudio,\niframe,\nembed,\nobject {\n display: block;\n vertical-align: middle;\n}\n\n/**\n * Constrain images and videos to the parent width and preserve\n * their instrinsic aspect ratio.\n *\n * https://github.com/mozdevs/cssremedy/issues/14\n */\n\nimg,\nvideo {\n max-width: 100%;\n height: auto;\n}\n\n.container {\n width: 100%;\n}\n\n@media (min-width: 640px) {\n .container {\n max-width: 640px;\n }\n}\n\n@media (min-width: 768px) {\n .container {\n max-width: 768px;\n }\n}\n\n@media (min-width: 1024px) {\n .container {\n max-width: 1024px;\n }\n}\n\n@media (min-width: 1280px) {\n .container {\n max-width: 1280px;\n }\n}\n\n.space-x-2 > :not(template) ~ :not(template) {\n --space-x-reverse: 0;\n margin-right: calc(0.5rem * var(--space-x-reverse));\n margin-left: calc(0.5rem * calc(1 - var(--space-x-reverse)));\n}\n\n.divide-y-2 > :not(template) ~ :not(template) {\n --divide-y-reverse: 0;\n border-top-width: calc(2px * calc(1 - var(--divide-y-reverse)));\n border-bottom-width: calc(2px * var(--divide-y-reverse));\n}\n\n.divide-component-divider > :not(template) ~ :not(template) {\n --divide-opacity: 1;\n border-color: #44475a;\n border-color: rgba(68, 71, 90, var(--divide-opacity));\n}\n\n.bg-background {\n --bg-opacity: 1;\n background-color: #282a36;\n background-color: rgba(40, 42, 54, var(--bg-opacity));\n}\n\n.bg-typeof-bg {\n background-color: transparent;\n}\n\n.bg-string-editor-bg {\n --bg-opacity: 1;\n background-color: #44475a;\n background-color: rgba(68, 71, 90, var(--bg-opacity));\n}\n\n.border-container-border {\n border-color: transparent;\n}\n\n.border-component-divider {\n --border-opacity: 1;\n border-color: #44475a;\n border-color: rgba(68, 71, 90, var(--border-opacity));\n}\n\n.border {\n border-width: 1px;\n}\n\n.border-t {\n border-top-width: 1px;\n}\n\n.block {\n display: block;\n}\n\n.inline-block {\n display: inline-block;\n}\n\n.inline {\n display: inline;\n}\n\n.flex {\n display: flex;\n}\n\n.inline-flex {\n display: inline-flex;\n}\n\n.table {\n display: table;\n}\n\n.hidden {\n display: none;\n}\n\n.flex-col {\n flex-direction: column;\n}\n\n.items-start {\n align-items: flex-start;\n}\n\n.items-center {\n align-items: center;\n}\n\n.justify-end {\n justify-content: flex-end;\n}\n\n.justify-between {\n justify-content: space-between;\n}\n\n.font-mono {\n font-family: Fira Code, monospace;\n}\n\n.font-extrabold {\n font-weight: 800;\n}\n\n.text-xs {\n font-size: 0.75rem;\n}\n\n.text-sm {\n font-size: 0.875rem;\n}\n\n.leading-none {\n line-height: 1;\n}\n\n.leading-relaxed {\n line-height: 1.625;\n}\n\n.mb-1 {\n margin-bottom: 0.25rem;\n}\n\n.ml-1 {\n margin-left: 0.25rem;\n}\n\n.mb-5 {\n margin-bottom: 1.25rem;\n}\n\n.mt-px {\n margin-top: 1px;\n}\n\n.-ml-3 {\n margin-left: -0.75rem;\n}\n\n.-mt-5 {\n margin-top: -1.25rem;\n}\n\n.-ml-7 {\n margin-left: -1.75rem;\n}\n\n.-mt-px {\n margin-top: -1px;\n}\n\n.-ml-3\\.5 {\n margin-left: -0.875rem;\n}\n\n.min-h-full {\n min-height: 100%;\n}\n\n.opacity-0 {\n opacity: 0;\n}\n\n.group:hover .group-hover\\:opacity-100 {\n opacity: 1;\n}\n\n.focus\\:outline-none:focus {\n outline: 2px solid transparent;\n outline-offset: 2px;\n}\n\n.overflow-y-hidden {\n overflow-y: hidden;\n}\n\n.p-1 {\n padding: 0.25rem;\n}\n\n.p-2 {\n padding: 0.5rem;\n}\n\n.p-1\\.5 {\n padding: 0.375rem;\n}\n\n.py-1 {\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n}\n\n.py-2 {\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.py-5 {\n padding-top: 1.25rem;\n padding-bottom: 1.25rem;\n}\n\n.pl-1 {\n padding-left: 0.25rem;\n}\n\n.pl-2 {\n padding-left: 0.5rem;\n}\n\n.pl-4 {\n padding-left: 1rem;\n}\n\n.pl-1\\.5 {\n padding-left: 0.375rem;\n}\n\n.fixed {\n position: fixed;\n}\n\n.absolute {\n position: absolute;\n}\n\n.relative {\n position: relative;\n}\n\n.right-0 {\n right: 0;\n}\n\n.bottom-0 {\n bottom: 0;\n}\n\n.left-0 {\n left: 0;\n}\n\n.top-1 {\n top: 0.25rem;\n}\n\n.stroke-current {\n stroke: currentColor;\n}\n\n.text-component-title {\n --text-opacity: 1;\n color: #ff79c6;\n color: rgba(255, 121, 198, var(--text-opacity));\n}\n\n.text-status-text {\n --text-opacity: 1;\n color: #6272a4;\n color: rgba(98, 114, 164, var(--text-opacity));\n}\n\n.text-property-name-color {\n --text-opacity: 1;\n color: #50fa7b;\n color: rgba(80, 250, 123, var(--text-opacity));\n}\n\n.text-property-seperator-color {\n --text-opacity: 1;\n color: #8be9fd;\n color: rgba(139, 233, 253, var(--text-opacity));\n}\n\n.text-typeof-color {\n --text-opacity: 1;\n color: #bd93f9;\n color: rgba(189, 147, 249, var(--text-opacity));\n}\n\n.text-value-color {\n --text-opacity: 1;\n color: #f8f8f2;\n color: rgba(248, 248, 242, var(--text-opacity));\n}\n\n.text-string-editor-color {\n --text-opacity: 1;\n color: #f8f8f2;\n color: rgba(248, 248, 242, var(--text-opacity));\n}\n\n.text-icon-color {\n --text-opacity: 1;\n color: #f1fa8c;\n color: rgba(241, 250, 140, var(--text-opacity));\n}\n\n.hover\\:text-status-text-hover:hover {\n --text-opacity: 1;\n color: #8be9fd;\n color: rgba(139, 233, 253, var(--text-opacity));\n}\n\n.whitespace-no-wrap {\n white-space: nowrap;\n}\n\n.w-4 {\n width: 1rem;\n}\n\n.w-full {\n width: 100%;\n}\n\n.z-10 {\n z-index: 10;\n}\n\n.z-30 {\n z-index: 30;\n}\n\n.z-50 {\n z-index: 50;\n}\n\n.transform {\n --transform-translate-x: 0;\n --transform-translate-y: 0;\n --transform-rotate: 0;\n --transform-skew-x: 0;\n --transform-skew-y: 0;\n --transform-scale-x: 1;\n --transform-scale-y: 1;\n transform: translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y));\n}\n\n.-rotate-90 {\n --transform-rotate: -90deg;\n}\n\n.transition {\n transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;\n}\n\n.duration-200 {\n transition-duration: 200ms;\n}\n\n@-webkit-keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n@-webkit-keyframes ping {\n 75%, 100% {\n transform: scale(2);\n opacity: 0;\n }\n}\n\n@keyframes ping {\n 75%, 100% {\n transform: scale(2);\n opacity: 0;\n }\n}\n\n@-webkit-keyframes pulse {\n 50% {\n opacity: .5;\n }\n}\n\n@keyframes pulse {\n 50% {\n opacity: .5;\n }\n}\n\n@-webkit-keyframes bounce {\n 0%, 100% {\n transform: translateY(-25%);\n -webkit-animation-timing-function: cubic-bezier(0.8,0,1,1);\n animation-timing-function: cubic-bezier(0.8,0,1,1);\n }\n\n 50% {\n transform: none;\n -webkit-animation-timing-function: cubic-bezier(0,0,0.2,1);\n animation-timing-function: cubic-bezier(0,0,0.2,1);\n }\n}\n\n@keyframes bounce {\n 0%, 100% {\n transform: translateY(-25%);\n -webkit-animation-timing-function: cubic-bezier(0.8,0,1,1);\n animation-timing-function: cubic-bezier(0.8,0,1,1);\n }\n\n 50% {\n transform: none;\n -webkit-animation-timing-function: cubic-bezier(0,0,0.2,1);\n animation-timing-function: cubic-bezier(0,0,0.2,1);\n }\n}\n\n@media (min-width: 640px) {\n .sm\\:container {\n width: 100%;\n }\n\n @media (min-width: 640px) {\n .sm\\:container {\n max-width: 640px;\n }\n }\n\n @media (min-width: 768px) {\n .sm\\:container {\n max-width: 768px;\n }\n }\n\n @media (min-width: 1024px) {\n .sm\\:container {\n max-width: 1024px;\n }\n }\n\n @media (min-width: 1280px) {\n .sm\\:container {\n max-width: 1280px;\n }\n }\n}\n\n@media (min-width: 768px) {\n .md\\:container {\n width: 100%;\n }\n\n @media (min-width: 640px) {\n .md\\:container {\n max-width: 640px;\n }\n }\n\n @media (min-width: 768px) {\n .md\\:container {\n max-width: 768px;\n }\n }\n\n @media (min-width: 1024px) {\n .md\\:container {\n max-width: 1024px;\n }\n }\n\n @media (min-width: 1280px) {\n .md\\:container {\n max-width: 1280px;\n }\n }\n}\n\n@media (min-width: 1024px) {\n .lg\\:container {\n width: 100%;\n }\n\n @media (min-width: 640px) {\n .lg\\:container {\n max-width: 640px;\n }\n }\n\n @media (min-width: 768px) {\n .lg\\:container {\n max-width: 768px;\n }\n }\n\n @media (min-width: 1024px) {\n .lg\\:container {\n max-width: 1024px;\n }\n }\n\n @media (min-width: 1280px) {\n .lg\\:container {\n max-width: 1280px;\n }\n }\n}\n\n@media (min-width: 1280px) {\n .xl\\:container {\n width: 100%;\n }\n\n @media (min-width: 640px) {\n .xl\\:container {\n max-width: 640px;\n }\n }\n\n @media (min-width: 768px) {\n .xl\\:container {\n max-width: 768px;\n }\n }\n\n @media (min-width: 1024px) {\n .xl\\:container {\n max-width: 1024px;\n }\n }\n\n @media (min-width: 1280px) {\n .xl\\:container {\n max-width: 1280px;\n }\n }\n}\n"; 657 | styleInject(css_248z); 658 | 659 | window.alpineDevToolsThemeDracula = css_248z; 660 | 661 | const alpine = window.deferLoadingAlpine || (alpine => alpine()); 662 | 663 | window.deferLoadingAlpine = callback => { 664 | alpine(callback); 665 | DevTools.start(Loader, Viewer, css_248z); 666 | }; 667 | 668 | }))); 669 | -------------------------------------------------------------------------------- /dist/Hacktoberfest2020.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | typeof define === 'function' && define.amd ? define(factory) : 3 | factory(); 4 | }((function () { 'use strict'; 5 | 6 | const DevTools = { 7 | start(Loader, Viewer, theme) { 8 | window.alpineDevTools = { 9 | version: '0.11.1', 10 | Viewer: Viewer, 11 | Loader: Loader, 12 | theme: theme 13 | }; 14 | window.addEventListener('DOMContentLoaded', () => { 15 | this.injectDevToolsHandler(); // A button is on the page already. use that instead 16 | 17 | const button = document.getElementById('alpine-devtools-button'); 18 | 19 | if (button) { 20 | button.addEventListener('click', () => { 21 | window.dispatchEvent(new CustomEvent('open-alpine-devtools-popup', { 22 | bubbles: true 23 | })); 24 | }); 25 | } 26 | 27 | if (sessionStorage.getItem('alpine-devtools') !== 'Popup') { 28 | setTimeout(() => { 29 | window.dispatchEvent(new CustomEvent('open-alpine-devtools', { 30 | bubbles: true 31 | })); 32 | }, 0); 33 | } 34 | }); 35 | }, 36 | 37 | injectDevToolsHandler() { 38 | const alpineDevToolsComponent = document.createElement('div'); 39 | alpineDevToolsComponent.id = 'alpine-devtools'; 40 | alpineDevToolsComponent.setAttribute('x-data', 'window.alpineDevTools.Loader(window.alpineDevTools.Viewer, window.alpineDevTools.theme)'); 41 | alpineDevToolsComponent.setAttribute('x-devtools-ignore', ''); 42 | alpineDevToolsComponent.setAttribute('x-on:open-alpine-devtools.window', 'openIframe()'); 43 | alpineDevToolsComponent.setAttribute('x-on:open-alpine-devtools-popup.window', 'openPopup()'); 44 | alpineDevToolsComponent.setAttribute('x-on:focus-alpine-devtools.window', 'focusDevTools()'); 45 | alpineDevToolsComponent.setAttribute('x-on:alpine-devtools-switch-theme.window', 'setTheme($event.detail)'); 46 | alpineDevToolsComponent.setAttribute('x-init', '$nextTick(() => { start() })'); 47 | alpineDevToolsComponent.style.cssText = "display:none!important"; 48 | document.body.appendChild(alpineDevToolsComponent); 49 | } 50 | 51 | }; 52 | 53 | const Loader = function (Viewer, theme) { 54 | return { 55 | alpines: [], 56 | open: false, 57 | observer: null, 58 | windowRef: null, 59 | viewerScript: Viewer, 60 | viewer: null, 61 | theme: theme, 62 | iframe: null, 63 | type: 'Iframe', 64 | sessionId: Date.now() + Math.floor(Math.random() * 1000000), 65 | 66 | start() { 67 | this.alpines = document.querySelectorAll('[x-data]:not([x-devtools-ignore])'); 68 | if (!this.alpines) return; 69 | this.registerAlpines(this.alpines); // If the window is already open, refresh it 70 | 71 | if (sessionStorage.getItem('alpine-devtools')) { 72 | this.type = sessionStorage.getItem('alpine-devtools'); // If viewer is already there, only update 73 | 74 | this.viewer || this.load(); 75 | this.updateAlpines(); 76 | } 77 | }, 78 | 79 | focusDevTools() { 80 | if (!this.windowRef) { 81 | this[`open${this.type}`](); 82 | } 83 | 84 | this.windowRef.focus(); 85 | }, 86 | 87 | registerAlpines(alpines) { 88 | this.observer = new MutationObserver(mutations => { 89 | this.updateAlpines(); 90 | }); 91 | alpines.forEach(alpine => { 92 | // This will trigger a mutation when internal data changes but no visual mutation is observed 93 | alpine.setAttribute('x-bind:data-last-refresh', 'Date.now()'); 94 | this.observer.observe(alpine, { 95 | attributes: true, 96 | childList: true, 97 | characterData: true, 98 | subtree: true 99 | }); 100 | }); 101 | }, 102 | 103 | updateAlpines() { 104 | this.checkIfNewAlpinesWereAddedAndRegisterThem(); 105 | 106 | if (this.windowRef) { 107 | this.windowRef.alpines = this.alpines; 108 | const viewer = this.windowRef.document.querySelector('#alpine-devtools-viewer'); 109 | if (!viewer) return; 110 | if (viewer.__x.$data.editing.length) return; 111 | typeof viewer.__x !== 'undefined' && viewer.__x.updateElements(viewer); 112 | } 113 | }, 114 | 115 | checkIfNewAlpinesWereAddedAndRegisterThem() { 116 | const fresh = [...document.querySelectorAll('[x-data]:not([x-devtools-ignore])')]; 117 | const newAlpines = fresh.filter(alpine => { 118 | return ![...this.alpines].includes(alpine); 119 | }); 120 | 121 | if (newAlpines) { 122 | this.alpines = document.querySelectorAll('[x-data]:not([x-devtools-ignore])'); 123 | this.registerAlpines(newAlpines); 124 | } 125 | }, 126 | 127 | openIframe() { 128 | this.iframe = document.getElementById('alpine-devtools-iframe'); 129 | 130 | if (!this.iframe) { 131 | const state = sessionStorage.getItem('alpine-devtools-iframe-state'); 132 | this.iframe = document.createElement('iframe'); 133 | this.iframe.id = 'alpine-devtools-iframe'; 134 | this.iframe.width = 450; 135 | this.iframe.height = 650; 136 | this.iframe.setAttribute('x-data', state ? state : '{height: 650, margin: "0.75rem"}'); 137 | this.iframe.setAttribute('x-devtools-ignore', ''); 138 | this.iframe.setAttribute('x-on:collapse-devtools.window', 'height = height === 650 ? 25 : 650;margin = margin === "0.75rem" ? "0" : "0.75rem";sessionStorage.setItem("alpine-devtools-iframe-state", `{height: ${height}, margin: "${margin}"}`)'); 139 | this.iframe.setAttribute('x-bind:style', '`position:fixed;box-shadow:0 25px 50px -12px rgba(0,0,0,.25)!important;bottom:0!important;right:0!important;margin:${margin}!important;background-color:#252f3f!important;height: ${height}px!important`'); 140 | document.body.appendChild(this.iframe); 141 | } 142 | 143 | this.windowRef = this.iframe.contentWindow; 144 | this.type = 'Iframe'; 145 | this.load(); 146 | }, 147 | 148 | openPopup() { 149 | if (this.iframe) { 150 | this.iframe.parentNode.removeChild(this.iframe); 151 | this.iframe = null; 152 | } 153 | 154 | this.windowRef = window.open('', 'alpine-devtools', 'width=450, height=650, left=100, top=100'); 155 | this.type = 'Popup'; 156 | this.load(); 157 | }, 158 | 159 | load() { 160 | if (!this.windowRef) { 161 | sessionStorage.removeItem('alpine-devtools'); 162 | this[`open${this.type}`](); 163 | } // Add the reference to the session so we don't need to reopen the popup everytime 164 | 165 | 166 | sessionStorage.setItem('alpine-devtools', this.type); // Starting color. Consider some loading animation 167 | 168 | this.windowRef.document.body.style.backgroundColor = '#1a202c'; 169 | this.windowRef.document.body.style.height = '100%'; 170 | this.windowRef.document.body.innerHTML = ''; 171 | this.windowRef.document.title = 'Alpine DevTools'; 172 | this.windowRef.alpines = this.alpines; // TODO: take in AlpineJS version option 173 | 174 | if (!this.windowRef.document.getElementById('alpine-devtools-script')) { 175 | const alpineScript = this.windowRef.document.createElement('script'); 176 | alpineScript.id = 'alpine-devtools-script'; 177 | const version = window.Alpine.version || '2.x.x'; 178 | alpineScript.src = `https://cdn.jsdelivr.net/gh/alpinejs/alpine@v${version}/dist/alpine.min.js`; 179 | this.windowRef.document.head.appendChild(alpineScript); 180 | } // TODO: This should eventually be settable somehow 181 | 182 | 183 | if (!this.windowRef.document.getElementById('alpine-devtools-font')) { 184 | const font = this.windowRef.document.createElement('link'); 185 | font.id = 'alpine-devtools-font'; 186 | font.rel = 'stylesheet'; 187 | font.href = "https://fonts.googleapis.com/css2?family=Fira+Code&display=swap"; 188 | this.windowRef.document.head.appendChild(font); 189 | } 190 | 191 | this.setTheme(this.theme); // Add the dev tools component function and remove any previous 192 | 193 | const oldScript = this.windowRef.document.getElementById('devtools-script'); 194 | oldScript && oldScript.remove(); 195 | const devtoolsScript = this.windowRef.document.createElement('script'); 196 | devtoolsScript.id = 'devtools-script'; 197 | devtoolsScript.textContent = `window.Viewer = ${this.viewerScript}`; 198 | this.windowRef.document.head.appendChild(devtoolsScript); 199 | this.loadView(); 200 | }, 201 | 202 | setTheme(theme) { 203 | if (!this.windowRef) { 204 | this[`open${this.type}`](); 205 | } 206 | 207 | this.theme = theme ? theme : this.theme; // Add the theme and remove any previous (Can possibly support theme swapping) 208 | 209 | const oldTheme = this.windowRef.document.getElementById('alpine-devtools-theme'); 210 | oldTheme && oldTheme.remove(); 211 | const devtoolsTheme = this.windowRef.document.createElement('style'); 212 | devtoolsTheme.id = 'alpine-devtools-theme'; 213 | devtoolsTheme.textContent = this.theme; 214 | this.windowRef.document.head.appendChild(devtoolsTheme); 215 | }, 216 | 217 | loadView() { 218 | this.viewer = this.windowRef.document.createElement('div'); 219 | this.viewer.id = 'alpine-devtools-viewer'; 220 | this.viewer.setAttribute('x-data', `window.Viewer('${this.type}')`); 221 | this.viewer.setAttribute('x-init', 'setup()'); 222 | this.viewer.setAttribute('x-on:open-alpine-devtools-popup.window', ` 223 | window.parent.dispatchEvent(new CustomEvent('open-alpine-devtools-popup', { 224 | bubbles: true, 225 | event: 'Popup' 226 | }))`); 227 | this.viewer.setAttribute('x-init', 'setup()'); 228 | this.open = true; 229 | window.alpineDevTools.open = true; 230 | setTimeout(() => { 231 | this.windowRef.document.body.appendChild(this.viewer); 232 | }, 500); 233 | this.windowRef.addEventListener('beforeunload', () => { 234 | sessionStorage.removeItem('alpine-devtools'); 235 | this.type === 'popup' && this.windowRef.close(); 236 | this.windowRef = null; 237 | this.open = false; 238 | window.alpineDevTools.open = false; 239 | }); 240 | } 241 | 242 | }; 243 | }; 244 | 245 | const Viewer = function (type) { 246 | return { 247 | editing: '', 248 | type: type, 249 | collapsedArrays: [], 250 | 251 | // TODO: persist this?? 252 | setup() { 253 | this.$el.innerHTML = `
257 | 261 |
263 | 280 |
281 |
287 |
288 |
`; 289 | }, 290 | 291 | computeTitle(alpine) { 292 | return alpine.getAttribute('x-title') || alpine.getAttribute('aria-label') || alpine.getAttribute('x-id') || alpine.id || this.convertFunctionName(alpine.getAttribute('x-data')) || `<${alpine.tagName.toLowerCase()}>`; 293 | }, 294 | 295 | getAlpineData(alpine) { 296 | if (!alpine.__x) return []; 297 | return Object.entries(alpine.__x.getUnobservedData()); 298 | }, 299 | 300 | getType(value) { 301 | // Leave as object for now until we need it 302 | // if (value === null) { 303 | // return 'null' 304 | // } 305 | if (Array.isArray(value)) { 306 | return 'array'; 307 | } 308 | 309 | if (typeof value === 'function') { 310 | return 'function'; 311 | } 312 | 313 | return typeof value; 314 | }, 315 | 316 | updateAlpine(type, alpineIndex, key, value, scope, context = '') { 317 | let scopes = scope.split('.'); // Just focus on the last one 318 | 319 | switch (scopes[scopes.length - 1]) { 320 | case 'boolean': 321 | return this.objectSetDeep(window.alpines[alpineIndex].__x.$data, context, value !== 'true'); 322 | 323 | case 'string': 324 | return this.objectSetDeep(window.alpines[alpineIndex].__x.$data, context, value); 325 | } 326 | }, 327 | 328 | removeItemFromArray(type, alpineIndex, key, value, scope, context = '') { 329 | return this.objectSpliceDeep(window.alpines[alpineIndex].__x.$data, context); 330 | }, 331 | 332 | getItem(key, value, alpineIndex = null, scope = '', context = '') { 333 | const id = Date.now() + Math.floor(Math.random() * 1000000); 334 | const type = this.getType(value); 335 | scope = scope ? `${scope}.${type}` : type; 336 | context = context ? `${context}.${key}` : key; 337 | return ` 338 | 339 | ${this.getGutterAction(id, type, alpineIndex, key, value, scope, context)} 340 | 341 | 342 | : 343 | ${this.getProperyTypeMessage(id, type, alpineIndex, key, value, scope, context)} 344 | 345 | ${this.getGutterExtraRight(id, type, alpineIndex, key, value, scope, context)} 346 | 347 | 350 | 357 | ${this.getValue(id, type, alpineIndex, key, value, scope, context)} 358 | 359 | ${this.getEditField(id, type, alpineIndex, key, value, scope, context)} 360 | 361 | `; 362 | }, 363 | 364 | getItemRowStyling(id, type, alpineIndex, key, value, scope, context = '') { 365 | switch (type) { 366 | case 'string': 367 | return `relative py-1 pl-1.5 flex items-start`; 368 | 369 | case 'array': 370 | return `relative py-1 pl-1.5 inline-block`; 371 | } 372 | 373 | return `relative py-1 pl-1.5 inline-block`; 374 | }, 375 | 376 | getEditField(id, type, alpineIndex, key, value, scope, context = '') { 377 | if (scope.endsWith('array.string') || scope === 'string') { 378 | return ``; 396 | } 397 | 398 | return ''; 399 | }, 400 | 401 | getGutterAction(id, type, alpineIndex, key, value, scope, context = '') { 402 | const wrap = content => `${content}`; 403 | 404 | const wrapTight = content => `${content}`; 405 | 406 | switch (type) { 407 | case 'boolean': 408 | return wrap(` 409 | `); 415 | 416 | case 'string': 417 | if ('string' !== scope && !scope.endsWith('array.string')) return ''; 418 | const deleteButton = ` 419 | `; 428 | const editButton = ` 429 | `; 438 | return scope.endsWith('array.string') ? wrapTight(deleteButton + editButton) : wrap(editButton); 439 | 440 | case 'array': 441 | return wrap(` 442 | `); 459 | 460 | default: 461 | return ''; 462 | } 463 | }, 464 | 465 | getGutterExtraRight(id, type, alpineIndex, key, value, scope, context = '') { 466 | switch (type) { 467 | case 'array': 468 | return ` 469 | 475 | 486 | `; 487 | 488 | case 'string': 489 | if (!scope.endsWith('array.string')) return ''; 490 | return ''; 491 | } 492 | 493 | return ''; 494 | }, 495 | 496 | getProperyTypeMessage(id, type, alpineIndex, key, value, scope) { 497 | let wrap = content => `${content}`; 498 | 499 | switch (type) { 500 | case 'string': 501 | return ''; 502 | 503 | case 'array': 504 | if (this.collapsedArrays.includes(`${alpineIndex}:${key}`)) { 505 | return wrap(`array[${value.length}]`); 506 | } 507 | 508 | return wrap`array`; 509 | } 510 | 511 | return wrap(type); 512 | }, 513 | 514 | openEditorAndSelectText(id, type, alpineIndex, key, value, scope, context) { 515 | // If the editor is already open and they press the edit icon again, commit the edit 516 | if (this.editing === `${alpineIndex}-${key}`) { 517 | this.updateAlpine('string', alpineIndex, key, this.$refs[`editor-${alpineIndex}-${key}`].textContent.trim(), scope, context); 518 | this.editing = ''; 519 | return; 520 | } 521 | 522 | this.editing = `${alpineIndex}-${key}`; 523 | this.$nextTick(() => { 524 | this.editing && this.$refs[`editor-${alpineIndex}-${key}`].focus(); 525 | this.editing && document.execCommand('selectAll', false, null); 526 | }); 527 | }, 528 | 529 | getValue(id, type, alpineIndex, key, value, scope, context = '') { 530 | switch (type) { 531 | case 'function': 532 | return ''; 533 | 534 | case 'boolean': 535 | return value; 536 | 537 | case 'number': 538 | return value; 539 | 540 | case 'string': 541 | if (!['string', 'array.string'].includes(scope)) { 542 | return `"${this.escapeHTML(value)}"`; 543 | } 544 | 545 | return ` 548 | "${this.escapeHTML(value)}" 549 | `; 550 | 551 | case 'array': 552 | if (!value) return value; 553 | return Object.entries(value).map(([...item]) => { 554 | return `
${this.getItem(item[0], item[1], alpineIndex, scope, context)}
`; 555 | }).join(''); 556 | 557 | case 'object': 558 | if (!value) return value; 559 | return Object.entries(value).map(([...item]) => { 560 | return `
${this.getItem(item[0], item[1], alpineIndex, scope, context)}
`; 561 | }).join(''); 562 | } 563 | 564 | return value; 565 | }, 566 | 567 | getStatusMessage() { 568 | if (this.editing.length) { 569 | return 'Press Enter or click away to finish. Press Esc to cancel.'; 570 | } 571 | 572 | return ` 573 | Watching ${window.alpines.length} components... 574 |
575 | 576 | 577 | 578 | 579 | 580 | 581 | 584 |
585 | `; 586 | }, 587 | 588 | convertFunctionName(functionName) { 589 | if (functionName.indexOf('{') === 0) return; 590 | let name = functionName.replace(/\(([^\)]+)\)/, '').replace('()', '').replace(/([A-Z])/g, " $1"); 591 | return name ? name.charAt(0).toUpperCase() + name.slice(1) : ''; 592 | }, 593 | 594 | escapeHTML(html) { 595 | let div = document.createElement('div'); 596 | div.innerText = html; 597 | return div.innerHTML; 598 | }, 599 | 600 | // Borrowed from https://stackoverflow.com/a/54733755/1437789 601 | objectSetDeep(object, path, value) { 602 | path = path.toString().match(/[^.[\]]+/g) || []; // Iterate all of them except the last one 603 | 604 | path.slice(0, -1).reduce((a, currentKey, index) => { 605 | // If the key does not exist or its value is not an object, create/override the key 606 | if (Object(a[currentKey]) !== a[currentKey]) { 607 | // Is the next key a potential array-index? 608 | a[currentKey] = Math.abs(path[index + 1]) >> 0 === +path[index + 1] ? [] // Yes: assign a new array object 609 | : {}; // No: assign a new plain object 610 | } 611 | 612 | return a[currentKey]; 613 | }, object)[path[path.length - 1]] = value; // Finally assign the value to the last key 614 | 615 | return object; 616 | }, 617 | 618 | objectSpliceDeep(object, path) { 619 | path = path.toString().match(/[^.[\]]+/g) || []; 620 | path.slice(0, -1).reduce((a, currentKey, index) => { 621 | return a[currentKey]; 622 | }, object).splice(path[path.length - 1], 1); 623 | return object; 624 | } 625 | 626 | }; 627 | }; 628 | 629 | function styleInject(css, ref) { 630 | if ( ref === void 0 ) ref = {}; 631 | var insertAt = ref.insertAt; 632 | 633 | if (!css || typeof document === 'undefined') { return; } 634 | 635 | var head = document.head || document.getElementsByTagName('head')[0]; 636 | var style = document.createElement('style'); 637 | style.type = 'text/css'; 638 | 639 | if (insertAt === 'top') { 640 | if (head.firstChild) { 641 | head.insertBefore(style, head.firstChild); 642 | } else { 643 | head.appendChild(style); 644 | } 645 | } else { 646 | head.appendChild(style); 647 | } 648 | 649 | if (style.styleSheet) { 650 | style.styleSheet.cssText = css; 651 | } else { 652 | style.appendChild(document.createTextNode(css)); 653 | } 654 | } 655 | 656 | var css_248z = "/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n line-height: 1.15; /* 1 */\n -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n margin: 0;\n}\n\n/**\n * Render the `main` element consistently in IE.\n */\n\nmain {\n display: block;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n/* Grouping content\n ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n box-sizing: content-box; /* 1 */\n height: 0; /* 1 */\n overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n border-bottom: none; /* 1 */\n text-decoration: underline; /* 2 */\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted; /* 2 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\n/* Embedded content\n ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n border-style: none;\n}\n\n/* Forms\n ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n font-family: inherit; /* 1 */\n font-size: 100%; /* 1 */\n line-height: 1.15; /* 1 */\n margin: 0; /* 2 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput { /* 1 */\n overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n border-style: none;\n padding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n * `fieldset` elements in all browsers.\n */\n\nlegend {\n box-sizing: border-box; /* 1 */\n color: inherit; /* 2 */\n display: table; /* 1 */\n max-width: 100%; /* 1 */\n padding: 0; /* 3 */\n white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n box-sizing: border-box; /* 1 */\n padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type=\"search\"] {\n -webkit-appearance: textfield; /* 1 */\n outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n -webkit-appearance: button; /* 1 */\n font: inherit; /* 2 */\n}\n\n/* Interactive\n ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n display: list-item;\n}\n\n/* Misc\n ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n display: none;\n}\n\n/**\n * Manually forked from SUIT CSS Base: https://github.com/suitcss/base\n * A thin layer on top of normalize.css that provides a starting point more\n * suitable for web applications.\n */\n\n/**\n * Removes the default spacing and border for appropriate elements.\n */\n\nblockquote,\ndl,\ndd,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nhr,\nfigure,\np,\npre {\n margin: 0;\n}\n\nbutton {\n background-color: transparent;\n background-image: none;\n}\n\n/**\n * Work around a Firefox/IE bug where the transparent `button` background\n * results in a loss of the default `button` focus styles.\n */\n\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\nfieldset {\n margin: 0;\n padding: 0;\n}\n\nol,\nul {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n/**\n * Tailwind custom reset styles\n */\n\n/**\n * 1. Use the user's configured `sans` font-family (with Tailwind's default\n * sans-serif font stack as a fallback) as a sane default.\n * 2. Use Tailwind's default \"normal\" line-height so the user isn't forced\n * to override it to ensure consistency even when using the default theme.\n */\n\nhtml {\n font-family: system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"; /* 1 */\n line-height: 1.5; /* 2 */\n}\n\n/**\n * 1. Prevent padding and border from affecting element width.\n *\n * We used to set this in the html element and inherit from\n * the parent element for everything else. This caused issues\n * in shadow-dom-enhanced elements like
where the content\n * is wrapped by a div with box-sizing set to `content-box`.\n *\n * https://github.com/mozdevs/cssremedy/issues/4\n *\n *\n * 2. Allow adding a border to an element by just adding a border-width.\n *\n * By default, the way the browser specifies that an element should have no\n * border is by setting it's border-style to `none` in the user-agent\n * stylesheet.\n *\n * In order to easily add borders to elements by just setting the `border-width`\n * property, we change the default border-style for all elements to `solid`, and\n * use border-width to hide them instead. This way our `border` utilities only\n * need to set the `border-width` property instead of the entire `border`\n * shorthand, making our border utilities much more straightforward to compose.\n *\n * https://github.com/tailwindcss/tailwindcss/pull/116\n */\n\n*,\n::before,\n::after {\n box-sizing: border-box; /* 1 */\n border-width: 0; /* 2 */\n border-style: solid; /* 2 */\n border-color: #e2e8f0; /* 2 */\n}\n\n/*\n * Ensure horizontal rules are visible by default\n */\n\nhr {\n border-top-width: 1px;\n}\n\n/**\n * Undo the `border-style: none` reset that Normalize applies to images so that\n * our `border-{width}` utilities have the expected effect.\n *\n * The Normalize reset is unnecessary for us since we default the border-width\n * to 0 on all elements.\n *\n * https://github.com/tailwindcss/tailwindcss/issues/362\n */\n\nimg {\n border-style: solid;\n}\n\ntextarea {\n resize: vertical;\n}\n\ninput::-moz-placeholder, textarea::-moz-placeholder {\n color: #a0aec0;\n}\n\ninput:-ms-input-placeholder, textarea:-ms-input-placeholder {\n color: #a0aec0;\n}\n\ninput::-ms-input-placeholder, textarea::-ms-input-placeholder {\n color: #a0aec0;\n}\n\ninput::placeholder,\ntextarea::placeholder {\n color: #a0aec0;\n}\n\nbutton,\n[role=\"button\"] {\n cursor: pointer;\n}\n\ntable {\n border-collapse: collapse;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n font-size: inherit;\n font-weight: inherit;\n}\n\n/**\n * Reset links to optimize for opt-in styling instead of\n * opt-out.\n */\n\na {\n color: inherit;\n text-decoration: inherit;\n}\n\n/**\n * Reset form element properties that are easy to forget to\n * style explicitly so you don't inadvertently introduce\n * styles that deviate from your design system. These styles\n * supplement a partial reset that is already applied by\n * normalize.css.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n padding: 0;\n line-height: inherit;\n color: inherit;\n}\n\n/**\n * Use the configured 'mono' font family for elements that\n * are expected to be rendered with a monospace font, falling\n * back to the system monospace stack if there is no configured\n * 'mono' font family.\n */\n\npre,\ncode,\nkbd,\nsamp {\n font-family: Fira Code, monospace;\n}\n\n/**\n * Make replaced elements `display: block` by default as that's\n * the behavior you want almost all of the time. Inspired by\n * CSS Remedy, with `svg` added as well.\n *\n * https://github.com/mozdevs/cssremedy/issues/14\n */\n\nimg,\nsvg,\nvideo,\ncanvas,\naudio,\niframe,\nembed,\nobject {\n display: block;\n vertical-align: middle;\n}\n\n/**\n * Constrain images and videos to the parent width and preserve\n * their instrinsic aspect ratio.\n *\n * https://github.com/mozdevs/cssremedy/issues/14\n */\n\nimg,\nvideo {\n max-width: 100%;\n height: auto;\n}\n\n.container {\n width: 100%;\n}\n\n@media (min-width: 640px) {\n .container {\n max-width: 640px;\n }\n}\n\n@media (min-width: 768px) {\n .container {\n max-width: 768px;\n }\n}\n\n@media (min-width: 1024px) {\n .container {\n max-width: 1024px;\n }\n}\n\n@media (min-width: 1280px) {\n .container {\n max-width: 1280px;\n }\n}\n\n.space-x-2 > :not(template) ~ :not(template) {\n --space-x-reverse: 0;\n margin-right: calc(0.5rem * var(--space-x-reverse));\n margin-left: calc(0.5rem * calc(1 - var(--space-x-reverse)));\n}\n\n.divide-y-2 > :not(template) ~ :not(template) {\n --divide-y-reverse: 0;\n border-top-width: calc(2px * calc(1 - var(--divide-y-reverse)));\n border-bottom-width: calc(2px * var(--divide-y-reverse));\n}\n\n.divide-component-divider > :not(template) ~ :not(template) {\n --divide-opacity: 1;\n border-color: #183d5d;\n border-color: rgba(24, 61, 93, var(--divide-opacity));\n}\n\n.bg-background {\n --bg-opacity: 1;\n background-color: #072540;\n background-color: rgba(7, 37, 64, var(--bg-opacity));\n}\n\n.bg-typeof-bg {\n background-color: transparent;\n}\n\n.bg-string-editor-bg {\n --bg-opacity: 1;\n background-color: #183d5d;\n background-color: rgba(24, 61, 93, var(--bg-opacity));\n}\n\n.border-container-border {\n border-color: transparent;\n}\n\n.border-component-divider {\n --border-opacity: 1;\n border-color: #183d5d;\n border-color: rgba(24, 61, 93, var(--border-opacity));\n}\n\n.border {\n border-width: 1px;\n}\n\n.border-t {\n border-top-width: 1px;\n}\n\n.block {\n display: block;\n}\n\n.inline-block {\n display: inline-block;\n}\n\n.inline {\n display: inline;\n}\n\n.flex {\n display: flex;\n}\n\n.inline-flex {\n display: inline-flex;\n}\n\n.table {\n display: table;\n}\n\n.hidden {\n display: none;\n}\n\n.flex-col {\n flex-direction: column;\n}\n\n.items-start {\n align-items: flex-start;\n}\n\n.items-center {\n align-items: center;\n}\n\n.justify-end {\n justify-content: flex-end;\n}\n\n.justify-between {\n justify-content: space-between;\n}\n\n.font-mono {\n font-family: Fira Code, monospace;\n}\n\n.font-extrabold {\n font-weight: 800;\n}\n\n.text-xs {\n font-size: 0.75rem;\n}\n\n.text-sm {\n font-size: 0.875rem;\n}\n\n.leading-none {\n line-height: 1;\n}\n\n.leading-relaxed {\n line-height: 1.625;\n}\n\n.mb-1 {\n margin-bottom: 0.25rem;\n}\n\n.ml-1 {\n margin-left: 0.25rem;\n}\n\n.mb-5 {\n margin-bottom: 1.25rem;\n}\n\n.mt-px {\n margin-top: 1px;\n}\n\n.-ml-3 {\n margin-left: -0.75rem;\n}\n\n.-mt-5 {\n margin-top: -1.25rem;\n}\n\n.-ml-7 {\n margin-left: -1.75rem;\n}\n\n.-mt-px {\n margin-top: -1px;\n}\n\n.-ml-3\\.5 {\n margin-left: -0.875rem;\n}\n\n.min-h-full {\n min-height: 100%;\n}\n\n.opacity-0 {\n opacity: 0;\n}\n\n.group:hover .group-hover\\:opacity-100 {\n opacity: 1;\n}\n\n.focus\\:outline-none:focus {\n outline: 2px solid transparent;\n outline-offset: 2px;\n}\n\n.overflow-y-hidden {\n overflow-y: hidden;\n}\n\n.p-1 {\n padding: 0.25rem;\n}\n\n.p-2 {\n padding: 0.5rem;\n}\n\n.p-1\\.5 {\n padding: 0.375rem;\n}\n\n.py-1 {\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n}\n\n.py-2 {\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.py-5 {\n padding-top: 1.25rem;\n padding-bottom: 1.25rem;\n}\n\n.pl-1 {\n padding-left: 0.25rem;\n}\n\n.pl-2 {\n padding-left: 0.5rem;\n}\n\n.pl-4 {\n padding-left: 1rem;\n}\n\n.pl-1\\.5 {\n padding-left: 0.375rem;\n}\n\n.fixed {\n position: fixed;\n}\n\n.absolute {\n position: absolute;\n}\n\n.relative {\n position: relative;\n}\n\n.right-0 {\n right: 0;\n}\n\n.bottom-0 {\n bottom: 0;\n}\n\n.left-0 {\n left: 0;\n}\n\n.top-1 {\n top: 0.25rem;\n}\n\n.stroke-current {\n stroke: currentColor;\n}\n\n.text-component-title {\n --text-opacity: 1;\n color: #ffffff;\n color: rgba(255, 255, 255, var(--text-opacity));\n}\n\n.text-status-text {\n --text-opacity: 1;\n color: #93c2db;\n color: rgba(147, 194, 219, var(--text-opacity));\n}\n\n.text-property-name-color {\n --text-opacity: 1;\n color: #ff8ae2;\n color: rgba(255, 138, 226, var(--text-opacity));\n}\n\n.text-property-seperator-color {\n --text-opacity: 1;\n color: #0069ff;\n color: rgba(0, 105, 255, var(--text-opacity));\n}\n\n.text-typeof-color {\n --text-opacity: 1;\n color: #93c2db;\n color: rgba(147, 194, 219, var(--text-opacity));\n}\n\n.text-value-color {\n --text-opacity: 1;\n color: #ffffff;\n color: rgba(255, 255, 255, var(--text-opacity));\n}\n\n.text-string-editor-color {\n --text-opacity: 1;\n color: #ffffff;\n color: rgba(255, 255, 255, var(--text-opacity));\n}\n\n.text-icon-color {\n --text-opacity: 1;\n color: #93c2db;\n color: rgba(147, 194, 219, var(--text-opacity));\n}\n\n.hover\\:text-status-text-hover:hover {\n --text-opacity: 1;\n color: #ff8ae2;\n color: rgba(255, 138, 226, var(--text-opacity));\n}\n\n.whitespace-no-wrap {\n white-space: nowrap;\n}\n\n.w-4 {\n width: 1rem;\n}\n\n.w-full {\n width: 100%;\n}\n\n.z-10 {\n z-index: 10;\n}\n\n.z-30 {\n z-index: 30;\n}\n\n.z-50 {\n z-index: 50;\n}\n\n.transform {\n --transform-translate-x: 0;\n --transform-translate-y: 0;\n --transform-rotate: 0;\n --transform-skew-x: 0;\n --transform-skew-y: 0;\n --transform-scale-x: 1;\n --transform-scale-y: 1;\n transform: translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y));\n}\n\n.-rotate-90 {\n --transform-rotate: -90deg;\n}\n\n.transition {\n transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;\n}\n\n.duration-200 {\n transition-duration: 200ms;\n}\n\n@-webkit-keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n@-webkit-keyframes ping {\n 75%, 100% {\n transform: scale(2);\n opacity: 0;\n }\n}\n\n@keyframes ping {\n 75%, 100% {\n transform: scale(2);\n opacity: 0;\n }\n}\n\n@-webkit-keyframes pulse {\n 50% {\n opacity: .5;\n }\n}\n\n@keyframes pulse {\n 50% {\n opacity: .5;\n }\n}\n\n@-webkit-keyframes bounce {\n 0%, 100% {\n transform: translateY(-25%);\n -webkit-animation-timing-function: cubic-bezier(0.8,0,1,1);\n animation-timing-function: cubic-bezier(0.8,0,1,1);\n }\n\n 50% {\n transform: none;\n -webkit-animation-timing-function: cubic-bezier(0,0,0.2,1);\n animation-timing-function: cubic-bezier(0,0,0.2,1);\n }\n}\n\n@keyframes bounce {\n 0%, 100% {\n transform: translateY(-25%);\n -webkit-animation-timing-function: cubic-bezier(0.8,0,1,1);\n animation-timing-function: cubic-bezier(0.8,0,1,1);\n }\n\n 50% {\n transform: none;\n -webkit-animation-timing-function: cubic-bezier(0,0,0.2,1);\n animation-timing-function: cubic-bezier(0,0,0.2,1);\n }\n}\n\n@media (min-width: 640px) {\n .sm\\:container {\n width: 100%;\n }\n\n @media (min-width: 640px) {\n .sm\\:container {\n max-width: 640px;\n }\n }\n\n @media (min-width: 768px) {\n .sm\\:container {\n max-width: 768px;\n }\n }\n\n @media (min-width: 1024px) {\n .sm\\:container {\n max-width: 1024px;\n }\n }\n\n @media (min-width: 1280px) {\n .sm\\:container {\n max-width: 1280px;\n }\n }\n}\n\n@media (min-width: 768px) {\n .md\\:container {\n width: 100%;\n }\n\n @media (min-width: 640px) {\n .md\\:container {\n max-width: 640px;\n }\n }\n\n @media (min-width: 768px) {\n .md\\:container {\n max-width: 768px;\n }\n }\n\n @media (min-width: 1024px) {\n .md\\:container {\n max-width: 1024px;\n }\n }\n\n @media (min-width: 1280px) {\n .md\\:container {\n max-width: 1280px;\n }\n }\n}\n\n@media (min-width: 1024px) {\n .lg\\:container {\n width: 100%;\n }\n\n @media (min-width: 640px) {\n .lg\\:container {\n max-width: 640px;\n }\n }\n\n @media (min-width: 768px) {\n .lg\\:container {\n max-width: 768px;\n }\n }\n\n @media (min-width: 1024px) {\n .lg\\:container {\n max-width: 1024px;\n }\n }\n\n @media (min-width: 1280px) {\n .lg\\:container {\n max-width: 1280px;\n }\n }\n}\n\n@media (min-width: 1280px) {\n .xl\\:container {\n width: 100%;\n }\n\n @media (min-width: 640px) {\n .xl\\:container {\n max-width: 640px;\n }\n }\n\n @media (min-width: 768px) {\n .xl\\:container {\n max-width: 768px;\n }\n }\n\n @media (min-width: 1024px) {\n .xl\\:container {\n max-width: 1024px;\n }\n }\n\n @media (min-width: 1280px) {\n .xl\\:container {\n max-width: 1280px;\n }\n }\n}\n"; 657 | styleInject(css_248z); 658 | 659 | window.alpineDevToolsThemeHacktoberfest2020 = css_248z; 660 | 661 | const alpine = window.deferLoadingAlpine || (alpine => alpine()); 662 | 663 | window.deferLoadingAlpine = callback => { 664 | alpine(callback); 665 | DevTools.start(Loader, Viewer, css_248z); 666 | }; 667 | 668 | }))); 669 | --------------------------------------------------------------------------------