165 |
166 |
167 |
168 |
169 | `;
170 | }
171 | }
172 | ....
173 | Navigate to the nested route
174 | ```
175 |
176 | ## Base URL
177 | Routing only takes place if a url also matches the document.baseURI.
178 |
179 | ```html
180 |
181 |
182 | Wont route
183 | Will route
184 | ```
185 |
186 | # Named outlets (no router or routes required)
187 | ## Routing using named outlets and HTML anchors
188 |
189 | ```html
190 | Please click a link
191 | ....
192 | Assign element to outlet main
193 | Assign element to outlet
194 | ```
195 |
196 | ## Passing data to named outlets
197 |
198 | ```html
199 | Please click a link
200 | ....
201 | Assign to outlet
202 | ```
203 |
204 | ## Code splitting and eager loading modules for named outlets
205 |
206 | ```html
207 | Please click a link
208 | ....
209 | Load from /path-to/user-main.js
210 | Load from /path-to/user-main.js
211 | ```
212 |
213 | # General to Routing and Named Outlets
214 | ## Styling link matching the active routes
215 |
216 | ```html
217 |
222 |
223 | Regular anchor - will route but wont get active status styling
224 | Router link - will route and get active status styling
225 | ```
226 |
227 | ## Navigating using HTML anchors
228 |
229 | ```html
230 | Regular anchor - will route but wont get active status styling
231 | outer link - will route and get active status styling
232 | ```
233 |
234 | ## Navigating using events
235 |
236 | ```js
237 | window.dispatchEvent(
238 | new CustomEvent(
239 | 'navigate', {
240 | detail: {
241 | href: '/(user/123' }}));
242 | ```
243 |
244 | ## Navigating using RouterElement
245 |
246 | ```js
247 | RouterElement.navigate('myUrl');
248 | RouterElement.navigate('/user/123');
249 | ```
250 |
251 | ## Guards
252 |
253 | ```js
254 | window.addEventListener('onRouteLeave', guard);
255 |
256 | guard(event) {
257 | if (document.getElementById('guard').checked) {
258 | // preventDefault to prevent the navigation
259 | event.preventDefault();
260 | }
261 | }
262 | ```
263 |
264 | ## Lifecycle Events
265 | ### onRouterAdded
266 | ### onRouteMatch
267 | ### onRouteLeave
268 | ### onRouteNotHandled
269 | ### onRouteCancelled
270 | ### onLinkActiveStatusUpdated
271 | ### onOutletUpdated
272 |
273 | ## Testing
274 | To run tests:
275 |
276 | npm test
277 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | # Node.js
2 | # Build a general Node.js project with npm.
3 | # Add steps that analyze code, save build artifacts, deploy, and more:
4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
5 |
6 | trigger:
7 | - master
8 |
9 | pool:
10 | vmImage: 'ubuntu-latest'
11 |
12 | steps:
13 | - task: NodeTool@0
14 | inputs:
15 | versionSpec: '10.x'
16 | displayName: 'Install Node.js'
17 |
18 | - script: |
19 | npm install
20 | npm run build
21 | displayName: 'npm install and build'
22 |
23 | - script: |
24 | npm test
25 | displayName: 'npm test'
26 |
--------------------------------------------------------------------------------
/d.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "lib": ["esnext.array", "esnext", "es2017", "dom"],
5 | "rootDir": "./",
6 | "moduleResolution": "node",
7 | "checkJs": true,
8 | "allowJs": true,
9 | "declaration": true,
10 | "emitDeclarationOnly": true
11 | },
12 | "include": [
13 | "src/**/*"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/exampleServer.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 |
3 | var app = express();
4 |
5 | app.use('/a-wc-router/examples', express.static('examples'));
6 | app.use('/a-wc-router/build', express.static('build'));
7 | app.use('/a-wc-router/src', express.static('src'));
8 |
9 | function spaHandler(req, res) {
10 | res.sendFile(`${__dirname}/examples/${req.params.example}/index.html`);
11 | };
12 |
13 | function main(req, res) {
14 | res.sendFile(`${__dirname}/examples/index.html`);
15 | };
16 |
17 | app.get('/a-wc-router/examples/', main);
18 | app.get('/a-wc-router/examples/index.html', main);
19 | app.get('/a-wc-router/examples/:example', spaHandler);
20 | app.get('/a-wc-router/examples/:example/*', spaHandler);
21 |
22 | app.listen(process.env.PORT || 3000);
23 |
24 | console.log(`server started on: http://localhost:${process.env.PORT || 3000}/a-wc-router/examples/`);
--------------------------------------------------------------------------------
/examples/assets/images/import-all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colscott/a-wc-router/62171609bccb476de90d66b066bcd20748038f61/examples/assets/images/import-all.png
--------------------------------------------------------------------------------
/examples/assets/images/import-immediate-fetch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colscott/a-wc-router/62171609bccb476de90d66b066bcd20748038f61/examples/assets/images/import-immediate-fetch.png
--------------------------------------------------------------------------------
/examples/assets/images/import-pre-fetch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colscott/a-wc-router/62171609bccb476de90d66b066bcd20748038f61/examples/assets/images/import-pre-fetch.png
--------------------------------------------------------------------------------
/examples/assets/styles/checkbox.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * # Semantic UI 2.7.2 - Checkbox
3 | * http://github.com/semantic-org/semantic-ui/
4 | *
5 | *
6 | * Released under the MIT license
7 | * http://opensource.org/licenses/MIT
8 | *
9 | */.ui.checkbox{position:relative;display:inline-block;-webkit-backface-visibility:hidden;backface-visibility:hidden;outline:0;vertical-align:baseline;font-style:normal;min-height:17px;font-size:1rem;line-height:17px;min-width:17px}.ui.checkbox input[type=checkbox],.ui.checkbox input[type=radio]{cursor:pointer;position:absolute;top:0;left:0;opacity:0!important;outline:0;z-index:3;width:17px;height:17px}.ui.checkbox .box,.ui.checkbox label{cursor:auto;position:relative;display:block;padding-left:1.85714em;outline:0;font-size:1em}.ui.checkbox .box:before,.ui.checkbox label:before{position:absolute;top:0;left:0;width:17px;height:17px;content:'';background:#fff;border-radius:.21428571rem;-webkit-transition:border .1s ease,opacity .1s ease,-webkit-transform .1s ease,-webkit-box-shadow .1s ease;transition:border .1s ease,opacity .1s ease,-webkit-transform .1s ease,-webkit-box-shadow .1s ease;transition:border .1s ease,opacity .1s ease,transform .1s ease,box-shadow .1s ease;transition:border .1s ease,opacity .1s ease,transform .1s ease,box-shadow .1s ease,-webkit-transform .1s ease,-webkit-box-shadow .1s ease;border:1px solid #d4d4d5}.ui.checkbox .box:after,.ui.checkbox label:after{position:absolute;font-size:14px;top:0;left:0;width:17px;height:17px;text-align:center;opacity:0;color:rgba(0,0,0,.87);-webkit-transition:border .1s ease,opacity .1s ease,-webkit-transform .1s ease,-webkit-box-shadow .1s ease;transition:border .1s ease,opacity .1s ease,-webkit-transform .1s ease,-webkit-box-shadow .1s ease;transition:border .1s ease,opacity .1s ease,transform .1s ease,box-shadow .1s ease;transition:border .1s ease,opacity .1s ease,transform .1s ease,box-shadow .1s ease,-webkit-transform .1s ease,-webkit-box-shadow .1s ease}.ui.checkbox label,.ui.checkbox+label{color:rgba(0,0,0,.87);-webkit-transition:color .1s ease;transition:color .1s ease}.ui.checkbox+label{vertical-align:middle}.ui.checkbox .box:hover::before,.ui.checkbox label:hover::before{background:#fff;border-color:rgba(34,36,38,.35)}.ui.checkbox label:hover,.ui.checkbox+label:hover{color:rgba(0,0,0,.8)}.ui.checkbox .box:active::before,.ui.checkbox label:active::before{background:#f9fafb;border-color:rgba(34,36,38,.35)}.ui.checkbox .box:active::after,.ui.checkbox label:active::after{color:rgba(0,0,0,.95)}.ui.checkbox input:active~label{color:rgba(0,0,0,.95)}.ui.checkbox input:focus~.box:before,.ui.checkbox input:focus~label:before{background:#fff;border-color:#96c8da}.ui.checkbox input:focus~.box:after,.ui.checkbox input:focus~label:after{color:rgba(0,0,0,.95)}.ui.checkbox input:focus~label{color:rgba(0,0,0,.95)}.ui.checkbox input:checked~.box:before,.ui.checkbox input:checked~label:before{background:#fff;border-color:rgba(34,36,38,.35)}.ui.checkbox input:checked~.box:after,.ui.checkbox input:checked~label:after{opacity:1;color:rgba(0,0,0,.95)}.ui.checkbox input:not([type=radio]):indeterminate~.box:before,.ui.checkbox input:not([type=radio]):indeterminate~label:before{background:#fff;border-color:rgba(34,36,38,.35)}.ui.checkbox input:not([type=radio]):indeterminate~.box:after,.ui.checkbox input:not([type=radio]):indeterminate~label:after{opacity:1;color:rgba(0,0,0,.95)}.ui.checkbox input:checked:focus~.box:before,.ui.checkbox input:checked:focus~label:before,.ui.checkbox input:not([type=radio]):indeterminate:focus~.box:before,.ui.checkbox input:not([type=radio]):indeterminate:focus~label:before{background:#fff;border-color:#96c8da}.ui.checkbox input:checked:focus~.box:after,.ui.checkbox input:checked:focus~label:after,.ui.checkbox input:not([type=radio]):indeterminate:focus~.box:after,.ui.checkbox input:not([type=radio]):indeterminate:focus~label:after{color:rgba(0,0,0,.95)}.ui.read-only.checkbox,.ui.read-only.checkbox label{cursor:default}.ui.checkbox input[disabled]~.box:after,.ui.checkbox input[disabled]~label,.ui.disabled.checkbox .box:after,.ui.disabled.checkbox label{cursor:default!important;opacity:.5;color:#000}.ui.checkbox input.hidden{z-index:-1}.ui.checkbox input.hidden+label{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ui.radio.checkbox{min-height:15px}.ui.radio.checkbox .box,.ui.radio.checkbox label{padding-left:1.85714em}.ui.radio.checkbox .box:before,.ui.radio.checkbox label:before{content:'';-webkit-transform:none;transform:none;width:15px;height:15px;border-radius:500rem;top:1px;left:0}.ui.radio.checkbox .box:after,.ui.radio.checkbox label:after{border:none;content:''!important;width:15px;height:15px;line-height:15px}.ui.radio.checkbox .box:after,.ui.radio.checkbox label:after{top:1px;left:0;width:15px;height:15px;border-radius:500rem;-webkit-transform:scale(.46666667);transform:scale(.46666667);background-color:rgba(0,0,0,.87)}.ui.radio.checkbox input:focus~.box:before,.ui.radio.checkbox input:focus~label:before{background-color:#fff}.ui.radio.checkbox input:focus~.box:after,.ui.radio.checkbox input:focus~label:after{background-color:rgba(0,0,0,.95)}.ui.radio.checkbox input:indeterminate~.box:after,.ui.radio.checkbox input:indeterminate~label:after{opacity:0}.ui.radio.checkbox input:checked~.box:before,.ui.radio.checkbox input:checked~label:before{background-color:#fff}.ui.radio.checkbox input:checked~.box:after,.ui.radio.checkbox input:checked~label:after{background-color:rgba(0,0,0,.95)}.ui.radio.checkbox input:focus:checked~.box:before,.ui.radio.checkbox input:focus:checked~label:before{background-color:#fff}.ui.radio.checkbox input:focus:checked~.box:after,.ui.radio.checkbox input:focus:checked~label:after{background-color:rgba(0,0,0,.95)}.ui.slider.checkbox{min-height:1.25rem}.ui.slider.checkbox input{width:3.5rem;height:1.25rem}.ui.slider.checkbox .box,.ui.slider.checkbox label{padding-left:4.5rem;line-height:1rem;color:rgba(0,0,0,.4)}.ui.slider.checkbox .box:before,.ui.slider.checkbox label:before{display:block;position:absolute;content:'';-webkit-transform:none;transform:none;border:none!important;left:0;z-index:1;top:.4rem;background-color:rgba(0,0,0,.05);width:3.5rem;height:.21428571rem;border-radius:500rem;-webkit-transition:background .3s ease;transition:background .3s ease}.ui.slider.checkbox .box:after,.ui.slider.checkbox label:after{background:#fff -webkit-gradient(linear,left top,left bottom,from(transparent),to(rgba(0,0,0,.05)));background:#fff -webkit-linear-gradient(transparent,rgba(0,0,0,.05));background:#fff linear-gradient(transparent,rgba(0,0,0,.05));position:absolute;content:''!important;opacity:1;z-index:2;border:none;-webkit-box-shadow:0 1px 2px 0 rgba(34,36,38,.15),0 0 0 1px rgba(34,36,38,.15) inset;box-shadow:0 1px 2px 0 rgba(34,36,38,.15),0 0 0 1px rgba(34,36,38,.15) inset;width:1.5rem;height:1.5rem;top:-.25rem;left:0;-webkit-transform:none;transform:none;border-radius:500rem;-webkit-transition:left .3s ease;transition:left .3s ease}.ui.slider.checkbox input:focus~.box:before,.ui.slider.checkbox input:focus~label:before{background-color:rgba(0,0,0,.15);border:none}.ui.slider.checkbox .box:hover,.ui.slider.checkbox label:hover{color:rgba(0,0,0,.8)}.ui.slider.checkbox .box:hover::before,.ui.slider.checkbox label:hover::before{background:rgba(0,0,0,.15)}.ui.slider.checkbox input:checked~.box,.ui.slider.checkbox input:checked~label{color:rgba(0,0,0,.95)!important}.ui.slider.checkbox input:checked~.box:before,.ui.slider.checkbox input:checked~label:before{background-color:#545454!important}.ui.slider.checkbox input:checked~.box:after,.ui.slider.checkbox input:checked~label:after{left:2rem}.ui.slider.checkbox input:focus:checked~.box,.ui.slider.checkbox input:focus:checked~label{color:rgba(0,0,0,.95)!important}.ui.slider.checkbox input:focus:checked~.box:before,.ui.slider.checkbox input:focus:checked~label:before{background-color:#000!important}.ui.toggle.checkbox{min-height:1.5rem}.ui.toggle.checkbox input{width:3.5rem;height:1.5rem}.ui.toggle.checkbox .box,.ui.toggle.checkbox label{min-height:1.5rem;padding-left:4.5rem;color:rgba(0,0,0,.87)}.ui.toggle.checkbox label{padding-top:.15em}.ui.toggle.checkbox .box:before,.ui.toggle.checkbox label:before{display:block;position:absolute;content:'';z-index:1;-webkit-transform:none;transform:none;border:none;top:0;background:rgba(0,0,0,.05);-webkit-box-shadow:none;box-shadow:none;width:3.5rem;height:1.5rem;border-radius:500rem}.ui.toggle.checkbox .box:after,.ui.toggle.checkbox label:after{background:#fff -webkit-gradient(linear,left top,left bottom,from(transparent),to(rgba(0,0,0,.05)));background:#fff -webkit-linear-gradient(transparent,rgba(0,0,0,.05));background:#fff linear-gradient(transparent,rgba(0,0,0,.05));position:absolute;content:''!important;opacity:1;z-index:2;border:none;-webkit-box-shadow:0 1px 2px 0 rgba(34,36,38,.15),0 0 0 1px rgba(34,36,38,.15) inset;box-shadow:0 1px 2px 0 rgba(34,36,38,.15),0 0 0 1px rgba(34,36,38,.15) inset;width:1.5rem;height:1.5rem;top:0;left:0;border-radius:500rem;-webkit-transition:background .3s ease,left .3s ease;transition:background .3s ease,left .3s ease}.ui.toggle.checkbox input~.box:after,.ui.toggle.checkbox input~label:after{left:-.05rem;-webkit-box-shadow:0 1px 2px 0 rgba(34,36,38,.15),0 0 0 1px rgba(34,36,38,.15) inset;box-shadow:0 1px 2px 0 rgba(34,36,38,.15),0 0 0 1px rgba(34,36,38,.15) inset}.ui.toggle.checkbox input:focus~.box:before,.ui.toggle.checkbox input:focus~label:before{background-color:rgba(0,0,0,.15);border:none}.ui.toggle.checkbox .box:hover::before,.ui.toggle.checkbox label:hover::before{background-color:rgba(0,0,0,.15);border:none}.ui.toggle.checkbox input:checked~.box,.ui.toggle.checkbox input:checked~label{color:rgba(0,0,0,.95)!important}.ui.toggle.checkbox input:checked~.box:before,.ui.toggle.checkbox input:checked~label:before{background-color:#2185d0!important}.ui.toggle.checkbox input:checked~.box:after,.ui.toggle.checkbox input:checked~label:after{left:2.15rem;-webkit-box-shadow:0 1px 2px 0 rgba(34,36,38,.15),0 0 0 1px rgba(34,36,38,.15) inset;box-shadow:0 1px 2px 0 rgba(34,36,38,.15),0 0 0 1px rgba(34,36,38,.15) inset}.ui.toggle.checkbox input:focus:checked~.box,.ui.toggle.checkbox input:focus:checked~label{color:rgba(0,0,0,.95)!important}.ui.toggle.checkbox input:focus:checked~.box:before,.ui.toggle.checkbox input:focus:checked~label:before{background-color:#0d71bb!important}.ui.fitted.checkbox .box,.ui.fitted.checkbox label{padding-left:0!important}.ui.fitted.toggle.checkbox{width:3.5rem}.ui.fitted.slider.checkbox{width:3.5rem}.ui.inverted.checkbox label,.ui.inverted.checkbox+label{color:rgba(255,255,255,.9)!important}.ui.inverted.checkbox .box:hover,.ui.inverted.checkbox label:hover{color:#fff!important}.ui.inverted.checkbox .box:hover::before,.ui.inverted.checkbox label:hover::before{border-color:rgba(34,36,38,.5)}.ui.inverted.slider.checkbox .box,.ui.inverted.slider.checkbox label{color:rgba(255,255,255,.5)}.ui.inverted.slider.checkbox .box:before,.ui.inverted.slider.checkbox label:before{background-color:rgba(255,255,255,.5)!important}.ui.inverted.slider.checkbox .box:hover::before,.ui.inverted.slider.checkbox label:hover::before{background:rgba(255,255,255,.7)!important}.ui.inverted.slider.checkbox input:checked~.box,.ui.inverted.slider.checkbox input:checked~label{color:#fff!important}.ui.inverted.slider.checkbox input:checked~.box:before,.ui.inverted.slider.checkbox input:checked~label:before{background-color:rgba(255,255,255,.8)!important}.ui.inverted.slider.checkbox input:focus:checked~.box,.ui.inverted.slider.checkbox input:focus:checked~label{color:#fff!important}.ui.inverted.slider.checkbox input:focus:checked~.box:before,.ui.inverted.slider.checkbox input:focus:checked~label:before{background-color:rgba(255,255,255,.8)!important}.ui.inverted.toggle.checkbox .box:before,.ui.inverted.toggle.checkbox label:before{background-color:rgba(255,255,255,.9)!important}.ui.inverted.toggle.checkbox .box:hover::before,.ui.inverted.toggle.checkbox label:hover::before{background:#fff!important}.ui.inverted.toggle.checkbox input:checked~.box,.ui.inverted.toggle.checkbox input:checked~label{color:#fff!important}.ui.inverted.toggle.checkbox input:checked~.box:before,.ui.inverted.toggle.checkbox input:checked~label:before{background-color:#2185d0!important}.ui.inverted.toggle.checkbox input:focus:checked~.box,.ui.inverted.toggle.checkbox input:focus:checked~label{color:#fff!important}.ui.inverted.toggle.checkbox input:focus:checked~.box:before,.ui.inverted.toggle.checkbox input:focus:checked~label:before{background-color:#0d71bb!important}@font-face{font-family:Checkbox;src:url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBD8AAAC8AAAAYGNtYXAYVtCJAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5Zn4huwUAAAF4AAABYGhlYWQGPe1ZAAAC2AAAADZoaGVhB30DyAAAAxAAAAAkaG10eBBKAEUAAAM0AAAAHGxvY2EAmgESAAADUAAAABBtYXhwAAkALwAAA2AAAAAgbmFtZSC8IugAAAOAAAABknBvc3QAAwAAAAAFFAAAACAAAwMTAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADoAgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6AL//f//AAAAAAAg6AD//f//AAH/4xgEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAEUAUQO7AvgAGgAAARQHAQYjIicBJjU0PwE2MzIfAQE2MzIfARYVA7sQ/hQQFhcQ/uMQEE4QFxcQqAF2EBcXEE4QAnMWEP4UEBABHRAXFhBOEBCoAXcQEE4QFwAAAAABAAABbgMlAkkAFAAAARUUBwYjISInJj0BNDc2MyEyFxYVAyUQEBf9SRcQEBAQFwK3FxAQAhJtFxAQEBAXbRcQEBAQFwAAAAABAAAASQMlA24ALAAAARUUBwYrARUUBwYrASInJj0BIyInJj0BNDc2OwE1NDc2OwEyFxYdATMyFxYVAyUQEBfuEBAXbhYQEO4XEBAQEBfuEBAWbhcQEO4XEBACEm0XEBDuFxAQEBAX7hAQF20XEBDuFxAQEBAX7hAQFwAAAQAAAAIAAHRSzT9fDzz1AAsEAAAAAADRsdR3AAAAANGx1HcAAAAAA7sDbgAAAAgAAgAAAAAAAAABAAADwP/AAAAEAAAAAAADuwABAAAAAAAAAAAAAAAAAAAABwQAAAAAAAAAAAAAAAIAAAAEAABFAyUAAAMlAAAAAAAAAAoAFAAeAE4AcgCwAAEAAAAHAC0AAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAIAAAAAQAAAAAAAgAHAGkAAQAAAAAAAwAIADkAAQAAAAAABAAIAH4AAQAAAAAABQALABgAAQAAAAAABgAIAFEAAQAAAAAACgAaAJYAAwABBAkAAQAQAAgAAwABBAkAAgAOAHAAAwABBAkAAwAQAEEAAwABBAkABAAQAIYAAwABBAkABQAWACMAAwABBAkABgAQAFkAAwABBAkACgA0ALBDaGVja2JveABDAGgAZQBjAGsAYgBvAHhWZXJzaW9uIDIuMABWAGUAcgBzAGkAbwBuACAAMgAuADBDaGVja2JveABDAGgAZQBjAGsAYgBvAHhDaGVja2JveABDAGgAZQBjAGsAYgBvAHhSZWd1bGFyAFIAZQBnAHUAbABhAHJDaGVja2JveABDAGgAZQBjAGsAYgBvAHhGb250IGdlbmVyYXRlZCBieSBJY29Nb29uLgBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('truetype')}.ui.checkbox .box:after,.ui.checkbox label:after{font-family:Checkbox}.ui.checkbox input:checked~.box:after,.ui.checkbox input:checked~label:after{content:'\e800'}.ui.checkbox input:indeterminate~.box:after,.ui.checkbox input:indeterminate~label:after{font-size:12px;content:'\e801'}
--------------------------------------------------------------------------------
/examples/assets/styles/header.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * # Semantic UI 2.7.2 - Header
3 | * http://github.com/semantic-org/semantic-ui/
4 | *
5 | *
6 | * Released under the MIT license
7 | * http://opensource.org/licenses/MIT
8 | *
9 | */.ui.header{border:none;margin:calc(2rem - .1428571428571429em) 0 1rem;padding:0 0;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-weight:700;line-height:1.28571429em;text-transform:none;color:rgba(0,0,0,.87)}.ui.header:first-child{margin-top:-.14285714em}.ui.header:last-child{margin-bottom:0}.ui.header .sub.header{display:block;font-weight:400;padding:0;margin:0;font-size:1rem;line-height:1.2em;color:rgba(0,0,0,.6)}.ui.header>.icon{display:table-cell;opacity:1;font-size:1.5em;padding-top:0;vertical-align:middle}.ui.header .icon:only-child{display:inline-block;padding:0;margin-right:.75rem}.ui.header>.image:not(.icon),.ui.header>img{display:inline-block;margin-top:.14285714em;width:2.5em;height:auto;vertical-align:middle}.ui.header>.image:not(.icon):only-child,.ui.header>img:only-child{margin-right:.75rem}.ui.header .content{display:inline-block;vertical-align:top}.ui.header>.image+.content,.ui.header>img+.content{padding-left:.75rem;vertical-align:middle}.ui.header>.icon+.content{padding-left:.75rem;display:table-cell;vertical-align:middle}.ui.header .ui.label{font-size:'';margin-left:.5rem;vertical-align:middle}.ui.header+p{margin-top:0}h1.ui.header{font-size:2rem}h2.ui.header{font-size:1.71428571rem}h3.ui.header{font-size:1.28571429rem}h4.ui.header{font-size:1.07142857rem}h5.ui.header{font-size:1rem}h1.ui.header .sub.header{font-size:1.14285714rem}h2.ui.header .sub.header{font-size:1.14285714rem}h3.ui.header .sub.header{font-size:1rem}h4.ui.header .sub.header{font-size:1rem}h5.ui.header .sub.header{font-size:.92857143rem}.ui.huge.header{min-height:1em;font-size:2em}.ui.large.header{font-size:1.71428571em}.ui.medium.header{font-size:1.28571429em}.ui.small.header{font-size:1.07142857em}.ui.tiny.header{font-size:1em}.ui.huge.header .sub.header{font-size:1.14285714rem}.ui.large.header .sub.header{font-size:1.14285714rem}.ui.header .sub.header{font-size:1rem}.ui.small.header .sub.header{font-size:1rem}.ui.tiny.header .sub.header{font-size:.92857143rem}.ui.sub.header{padding:0;margin-bottom:.14285714rem;font-weight:700;font-size:.85714286em;text-transform:uppercase;color:''}.ui.small.sub.header{font-size:.78571429em}.ui.large.sub.header{font-size:.92857143em}.ui.huge.sub.header{font-size:1em}.ui.icon.header{display:inline-block;text-align:center;margin:2rem 0 1rem}.ui.icon.header:after{content:'';display:block;height:0;clear:both;visibility:hidden}.ui.icon.header:first-child{margin-top:0}.ui.icon.header .icon{float:none;display:block;width:auto;height:auto;line-height:1;padding:0;font-size:3em;margin:0 auto .5rem;opacity:1}.ui.icon.header .corner.icon{font-size:calc(3em * .45)}.ui.icon.header .content{display:block;padding:0}.ui.icon.header .circular.icon{font-size:2em}.ui.icon.header .square.icon{font-size:2em}.ui.block.icon.header .icon{margin-bottom:0}.ui.icon.header.aligned{margin-left:auto;margin-right:auto;display:block}.ui.disabled.header{opacity:.45}.ui.inverted.header{color:#fff}.ui.inverted.header .sub.header{color:rgba(255,255,255,.8)}.ui.inverted.attached.header{background:#1b1c1d;-webkit-box-shadow:none;box-shadow:none;border-color:transparent}.ui.inverted.block.header{background:#545454 -webkit-gradient(linear,left top,left bottom,from(transparent),to(rgba(0,0,0,.05)));background:#545454 -webkit-linear-gradient(transparent,rgba(0,0,0,.05));background:#545454 linear-gradient(transparent,rgba(0,0,0,.05));-webkit-box-shadow:none;box-shadow:none;border-bottom:none}.ui.primary.header{color:#2185d0}a.ui.primary.header:hover{color:#1678c2}.ui.primary.dividing.header{border-bottom:2px solid #2185d0}.ui.inverted.primary.header.header.header{color:#54c8ff}a.ui.inverted.primary.header.header.header:hover{color:#21b8ff}.ui.inverted.primary.dividing.header{border-bottom:2px solid #54c8ff}.ui.secondary.header{color:#1b1c1d}a.ui.secondary.header:hover{color:#27292a}.ui.secondary.dividing.header{border-bottom:2px solid #1b1c1d}.ui.inverted.secondary.header.header.header{color:#545454}a.ui.inverted.secondary.header.header.header:hover{color:#6e6e6e}.ui.inverted.secondary.dividing.header{border-bottom:2px solid #545454}.ui.red.header{color:#db2828}a.ui.red.header:hover{color:#d01919}.ui.red.dividing.header{border-bottom:2px solid #db2828}.ui.inverted.red.header.header.header{color:#ff695e}a.ui.inverted.red.header.header.header:hover{color:#ff392b}.ui.inverted.red.dividing.header{border-bottom:2px solid #ff695e}.ui.orange.header{color:#f2711c}a.ui.orange.header:hover{color:#f26202}.ui.orange.dividing.header{border-bottom:2px solid #f2711c}.ui.inverted.orange.header.header.header{color:#ff851b}a.ui.inverted.orange.header.header.header:hover{color:#e76b00}.ui.inverted.orange.dividing.header{border-bottom:2px solid #ff851b}.ui.yellow.header{color:#fbbd08}a.ui.yellow.header:hover{color:#eaae00}.ui.yellow.dividing.header{border-bottom:2px solid #fbbd08}.ui.inverted.yellow.header.header.header{color:#ffe21f}a.ui.inverted.yellow.header.header.header:hover{color:#ebcd00}.ui.inverted.yellow.dividing.header{border-bottom:2px solid #ffe21f}.ui.olive.header{color:#b5cc18}a.ui.olive.header:hover{color:#a7bd0d}.ui.olive.dividing.header{border-bottom:2px solid #b5cc18}.ui.inverted.olive.header.header.header{color:#d9e778}a.ui.inverted.olive.header.header.header:hover{color:#d2e745}.ui.inverted.olive.dividing.header{border-bottom:2px solid #d9e778}.ui.green.header{color:#21ba45}a.ui.green.header:hover{color:#16ab39}.ui.green.dividing.header{border-bottom:2px solid #21ba45}.ui.inverted.green.header.header.header{color:#2ecc40}a.ui.inverted.green.header.header.header:hover{color:#1ea92e}.ui.inverted.green.dividing.header{border-bottom:2px solid #2ecc40}.ui.teal.header{color:#00b5ad}a.ui.teal.header:hover{color:#009c95}.ui.teal.dividing.header{border-bottom:2px solid #00b5ad}.ui.inverted.teal.header.header.header{color:#6dffff}a.ui.inverted.teal.header.header.header:hover{color:#3affff}.ui.inverted.teal.dividing.header{border-bottom:2px solid #6dffff}.ui.blue.header{color:#2185d0}a.ui.blue.header:hover{color:#1678c2}.ui.blue.dividing.header{border-bottom:2px solid #2185d0}.ui.inverted.blue.header.header.header{color:#54c8ff}a.ui.inverted.blue.header.header.header:hover{color:#21b8ff}.ui.inverted.blue.dividing.header{border-bottom:2px solid #54c8ff}.ui.violet.header{color:#6435c9}a.ui.violet.header:hover{color:#5829bb}.ui.violet.dividing.header{border-bottom:2px solid #6435c9}.ui.inverted.violet.header.header.header{color:#a291fb}a.ui.inverted.violet.header.header.header:hover{color:#745aff}.ui.inverted.violet.dividing.header{border-bottom:2px solid #a291fb}.ui.purple.header{color:#a333c8}a.ui.purple.header:hover{color:#9627ba}.ui.purple.dividing.header{border-bottom:2px solid #a333c8}.ui.inverted.purple.header.header.header{color:#dc73ff}a.ui.inverted.purple.header.header.header:hover{color:#cf40ff}.ui.inverted.purple.dividing.header{border-bottom:2px solid #dc73ff}.ui.pink.header{color:#e03997}a.ui.pink.header:hover{color:#e61a8d}.ui.pink.dividing.header{border-bottom:2px solid #e03997}.ui.inverted.pink.header.header.header{color:#ff8edf}a.ui.inverted.pink.header.header.header:hover{color:#ff5bd1}.ui.inverted.pink.dividing.header{border-bottom:2px solid #ff8edf}.ui.brown.header{color:#a5673f}a.ui.brown.header:hover{color:#975b33}.ui.brown.dividing.header{border-bottom:2px solid #a5673f}.ui.inverted.brown.header.header.header{color:#d67c1c}a.ui.inverted.brown.header.header.header:hover{color:#b0620f}.ui.inverted.brown.dividing.header{border-bottom:2px solid #d67c1c}.ui.grey.header{color:#767676}a.ui.grey.header:hover{color:#838383}.ui.grey.dividing.header{border-bottom:2px solid #767676}.ui.inverted.grey.header.header.header{color:#dcddde}a.ui.inverted.grey.header.header.header:hover{color:#c2c4c5}.ui.inverted.grey.dividing.header{border-bottom:2px solid #dcddde}.ui.black.header{color:#1b1c1d}a.ui.black.header:hover{color:#27292a}.ui.black.dividing.header{border-bottom:2px solid #1b1c1d}.ui.inverted.black.header.header.header{color:#545454}a.ui.inverted.black.header.header.header:hover{color:#000}.ui.inverted.black.dividing.header{border-bottom:2px solid #545454}.ui.left.aligned.header{text-align:left}.ui.right.aligned.header{text-align:right}.ui.center.aligned.header,.ui.centered.header{text-align:center}.ui.justified.header{text-align:justify}.ui.justified.header:after{display:inline-block;content:'';width:100%}.ui.floated.header,.ui[class*="left floated"].header{float:left;margin-top:0;margin-right:.5em}.ui[class*="right floated"].header{float:right;margin-top:0;margin-left:.5em}.ui.fitted.header{padding:0}.ui.dividing.header{padding-bottom:.21428571rem;border-bottom:1px solid rgba(34,36,38,.15)}.ui.dividing.header .sub.header{padding-bottom:.21428571rem}.ui.dividing.header .icon{margin-bottom:0}.ui.inverted.dividing.header{border-bottom-color:rgba(255,255,255,.1)}.ui.block.header{background:#f3f4f5;padding:.78571429rem 1rem;-webkit-box-shadow:none;box-shadow:none;border:1px solid #d4d4d5;border-radius:.28571429rem}.ui.tiny.block.header{font-size:.85714286rem}.ui.small.block.header{font-size:.92857143rem}.ui.block.header:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6){font-size:1rem}.ui.large.block.header{font-size:1.14285714rem}.ui.huge.block.header{font-size:1.42857143rem}.ui.attached.header{background:#fff;padding:.78571429rem 1rem;margin:0 -1px 0 -1px;-webkit-box-shadow:none;box-shadow:none;border:1px solid #d4d4d5;border-radius:0}.ui.attached.block.header{background:#f3f4f5}.ui.attached:not(.top).header{border-top:none}.ui.top.attached.header{border-radius:.28571429rem .28571429rem 0 0}.ui.bottom.attached.header{border-radius:0 0 .28571429rem .28571429rem}.ui.tiny.attached.header{font-size:.85714286em}.ui.small.attached.header{font-size:.92857143em}.ui.attached.header:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6){font-size:1em}.ui.large.attached.header{font-size:1.14285714em}.ui.huge.attached.header{font-size:1.42857143em}.ui.header:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6){font-size:1.28571429em}
--------------------------------------------------------------------------------
/examples/assets/styles/item.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * # Semantic UI 2.7.2 - Item
3 | * http://github.com/semantic-org/semantic-ui/
4 | *
5 | *
6 | * Released under the MIT license
7 | * http://opensource.org/licenses/MIT
8 | *
9 | */.ui.items>.item{display:-webkit-box;display:-ms-flexbox;display:flex;margin:1em 0;width:100%;min-height:0;background:0 0;padding:0;border:none;border-radius:0;-webkit-box-shadow:none;box-shadow:none;-webkit-transition:-webkit-box-shadow .1s ease;transition:-webkit-box-shadow .1s ease;transition:box-shadow .1s ease;transition:box-shadow .1s ease,-webkit-box-shadow .1s ease;z-index:''}.ui.items>.item a{cursor:pointer}.ui.items{margin:1.5em 0}.ui.items:first-child{margin-top:0!important}.ui.items:last-child{margin-bottom:0!important}.ui.items>.item:after{display:block;content:' ';height:0;clear:both;overflow:hidden;visibility:hidden}.ui.items>.item:first-child{margin-top:0}.ui.items>.item:last-child{margin-bottom:0}.ui.items>.item>.image{position:relative;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;display:block;float:none;margin:0;padding:0;max-height:'';-ms-flex-item-align:start;align-self:start}.ui.items>.item>.image>img{display:block;width:100%;height:auto;border-radius:.125rem;border:none}.ui.items>.item>.image:only-child>img{border-radius:0}.ui.items>.item>.content{display:block;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;background:0 0;color:rgba(0,0,0,.87);margin:0;padding:0;-webkit-box-shadow:none;box-shadow:none;font-size:1em;border:none;border-radius:0}.ui.items>.item>.content:after{display:block;content:' ';height:0;clear:both;overflow:hidden;visibility:hidden}.ui.items>.item>.image+.content{min-width:0;width:auto;display:block;margin-left:0;-ms-flex-item-align:start;align-self:start;padding-left:1.5em}.ui.items>.item>.content>.header{display:inline-block;margin:-.21425em 0 0;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-weight:700;color:rgba(0,0,0,.85)}.ui.items>.item>.content>.header:not(.ui){font-size:1.28571429em}.ui.items>.item [class*="left floated"]{float:left}.ui.items>.item [class*="right floated"]{float:right}.ui.items>.item .content img{-ms-flex-item-align:center;align-self:center;width:''}.ui.items>.item .avatar img,.ui.items>.item img.avatar{width:'';height:'';border-radius:500rem}.ui.items>.item>.content>.description{margin-top:.6em;max-width:auto;font-size:1em;line-height:1.4285em;color:rgba(0,0,0,.87)}.ui.items>.item>.content p{margin:0 0 .5em}.ui.items>.item>.content p:last-child{margin-bottom:0}.ui.items>.item .meta{margin:.5em 0 .5em;font-size:1em;line-height:1em;color:rgba(0,0,0,.6)}.ui.items>.item .meta *{margin-right:.3em}.ui.items>.item .meta :last-child{margin-right:0}.ui.items>.item .meta [class*="right floated"]{margin-right:0;margin-left:.3em}.ui.items>.item>.content a:not(.ui){color:'';-webkit-transition:color .1s ease;transition:color .1s ease}.ui.items>.item>.content a:not(.ui):hover{color:''}.ui.items>.item>.content>a.header{color:rgba(0,0,0,.85)}.ui.items>.item>.content>a.header:hover{color:#1e70bf}.ui.items>.item .meta>a:not(.ui){color:rgba(0,0,0,.4)}.ui.items>.item .meta>a:not(.ui):hover{color:rgba(0,0,0,.87)}.ui.items>.item>.content .favorite.icon{cursor:pointer;opacity:.75;-webkit-transition:color .1s ease;transition:color .1s ease}.ui.items>.item>.content .favorite.icon:hover{opacity:1;color:#ffb70a}.ui.items>.item>.content .active.favorite.icon{color:#ffe623}.ui.items>.item>.content .like.icon{cursor:pointer;opacity:.75;-webkit-transition:color .1s ease;transition:color .1s ease}.ui.items>.item>.content .like.icon:hover{opacity:1;color:#ff2733}.ui.items>.item>.content .active.like.icon{color:#ff2733}.ui.items>.item .extra{display:block;position:relative;background:0 0;margin:.5rem 0 0;width:100%;padding:0 0 0;top:0;left:0;color:rgba(0,0,0,.4);-webkit-box-shadow:none;box-shadow:none;-webkit-transition:color .1s ease;transition:color .1s ease;border-top:none}.ui.items>.item .extra>*{margin:.25rem .5rem .25rem 0}.ui.items>.item .extra>[class*="right floated"]{margin:.25rem 0 .25rem .5rem}.ui.items>.item .extra:after{display:block;content:' ';height:0;clear:both;overflow:hidden;visibility:hidden}.ui.items>.item>.image:not(.ui){width:175px}@media only screen and (min-width:768px) and (max-width:991px){.ui.items>.item{margin:1em 0}.ui.items>.item>.image:not(.ui){width:150px}.ui.items>.item>.image+.content{display:block;padding:0 0 0 1em}}@media only screen and (max-width:767px){.ui.items:not(.unstackable)>.item{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:2em 0}.ui.items:not(.unstackable)>.item>.image{display:block;margin-left:auto;margin-right:auto}.ui.items:not(.unstackable)>.item>.image,.ui.items:not(.unstackable)>.item>.image>img{max-width:100%!important;width:auto!important;max-height:250px!important}.ui.items:not(.unstackable)>.item>.image+.content{display:block;padding:1.5em 0 0}}.ui.items>.item>.image+[class*="top aligned"].content{-ms-flex-item-align:start;align-self:flex-start}.ui.items>.item>.image+[class*="middle aligned"].content{-ms-flex-item-align:center;align-self:center}.ui.items>.item>.image+[class*="bottom aligned"].content{-ms-flex-item-align:end;align-self:flex-end}.ui.relaxed.items>.item{margin:1.5em 0}.ui[class*="very relaxed"].items>.item{margin:2em 0}.ui.divided.items>.item{border-top:1px solid rgba(34,36,38,.15);margin:0;padding:1em 0}.ui.divided.items>.item:first-child{border-top:none;margin-top:0!important;padding-top:0!important}.ui.divided.items>.item:last-child{margin-bottom:0!important;padding-bottom:0!important}.ui.relaxed.divided.items>.item{margin:0;padding:1.5em 0}.ui[class*="very relaxed"].divided.items>.item{margin:0;padding:2em 0}.ui.items a.item:hover,.ui.link.items>.item:hover{cursor:pointer}.ui.items a.item:hover .content .header,.ui.link.items>.item:hover .content .header{color:#1e70bf}.ui.items>.item{font-size:1em}@media only screen and (max-width:767px){.ui.unstackable.items>.item>.image,.ui.unstackable.items>.item>.image>img{width:125px!important}}.ui.inverted.items>.item{background:0 0}.ui.inverted.items>.item>.content{background:0 0;color:rgba(255,255,255,.9)}.ui.inverted.items>.item .extra{background:0 0}.ui.inverted.items>.item>.content>.header{color:rgba(255,255,255,.9)}.ui.inverted.items>.item>.content>.description{color:rgba(255,255,255,.9)}.ui.inverted.items>.item .meta{color:rgba(255,255,255,.8)}.ui.inverted.items>.item>.content a:not(.ui){color:#57a4ef}.ui.inverted.items>.item>.content a:not(.ui):hover{color:#4183c4}.ui.inverted.items>.item>.content>a.header{color:rgba(255,255,255,.9)}.ui.inverted.items>.item>.content>a.header:hover{color:#fff}.ui.inverted.items>.item .meta>a:not(.ui){color:rgba(255,255,255,.7)}.ui.inverted.items>.item .meta>a:not(.ui):hover{color:rgba(255,255,255,.9)}.ui.inverted.items>.item>.content .favorite.icon:hover{color:#ffc63d}.ui.inverted.items>.item>.content .active.favorite.icon{color:#ffec56}.ui.inverted.items>.item>.content .like.icon:hover{color:#ff5a63}.ui.inverted.items>.item>.content .active.like.icon{color:#ff5a63}.ui.inverted.items>.item .extra{color:rgba(255,255,255,.7)}.ui.inverted.items a.item:hover .content .header,.ui.inverted.link.items>.item:hover .content .header{color:#fff}.ui.inverted.divided.items>.item{border-top:1px solid rgba(255,255,255,.1)}.ui.inverted.divided.items>.item:first-child{border-top:none}
--------------------------------------------------------------------------------
/examples/assets/styles/reset.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * # Semantic UI 2.7.2 - Reset
3 | * http://github.com/semantic-org/semantic-ui/
4 | *
5 | *
6 | * Released under the MIT license
7 | * http://opensource.org/licenses/MIT
8 | *
9 | */*,:after,:before{-webkit-box-sizing:inherit;box-sizing:inherit}html{-webkit-box-sizing:border-box;box-sizing:border-box}input[type=email],input[type=password],input[type=search],input[type=text]{-webkit-appearance:none;-moz-appearance:none}/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}
--------------------------------------------------------------------------------
/examples/assets/styles/segment.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * # Semantic UI 2.7.2 - Segment
3 | * http://github.com/semantic-org/semantic-ui/
4 | *
5 | *
6 | * Released under the MIT license
7 | * http://opensource.org/licenses/MIT
8 | *
9 | */.ui.segment{position:relative;background:#fff;-webkit-box-shadow:0 1px 2px 0 rgba(34,36,38,.15);box-shadow:0 1px 2px 0 rgba(34,36,38,.15);margin:1rem 0;padding:1em 1em;border-radius:.28571429rem;border:1px solid rgba(34,36,38,.15)}.ui.segment:first-child{margin-top:0}.ui.segment:last-child{margin-bottom:0}.ui.vertical.segment{margin:0;padding-left:0;padding-right:0;background:none transparent;border-radius:0;-webkit-box-shadow:none;box-shadow:none;border:none;border-bottom:1px solid rgba(34,36,38,.15)}.ui.vertical.segment:last-child{border-bottom:none}.ui.inverted.segment>.ui.header{color:#fff}.ui[class*="bottom attached"].segment>[class*="top attached"].label{border-top-left-radius:0;border-top-right-radius:0}.ui[class*="top attached"].segment>[class*="bottom attached"].label{border-bottom-left-radius:0;border-bottom-right-radius:0}.ui.attached.segment:not(.top):not(.bottom)>[class*="top attached"].label{border-top-left-radius:0;border-top-right-radius:0}.ui.attached.segment:not(.top):not(.bottom)>[class*="bottom attached"].label{border-bottom-left-radius:0;border-bottom-right-radius:0}.ui.grid>.row>.ui.segment.column,.ui.grid>.ui.segment.column,.ui.page.grid.segment{padding-top:2em;padding-bottom:2em}.ui.grid.segment{margin:1rem 0;border-radius:.28571429rem}.ui.basic.table.segment{background:#fff;border:1px solid rgba(34,36,38,.15);-webkit-box-shadow:0 1px 2px 0 rgba(34,36,38,.15);box-shadow:0 1px 2px 0 rgba(34,36,38,.15)}.ui[class*="very basic"].table.segment{padding:1em 1em}.ui.segment.tab:last-child{margin-bottom:1rem}.ui.placeholder.segment{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;max-width:initial;-webkit-animation:none;animation:none;overflow:visible;padding:1em 1em;min-height:18rem;background:#f9fafb;border-color:rgba(34,36,38,.15);-webkit-box-shadow:0 2px 25px 0 rgba(34,36,38,.05) inset;box-shadow:0 2px 25px 0 rgba(34,36,38,.05) inset}.ui.placeholder.segment .button,.ui.placeholder.segment textarea{display:block}.ui.placeholder.segment .button,.ui.placeholder.segment .field,.ui.placeholder.segment textarea,.ui.placeholder.segment>.ui.input{max-width:15rem;margin-left:auto;margin-right:auto}.ui.placeholder.segment .column .button,.ui.placeholder.segment .column .field,.ui.placeholder.segment .column textarea,.ui.placeholder.segment .column>.ui.input{max-width:15rem;margin-left:auto;margin-right:auto}.ui.placeholder.segment>.inline{-ms-flex-item-align:center;align-self:center}.ui.placeholder.segment>.inline>.button{display:inline-block;width:auto;margin:0 .35714286rem 0 0}.ui.placeholder.segment>.inline>.button:last-child{margin-right:0}.ui.piled.segment,.ui.piled.segments{margin:3em 0;-webkit-box-shadow:'';box-shadow:'';z-index:auto}.ui.piled.segment:first-child{margin-top:0}.ui.piled.segment:last-child{margin-bottom:0}.ui.piled.segment:after,.ui.piled.segment:before,.ui.piled.segments:after,.ui.piled.segments:before{background-color:#fff;visibility:visible;content:'';display:block;height:100%;left:0;position:absolute;width:100%;border:1px solid rgba(34,36,38,.15);-webkit-box-shadow:'';box-shadow:''}.ui.piled.segment:before,.ui.piled.segments:before{-webkit-transform:rotate(-1.2deg);transform:rotate(-1.2deg);top:0;z-index:-2}.ui.piled.segment:after,.ui.piled.segments:after{-webkit-transform:rotate(1.2deg);transform:rotate(1.2deg);top:0;z-index:-1}.ui[class*="top attached"].piled.segment{margin-top:3em;margin-bottom:0}.ui.piled.segment[class*="top attached"]:first-child{margin-top:0}.ui.piled.segment[class*="bottom attached"]{margin-top:0;margin-bottom:3em}.ui.piled.segment[class*="bottom attached"]:last-child{margin-bottom:0}.ui.stacked.segment{padding-bottom:1.4em}.ui.stacked.segment:after,.ui.stacked.segment:before,.ui.stacked.segments:after,.ui.stacked.segments:before{content:'';position:absolute;bottom:-3px;left:0;border-top:1px solid rgba(34,36,38,.15);background:rgba(0,0,0,.03);width:100%;height:6px;visibility:visible}.ui.stacked.segment:before,.ui.stacked.segments:before{display:none}.ui.tall.stacked.segment:before,.ui.tall.stacked.segments:before{display:block;bottom:0}.ui.stacked.inverted.segment:after,.ui.stacked.inverted.segment:before,.ui.stacked.inverted.segments:after,.ui.stacked.inverted.segments:before{background-color:rgba(0,0,0,.03);border-top:1px solid rgba(34,36,38,.35)}.ui.padded.segment{padding:1.5em}.ui[class*="very padded"].segment{padding:3em}.ui.padded.segment.vertical.segment,.ui[class*="very padded"].vertical.segment{padding-left:0;padding-right:0}.ui.compact.segment{display:table}.ui.compact.segments{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}.ui.compact.segments .segment,.ui.segments .compact.segment{display:block;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto}.ui.circular.segment{display:table-cell;padding:2em;text-align:center;vertical-align:middle;border-radius:500em}.ui.raised.raised.segment,.ui.raised.raised.segments{-webkit-box-shadow:0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.15);box-shadow:0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.15)}.ui.segments{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;position:relative;margin:1rem 0;border:1px solid rgba(34,36,38,.15);-webkit-box-shadow:0 1px 2px 0 rgba(34,36,38,.15);box-shadow:0 1px 2px 0 rgba(34,36,38,.15);border-radius:.28571429rem}.ui.segments:first-child{margin-top:0}.ui.segments:last-child{margin-bottom:0}.ui.segments>.segment{top:0;bottom:0;border-radius:0;margin:0;width:auto;-webkit-box-shadow:none;box-shadow:none;border:none;border-top:1px solid rgba(34,36,38,.15)}.ui.segments:not(.horizontal)>.segment:first-child{top:0;bottom:0;border-top:none;margin-top:0;margin-bottom:0;border-radius:.28571429rem .28571429rem 0 0}.ui.segments:not(.horizontal)>.segment:last-child{top:0;bottom:0;margin-top:0;margin-bottom:0;-webkit-box-shadow:0 1px 2px 0 rgba(34,36,38,.15),none;box-shadow:0 1px 2px 0 rgba(34,36,38,.15),none;border-radius:0 0 .28571429rem .28571429rem}.ui.segments:not(.horizontal)>.segment:only-child{border-radius:.28571429rem}.ui.segments>.ui.segments{border-top:1px solid rgba(34,36,38,.15);margin:1rem 1rem}.ui.segments>.segments:first-child{border-top:none}.ui.segments>.segment+.segments:not(.horizontal){margin-top:0}.ui.horizontal.segments{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;background-color:transparent;padding:0;-webkit-box-shadow:0 1px 2px 0 rgba(34,36,38,.15);box-shadow:0 1px 2px 0 rgba(34,36,38,.15);margin:1rem 0;border-radius:.28571429rem;border:1px solid rgba(34,36,38,.15)}.ui.stackable.horizontal.segments{-ms-flex-wrap:wrap;flex-wrap:wrap}.ui.segments>.horizontal.segments{margin:0;background-color:transparent;border-radius:0;border:none;-webkit-box-shadow:none;box-shadow:none;border-top:1px solid rgba(34,36,38,.15)}.ui.horizontal.segments>.segment{-webkit-box-flex:1;flex:1 1 auto;-ms-flex:1 1 0;margin:0;min-width:0;background-color:transparent;border-radius:0;border:none;-webkit-box-shadow:none;box-shadow:none;border-left:1px solid rgba(34,36,38,.15)}.ui.segments>.horizontal.segments:first-child{border-top:none}.ui.horizontal.segments:not(.stackable)>.segment:first-child{border-left:none}.ui.disabled.segment{opacity:.45;color:rgba(40,40,40,.3)}.ui.loading.segment{position:relative;cursor:default;pointer-events:none;text-shadow:none!important;-webkit-transition:all 0s linear;transition:all 0s linear}.ui.loading.segment:before{position:absolute;content:'';top:0;left:0;background:rgba(255,255,255,.8);width:100%;height:100%;border-radius:.28571429rem;z-index:100}.ui.loading.segment:after{position:absolute;content:'';top:50%;left:50%;margin:-1.5em 0 0 -1.5em;width:3em;height:3em;-webkit-animation:loader .6s infinite linear;animation:loader .6s infinite linear;border:.2em solid #767676;border-radius:500rem;-webkit-box-shadow:0 0 0 1px transparent;box-shadow:0 0 0 1px transparent;visibility:visible;z-index:101}.ui.basic.segment,.ui.basic.segments,.ui.segments .ui.basic.segment{background:none transparent;-webkit-box-shadow:none;box-shadow:none;border:none;border-radius:0}.ui.clearing.segment:after{content:".";display:block;height:0;clear:both;visibility:hidden}.ui.red.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #db2828}.ui.inverted.red.segment.segment.segment.segment.segment{background-color:#db2828;color:#fff}.ui.orange.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #f2711c}.ui.inverted.orange.segment.segment.segment.segment.segment{background-color:#f2711c;color:#fff}.ui.yellow.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #fbbd08}.ui.inverted.yellow.segment.segment.segment.segment.segment{background-color:#fbbd08;color:#fff}.ui.olive.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #b5cc18}.ui.inverted.olive.segment.segment.segment.segment.segment{background-color:#b5cc18;color:#fff}.ui.green.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #21ba45}.ui.inverted.green.segment.segment.segment.segment.segment{background-color:#21ba45;color:#fff}.ui.teal.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #00b5ad}.ui.inverted.teal.segment.segment.segment.segment.segment{background-color:#00b5ad;color:#fff}.ui.blue.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #2185d0}.ui.inverted.blue.segment.segment.segment.segment.segment{background-color:#2185d0;color:#fff}.ui.violet.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #6435c9}.ui.inverted.violet.segment.segment.segment.segment.segment{background-color:#6435c9;color:#fff}.ui.purple.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #a333c8}.ui.inverted.purple.segment.segment.segment.segment.segment{background-color:#a333c8;color:#fff}.ui.pink.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #e03997}.ui.inverted.pink.segment.segment.segment.segment.segment{background-color:#e03997;color:#fff}.ui.brown.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #a5673f}.ui.inverted.brown.segment.segment.segment.segment.segment{background-color:#a5673f;color:#fff}.ui.grey.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #767676}.ui.inverted.grey.segment.segment.segment.segment.segment{background-color:#767676;color:#fff}.ui.black.segment.segment.segment.segment.segment:not(.inverted){border-top:2px solid #1b1c1d}.ui.inverted.black.segment.segment.segment.segment.segment{background-color:#1b1c1d;color:#fff}.ui[class*="left aligned"].segment{text-align:left}.ui[class*="right aligned"].segment{text-align:right}.ui[class*="center aligned"].segment{text-align:center}.ui.floated.segment,.ui[class*="left floated"].segment{float:left;margin-right:1em}.ui[class*="right floated"].segment{float:right;margin-left:1em}.ui.inverted.segment{border:none;-webkit-box-shadow:none;box-shadow:none}.ui.inverted.segment,.ui.primary.inverted.segment{background:#1b1c1d;color:rgba(255,255,255,.9)}.ui.inverted.segment .segment{color:rgba(0,0,0,.87)}.ui.inverted.segment .inverted.segment{color:rgba(255,255,255,.9)}.ui.inverted.attached.segment{border-color:#555}.ui.inverted.loading.segment{color:#fff}.ui.inverted.loading.segment:before{background:rgba(0,0,0,.85)}.ui.secondary.segment{background:#f3f4f5;color:rgba(0,0,0,.6)}.ui.secondary.inverted.segment{background:#4c4f52 -webkit-gradient(linear,left top,left bottom,color-stop(0,rgba(255,255,255,.2)),to(rgba(255,255,255,.2)));background:#4c4f52 -webkit-linear-gradient(rgba(255,255,255,.2) 0,rgba(255,255,255,.2) 100%);background:#4c4f52 linear-gradient(rgba(255,255,255,.2) 0,rgba(255,255,255,.2) 100%);color:rgba(255,255,255,.8)}.ui.tertiary.segment{background:#dcddde;color:rgba(0,0,0,.6)}.ui.tertiary.inverted.segment{background:#717579 -webkit-gradient(linear,left top,left bottom,color-stop(0,rgba(255,255,255,.35)),to(rgba(255,255,255,.35)));background:#717579 -webkit-linear-gradient(rgba(255,255,255,.35) 0,rgba(255,255,255,.35) 100%);background:#717579 linear-gradient(rgba(255,255,255,.35) 0,rgba(255,255,255,.35) 100%);color:rgba(255,255,255,.8)}.ui.attached.segment{top:0;bottom:0;border-radius:0;margin:0 -1px;width:calc(100% + 2px);max-width:calc(100% + 2px);-webkit-box-shadow:none;box-shadow:none;border:1px solid #d4d4d5}.ui.attached:not(.message)+.ui.attached.segment:not(.top){border-top:none}.ui[class*="top attached"].segment{bottom:0;margin-bottom:0;top:0;margin-top:1rem;border-radius:.28571429rem .28571429rem 0 0}.ui.segment[class*="top attached"]:first-child{margin-top:0}.ui.segment[class*="bottom attached"]{bottom:0;margin-top:0;top:0;margin-bottom:1rem;-webkit-box-shadow:0 1px 2px 0 rgba(34,36,38,.15),none;box-shadow:0 1px 2px 0 rgba(34,36,38,.15),none;border-radius:0 0 .28571429rem .28571429rem}.ui.segment[class*="bottom attached"]:last-child{margin-bottom:1rem}.ui.mini.segment,.ui.mini.segments .segment{font-size:.78571429rem}.ui.tiny.segment,.ui.tiny.segments .segment{font-size:.85714286rem}.ui.small.segment,.ui.small.segments .segment{font-size:.92857143rem}.ui.segment,.ui.segments .segment{font-size:1rem}.ui.large.segment,.ui.large.segments .segment{font-size:1.14285714rem}.ui.big.segment,.ui.big.segments .segment{font-size:1.28571429rem}.ui.huge.segment,.ui.huge.segments .segment{font-size:1.42857143rem}.ui.massive.segment,.ui.massive.segments .segment{font-size:1.71428571rem}
--------------------------------------------------------------------------------
/examples/assets/styles/sidebar.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * # Semantic UI 2.7.2 - Sidebar
3 | * http://github.com/semantic-org/semantic-ui/
4 | *
5 | *
6 | * Released under the MIT license
7 | * http://opensource.org/licenses/MIT
8 | *
9 | */.ui.sidebar{position:fixed;top:0;left:0;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transition:none;transition:none;will-change:transform;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);visibility:hidden;-webkit-overflow-scrolling:touch;height:100%!important;max-height:100%;border-radius:0!important;margin:0!important;overflow-y:auto!important;z-index:102}.ui.sidebar>*{-webkit-backface-visibility:hidden;backface-visibility:hidden}.ui.left.sidebar{right:auto;left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.ui.right.sidebar{right:0!important;left:auto!important;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.ui.bottom.sidebar,.ui.top.sidebar{width:100%!important;height:auto!important}.ui.top.sidebar{top:0!important;bottom:auto!important;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}.ui.bottom.sidebar{top:auto!important;bottom:0!important;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}.pushable{height:100%;overflow-x:hidden;padding:0!important}body.pushable{background:#545454!important}.pushable:not(body){-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.pushable:not(body)>.fixed,.pushable:not(body)>.pusher:after,.pushable:not(body)>.ui.sidebar{position:absolute}.pushable>.fixed{position:fixed;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;will-change:transform;z-index:101}.pushable>.pusher{position:relative;-webkit-backface-visibility:hidden;backface-visibility:hidden;overflow:hidden;min-height:100%;-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;z-index:2;background:inherit}body.pushable>.pusher{background:#fff}.pushable>.pusher:after{position:fixed;top:0;right:0;content:'';background-color:rgba(0,0,0,.4);overflow:hidden;opacity:0;-webkit-transition:opacity .5s;transition:opacity .5s;will-change:opacity;z-index:1000}.ui.sidebar.menu .item{border-radius:0!important}.pushable>.pusher.dimmed:after{width:100%!important;height:100%!important;opacity:1!important}.ui.animating.sidebar{visibility:visible}.ui.visible.sidebar{visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.left.visible.sidebar,.ui.right.visible.sidebar{-webkit-box-shadow:0 0 20px rgba(34,36,38,.15);box-shadow:0 0 20px rgba(34,36,38,.15)}.ui.bottom.visible.sidebar,.ui.top.visible.sidebar{-webkit-box-shadow:0 0 20px rgba(34,36,38,.15);box-shadow:0 0 20px rgba(34,36,38,.15)}.ui.visible.left.sidebar~.fixed,.ui.visible.left.sidebar~.pusher{-webkit-transform:translate3d(260px,0,0);transform:translate3d(260px,0,0)}.ui.visible.right.sidebar~.fixed,.ui.visible.right.sidebar~.pusher{-webkit-transform:translate3d(-260px,0,0);transform:translate3d(-260px,0,0)}.ui.visible.top.sidebar~.fixed,.ui.visible.top.sidebar~.pusher{-webkit-transform:translate3d(0,36px,0);transform:translate3d(0,36px,0)}.ui.visible.bottom.sidebar~.fixed,.ui.visible.bottom.sidebar~.pusher{-webkit-transform:translate3d(0,-36px,0);transform:translate3d(0,-36px,0)}.ui.visible.left.sidebar~.ui.visible.right.sidebar~.fixed,.ui.visible.left.sidebar~.ui.visible.right.sidebar~.pusher,.ui.visible.right.sidebar~.ui.visible.left.sidebar~.fixed,.ui.visible.right.sidebar~.ui.visible.left.sidebar~.pusher{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.thin.left.sidebar,.ui.thin.right.sidebar{width:150px}.ui[class*="very thin"].left.sidebar,.ui[class*="very thin"].right.sidebar{width:60px}.ui.left.sidebar,.ui.right.sidebar{width:260px}.ui.wide.left.sidebar,.ui.wide.right.sidebar{width:350px}.ui[class*="very wide"].left.sidebar,.ui[class*="very wide"].right.sidebar{width:475px}.ui.visible.thin.left.sidebar~.fixed,.ui.visible.thin.left.sidebar~.pusher{-webkit-transform:translate3d(150px,0,0);transform:translate3d(150px,0,0)}.ui.visible[class*="very thin"].left.sidebar~.fixed,.ui.visible[class*="very thin"].left.sidebar~.pusher{-webkit-transform:translate3d(60px,0,0);transform:translate3d(60px,0,0)}.ui.visible.wide.left.sidebar~.fixed,.ui.visible.wide.left.sidebar~.pusher{-webkit-transform:translate3d(350px,0,0);transform:translate3d(350px,0,0)}.ui.visible[class*="very wide"].left.sidebar~.fixed,.ui.visible[class*="very wide"].left.sidebar~.pusher{-webkit-transform:translate3d(475px,0,0);transform:translate3d(475px,0,0)}.ui.visible.thin.right.sidebar~.fixed,.ui.visible.thin.right.sidebar~.pusher{-webkit-transform:translate3d(-150px,0,0);transform:translate3d(-150px,0,0)}.ui.visible[class*="very thin"].right.sidebar~.fixed,.ui.visible[class*="very thin"].right.sidebar~.pusher{-webkit-transform:translate3d(-60px,0,0);transform:translate3d(-60px,0,0)}.ui.visible.wide.right.sidebar~.fixed,.ui.visible.wide.right.sidebar~.pusher{-webkit-transform:translate3d(-350px,0,0);transform:translate3d(-350px,0,0)}.ui.visible[class*="very wide"].right.sidebar~.fixed,.ui.visible[class*="very wide"].right.sidebar~.pusher{-webkit-transform:translate3d(-475px,0,0);transform:translate3d(-475px,0,0)}.ui.overlay.sidebar{z-index:102}.ui.left.overlay.sidebar{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.ui.right.overlay.sidebar{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.ui.top.overlay.sidebar{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}.ui.bottom.overlay.sidebar{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}.animating.ui.overlay.sidebar,.ui.visible.overlay.sidebar{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease}.ui.visible.left.overlay.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.visible.right.overlay.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.visible.top.overlay.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.visible.bottom.overlay.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.visible.overlay.sidebar~.fixed,.ui.visible.overlay.sidebar~.pusher{-webkit-transform:none!important;transform:none!important}.ui.push.sidebar{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;z-index:102}.ui.left.push.sidebar{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.ui.right.push.sidebar{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.ui.top.push.sidebar{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}.ui.bottom.push.sidebar{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}.ui.visible.push.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.uncover.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);z-index:1}.ui.visible.uncover.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease}.ui.slide.along.sidebar{z-index:1}.ui.left.slide.along.sidebar{-webkit-transform:translate3d(-50%,0,0);transform:translate3d(-50%,0,0)}.ui.right.slide.along.sidebar{-webkit-transform:translate3d(50%,0,0);transform:translate3d(50%,0,0)}.ui.top.slide.along.sidebar{-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0)}.ui.bottom.slide.along.sidebar{-webkit-transform:translate3d(0,50%,0);transform:translate3d(0,50%,0)}.ui.animating.slide.along.sidebar{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease}.ui.visible.slide.along.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.slide.out.sidebar{z-index:1}.ui.left.slide.out.sidebar{-webkit-transform:translate3d(50%,0,0);transform:translate3d(50%,0,0)}.ui.right.slide.out.sidebar{-webkit-transform:translate3d(-50%,0,0);transform:translate3d(-50%,0,0)}.ui.top.slide.out.sidebar{-webkit-transform:translate3d(0,50%,0);transform:translate3d(0,50%,0)}.ui.bottom.slide.out.sidebar{-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0)}.ui.animating.slide.out.sidebar{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease}.ui.visible.slide.out.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.scale.down.sidebar{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;z-index:102}.ui.left.scale.down.sidebar{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.ui.right.scale.down.sidebar{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.ui.top.scale.down.sidebar{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}.ui.bottom.scale.down.sidebar{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}.ui.scale.down.left.sidebar~.pusher{-webkit-transform-origin:75% 50%;transform-origin:75% 50%}.ui.scale.down.right.sidebar~.pusher{-webkit-transform-origin:25% 50%;transform-origin:25% 50%}.ui.scale.down.top.sidebar~.pusher{-webkit-transform-origin:50% 75%;transform-origin:50% 75%}.ui.scale.down.bottom.sidebar~.pusher{-webkit-transform-origin:50% 25%;transform-origin:50% 25%}.ui.animating.scale.down>.visible.ui.sidebar{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease}.ui.animating.scale.down.sidebar~.pusher,.ui.visible.scale.down.sidebar~.pusher{display:block!important;width:100%;height:100%;overflow:hidden!important}.ui.visible.scale.down.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.visible.scale.down.sidebar~.pusher{-webkit-transform:scale(.75);transform:scale(.75)}
--------------------------------------------------------------------------------
/examples/assets/styles/site.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * # Semantic UI 2.7.2 - Site
3 | * http://github.com/semantic-org/semantic-ui/
4 | *
5 | *
6 | * Released under the MIT license
7 | * http://opensource.org/licenses/MIT
8 | *
9 | */@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic&subset=latin);body,html{height:100%}html{font-size:14px}body{margin:0;padding:0;overflow-x:hidden;min-width:320px;background:#fff;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-size:14px;line-height:1.4285em;color:rgba(0,0,0,.87);font-smoothing:antialiased}h1,h2,h3,h4,h5{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;line-height:1.28571429em;margin:calc(2rem - .1428571428571429em) 0 1rem;font-weight:700;padding:0}h1{min-height:1rem;font-size:2rem}h2{font-size:1.71428571rem}h3{font-size:1.28571429rem}h4{font-size:1.07142857rem}h5{font-size:1rem}h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child{margin-top:0}h1:last-child,h2:last-child,h3:last-child,h4:last-child,h5:last-child{margin-bottom:0}p{margin:0 0 1em;line-height:1.4285em}p:first-child{margin-top:0}p:last-child{margin-bottom:0}a{color:#4183c4;text-decoration:none}a:hover{color:#1e70bf;text-decoration:none}::-webkit-selection{background-color:#cce2ff;color:rgba(0,0,0,.87)}::selection{background-color:#cce2ff;color:rgba(0,0,0,.87)}input::-webkit-selection,textarea::-webkit-selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.87)}input::selection,textarea::selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.87)}body ::-webkit-scrollbar{-webkit-appearance:none;width:10px;height:10px}body ::-webkit-scrollbar-track{background:rgba(0,0,0,.1);border-radius:0}body ::-webkit-scrollbar-thumb{cursor:pointer;border-radius:5px;background:rgba(0,0,0,.25);-webkit-transition:color .2s ease;transition:color .2s ease}body ::-webkit-scrollbar-thumb:window-inactive{background:rgba(0,0,0,.15)}body ::-webkit-scrollbar-thumb:hover{background:rgba(128,135,139,.8)}body .ui.inverted:not(.dimmer)::-webkit-scrollbar-track{background:rgba(255,255,255,.1)}body .ui.inverted:not(.dimmer)::-webkit-scrollbar-thumb{background:rgba(255,255,255,.25)}body .ui.inverted:not(.dimmer)::-webkit-scrollbar-thumb:window-inactive{background:rgba(255,255,255,.15)}body .ui.inverted:not(.dimmer)::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.35)}
--------------------------------------------------------------------------------
/examples/assets/styles/table.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * # Semantic UI 2.7.2 - Table
3 | * http://github.com/semantic-org/semantic-ui/
4 | *
5 | *
6 | * Released under the MIT license
7 | * http://opensource.org/licenses/MIT
8 | *
9 | */.ui.table{width:100%;background:#fff;margin:1em 0;border:1px solid rgba(34,36,38,.15);-webkit-box-shadow:none;box-shadow:none;border-radius:.28571429rem;text-align:left;vertical-align:middle;color:rgba(0,0,0,.87);border-collapse:separate;border-spacing:0}.ui.table:first-child{margin-top:0}.ui.table:last-child{margin-bottom:0}.ui.table tbody,.ui.table thead{text-align:inherit;vertical-align:inherit}.ui.table td,.ui.table th{-webkit-transition:background .1s ease,color .1s ease;transition:background .1s ease,color .1s ease}.ui.table thead{-webkit-box-shadow:none;box-shadow:none}.ui.table thead th{cursor:auto;background:#f9fafb;text-align:inherit;color:rgba(0,0,0,.87);padding:.92857143em .78571429em;vertical-align:inherit;font-style:none;font-weight:700;text-transform:none;border-bottom:1px solid rgba(34,36,38,.1);border-left:none}.ui.table thead tr>th:first-child{border-left:none}.ui.table thead tr:first-child>th:first-child{border-radius:.28571429rem 0 0 0}.ui.table thead tr:first-child>th:last-child{border-radius:0 .28571429rem 0 0}.ui.table thead tr:first-child>th:only-child{border-radius:.28571429rem .28571429rem 0 0}.ui.table tfoot{-webkit-box-shadow:none;box-shadow:none}.ui.table tfoot th{cursor:auto;border-top:1px solid rgba(34,36,38,.15);background:#f9fafb;text-align:inherit;color:rgba(0,0,0,.87);padding:.78571429em .78571429em;vertical-align:middle;font-style:normal;font-weight:400;text-transform:none}.ui.table tfoot tr>th:first-child{border-left:none}.ui.table tfoot tr:first-child>th:first-child{border-radius:0 0 0 .28571429rem}.ui.table tfoot tr:first-child>th:last-child{border-radius:0 0 .28571429rem 0}.ui.table tfoot tr:first-child>th:only-child{border-radius:0 0 .28571429rem .28571429rem}.ui.table tr td{border-top:1px solid rgba(34,36,38,.1)}.ui.table tr:first-child td{border-top:none}.ui.table tbody+tbody tr:first-child td{border-top:1px solid rgba(34,36,38,.1)}.ui.table td{padding:.78571429em .78571429em;text-align:inherit}.ui.table>.icon{vertical-align:baseline}.ui.table>.icon:only-child{margin:0}.ui.table.segment{padding:0}.ui.table.segment:after{display:none}.ui.table.segment.stacked:after{display:block}@media only screen and (max-width:767px){.ui.table:not(.unstackable){width:100%;padding:0}.ui.table:not(.unstackable) tbody,.ui.table:not(.unstackable) tr,.ui.table:not(.unstackable) tr>td,.ui.table:not(.unstackable) tr>th{display:block!important;width:auto!important}.ui.table:not(.unstackable) thead{display:block}.ui.table:not(.unstackable) tfoot{display:block}.ui.table:not(.unstackable) tr{padding-top:1em;padding-bottom:1em;-webkit-box-shadow:0 -1px 0 0 rgba(0,0,0,.1) inset!important;box-shadow:0 -1px 0 0 rgba(0,0,0,.1) inset!important}.ui.table:not(.unstackable) tr>th,.ui.ui.ui.ui.table:not(.unstackable) tr>td{background:0 0;border:none;padding:.25em .75em;-webkit-box-shadow:none!important;box-shadow:none!important}.ui.table:not(.unstackable) td:first-child,.ui.table:not(.unstackable) th:first-child{font-weight:700}.ui.definition.table:not(.unstackable) thead th:first-child{-webkit-box-shadow:none!important;box-shadow:none!important}}.ui.table .collapsing .image,.ui.table .collapsing .image img{max-width:none}.ui.structured.table{border-collapse:collapse}.ui.structured.table thead th{border-left:none;border-right:none}.ui.structured.sortable.table thead th{border-left:1px solid rgba(34,36,38,.15);border-right:1px solid rgba(34,36,38,.15)}.ui.structured.basic.table th{border-left:none;border-right:none}.ui.structured.celled.table tr td,.ui.structured.celled.table tr th{border-left:1px solid rgba(34,36,38,.1);border-right:1px solid rgba(34,36,38,.1)}.ui.definition.table thead:not(.full-width) th:first-child{pointer-events:none;background:0 0;font-weight:400;color:rgba(0,0,0,.4);-webkit-box-shadow:-1px -1px 0 1px #fff;box-shadow:-1px -1px 0 1px #fff}.ui.definition.table tfoot:not(.full-width) th:first-child{pointer-events:none;background:0 0;font-weight:400;color:rgba(0,0,0,.4);-webkit-box-shadow:1px 1px 0 1px #fff;box-shadow:1px 1px 0 1px #fff}.ui.celled.definition.table thead:not(.full-width) th:first-child{-webkit-box-shadow:0 -1px 0 1px #fff;box-shadow:0 -1px 0 1px #fff}.ui.celled.definition.table tfoot:not(.full-width) th:first-child{-webkit-box-shadow:0 1px 0 1px #fff;box-shadow:0 1px 0 1px #fff}.ui.definition.table tr td.definition,.ui.definition.table tr td:first-child:not(.ignored){background:rgba(0,0,0,.03);font-weight:700;color:rgba(0,0,0,.95);text-transform:'';-webkit-box-shadow:'';box-shadow:'';text-align:'';font-size:1em;padding-left:'';padding-right:''}.ui.definition.table thead:not(.full-width) th:nth-child(2){border-left:1px solid rgba(34,36,38,.15)}.ui.definition.table tfoot:not(.full-width) th:nth-child(2){border-left:1px solid rgba(34,36,38,.15)}.ui.definition.table td:nth-child(2){border-left:1px solid rgba(34,36,38,.15)}.ui.ui.table td.positive,.ui.ui.ui.ui.table tr.positive{-webkit-box-shadow:0 0 0 #a3c293 inset;box-shadow:0 0 0 #a3c293 inset;background:#fcfff5;color:#2c662d}.ui.ui.table td.negative,.ui.ui.ui.ui.table tr.negative{-webkit-box-shadow:0 0 0 #e0b4b4 inset;box-shadow:0 0 0 #e0b4b4 inset;background:#fff6f6;color:#9f3a38}.ui.ui.table td.error,.ui.ui.ui.ui.table tr.error{-webkit-box-shadow:0 0 0 #e0b4b4 inset;box-shadow:0 0 0 #e0b4b4 inset;background:#fff6f6;color:#9f3a38}.ui.ui.table td.warning,.ui.ui.ui.ui.table tr.warning{-webkit-box-shadow:0 0 0 #c9ba9b inset;box-shadow:0 0 0 #c9ba9b inset;background:#fffaf3;color:#573a08}.ui.ui.table td.active,.ui.ui.ui.ui.table tr.active{-webkit-box-shadow:0 0 0 rgba(0,0,0,.87) inset;box-shadow:0 0 0 rgba(0,0,0,.87) inset;background:#e0e0e0;color:rgba(0,0,0,.87)}.ui.table tr td.disabled,.ui.table tr.disabled td,.ui.table tr.disabled:hover,.ui.table tr:hover td.disabled{pointer-events:none;color:rgba(40,40,40,.3)}@media only screen and (max-width:991px){.ui[class*="tablet stackable"].table,.ui[class*="tablet stackable"].table tbody,.ui[class*="tablet stackable"].table tr,.ui[class*="tablet stackable"].table tr>td,.ui[class*="tablet stackable"].table tr>th{display:block!important;width:100%!important}.ui[class*="tablet stackable"].table{padding:0}.ui[class*="tablet stackable"].table thead{display:block}.ui[class*="tablet stackable"].table tfoot{display:block}.ui[class*="tablet stackable"].table tr{padding-top:1em;padding-bottom:1em;-webkit-box-shadow:0 -1px 0 0 rgba(0,0,0,.1) inset!important;box-shadow:0 -1px 0 0 rgba(0,0,0,.1) inset!important}.ui[class*="tablet stackable"].table tr>td,.ui[class*="tablet stackable"].table tr>th{background:0 0;border:none!important;padding:.25em .75em;-webkit-box-shadow:none!important;box-shadow:none!important}.ui.definition[class*="tablet stackable"].table thead th:first-child{-webkit-box-shadow:none!important;box-shadow:none!important}}.ui.table [class*="left aligned"],.ui.table[class*="left aligned"]{text-align:left}.ui.table [class*="center aligned"],.ui.table[class*="center aligned"]{text-align:center}.ui.table [class*="right aligned"],.ui.table[class*="right aligned"]{text-align:right}.ui.table [class*="top aligned"],.ui.table[class*="top aligned"]{vertical-align:top}.ui.table [class*="middle aligned"],.ui.table[class*="middle aligned"]{vertical-align:middle}.ui.table [class*="bottom aligned"],.ui.table[class*="bottom aligned"]{vertical-align:bottom}.ui.table td.collapsing,.ui.table th.collapsing{width:1px;white-space:nowrap}.ui.fixed.table{table-layout:fixed}.ui.fixed.table td,.ui.fixed.table th{overflow:hidden;text-overflow:ellipsis}.ui.table tbody tr td.selectable:hover,.ui.ui.selectable.table tbody tr:hover{background:rgba(0,0,0,.05);color:rgba(0,0,0,.95)}.ui.inverted.table tbody tr td.selectable:hover,.ui.ui.selectable.inverted.table tbody tr:hover{background:rgba(255,255,255,.08);color:#fff}.ui.table tbody tr td.selectable{padding:0}.ui.table tbody tr td.selectable>a:not(.ui){display:block;color:inherit;padding:.78571429em .78571429em}.ui.selectable.table tr:hover td.error,.ui.table tr td.selectable.error:hover,.ui.ui.selectable.table tr.error:hover{background:#ffe7e7;color:#943634}.ui.selectable.table tr:hover td.warning,.ui.table tr td.selectable.warning:hover,.ui.ui.selectable.table tr.warning:hover{background:#fff4e4;color:#493107}.ui.selectable.table tr:hover td.active,.ui.table tr td.selectable.active:hover,.ui.ui.selectable.table tr.active:hover{background:#e0e0e0;color:rgba(0,0,0,.87)}.ui.selectable.table tr:hover td.positive,.ui.table tr td.selectable.positive:hover,.ui.ui.selectable.table tr.positive:hover{background:#f7ffe6;color:#275b28}.ui.selectable.table tr:hover td.negative,.ui.table tr td.selectable.negative:hover,.ui.ui.selectable.table tr.negative:hover{background:#ffe7e7;color:#943634}.ui.attached.table{top:0;bottom:0;border-radius:0;margin:0 -1px;width:calc(100% + 2px);max-width:calc(100% + 2px);-webkit-box-shadow:none;box-shadow:none;border:1px solid #d4d4d5}.ui.attached+.ui.attached.table:not(.top){border-top:none}.ui[class*="top attached"].table{bottom:0;margin-bottom:0;top:0;margin-top:1em;border-radius:.28571429rem .28571429rem 0 0}.ui.table[class*="top attached"]:first-child{margin-top:0}.ui[class*="bottom attached"].table{bottom:0;margin-top:0;top:0;margin-bottom:1em;-webkit-box-shadow:none,none;box-shadow:none,none;border-radius:0 0 .28571429rem .28571429rem}.ui[class*="bottom attached"].table:last-child{margin-bottom:0}.ui.striped.table tbody tr:nth-child(2n),.ui.striped.table>tr:nth-child(2n){background-color:rgba(0,0,50,.02)}.ui.inverted.striped.table tbody tr:nth-child(2n),.ui.inverted.striped.table>tr:nth-child(2n){background-color:rgba(255,255,255,.05)}.ui.striped.selectable.selectable.selectable.table tbody tr.active:hover{background:#efefef;color:rgba(0,0,0,.95)}.ui.table [class*="single line"],.ui.table[class*="single line"]{white-space:nowrap}.ui.primary.table{border-top:.2em solid #2185d0}.ui.inverted.primary.table{background-color:#2185d0;color:#fff}.ui.ui.table td.primary,.ui.ui.ui.ui.table tr.primary{-webkit-box-shadow:0 0 0 #1a69a4 inset;box-shadow:0 0 0 #1a69a4 inset;background:#ddf4ff;color:rgba(255,255,255,.9)}.ui.selectable.table tr:hover td.primary,.ui.table tr td.selectable.primary:hover,.ui.ui.selectable.table tr.primary:hover{background:#d3f1ff;color:rgba(255,255,255,.9)}.ui.secondary.table{border-top:.2em solid #1b1c1d}.ui.inverted.secondary.table{background-color:#1b1c1d;color:#fff}.ui.ui.table td.secondary,.ui.ui.ui.ui.table tr.secondary{-webkit-box-shadow:0 0 0 #020203 inset;box-shadow:0 0 0 #020203 inset;background:#ddd;color:rgba(255,255,255,.9)}.ui.selectable.table tr:hover td.secondary,.ui.table tr td.selectable.secondary:hover,.ui.ui.selectable.table tr.secondary:hover{background:#e2e2e2;color:rgba(255,255,255,.9)}.ui.red.table{border-top:.2em solid #db2828}.ui.inverted.red.table{background-color:#db2828;color:#fff}.ui.ui.table td.red,.ui.ui.ui.ui.table tr.red{-webkit-box-shadow:0 0 0 #b21e1e inset;box-shadow:0 0 0 #b21e1e inset;background:#ffe1df;color:#db2828}.ui.selectable.table tr:hover td.red,.ui.table tr td.selectable.red:hover,.ui.ui.selectable.table tr.red:hover{background:#ffd7d5;color:#db2828}.ui.orange.table{border-top:.2em solid #f2711c}.ui.inverted.orange.table{background-color:#f2711c;color:#fff}.ui.ui.table td.orange,.ui.ui.ui.ui.table tr.orange{-webkit-box-shadow:0 0 0 #cf590c inset;box-shadow:0 0 0 #cf590c inset;background:#ffe7d1;color:#f2711c}.ui.selectable.table tr:hover td.orange,.ui.table tr td.selectable.orange:hover,.ui.ui.selectable.table tr.orange:hover{background:#fae1cc;color:#f2711c}.ui.yellow.table{border-top:.2em solid #fbbd08}.ui.inverted.yellow.table{background-color:#fbbd08;color:#fff}.ui.ui.table td.yellow,.ui.ui.ui.ui.table tr.yellow{-webkit-box-shadow:0 0 0 #cd9903 inset;box-shadow:0 0 0 #cd9903 inset;background:#fff9d2;color:#b58105}.ui.selectable.table tr:hover td.yellow,.ui.table tr td.selectable.yellow:hover,.ui.ui.selectable.table tr.yellow:hover{background:#fbf5cc;color:#b58105}.ui.olive.table{border-top:.2em solid #b5cc18}.ui.inverted.olive.table{background-color:#b5cc18;color:#fff}.ui.ui.table td.olive,.ui.ui.ui.ui.table tr.olive{-webkit-box-shadow:0 0 0 #8d9e13 inset;box-shadow:0 0 0 #8d9e13 inset;background:#f7fae4;color:#8abc1e}.ui.selectable.table tr:hover td.olive,.ui.table tr td.selectable.olive:hover,.ui.ui.selectable.table tr.olive:hover{background:#f6fada;color:#8abc1e}.ui.green.table{border-top:.2em solid #21ba45}.ui.inverted.green.table{background-color:#21ba45;color:#fff}.ui.ui.table td.green,.ui.ui.ui.ui.table tr.green{-webkit-box-shadow:0 0 0 #198f35 inset;box-shadow:0 0 0 #198f35 inset;background:#d5f5d9;color:#1ebc30}.ui.selectable.table tr:hover td.green,.ui.table tr td.selectable.green:hover,.ui.ui.selectable.table tr.green:hover{background:#d2eed5;color:#1ebc30}.ui.teal.table{border-top:.2em solid #00b5ad}.ui.inverted.teal.table{background-color:#00b5ad;color:#fff}.ui.ui.table td.teal,.ui.ui.ui.ui.table tr.teal{-webkit-box-shadow:0 0 0 #00827c inset;box-shadow:0 0 0 #00827c inset;background:#e2ffff;color:#10a3a3}.ui.selectable.table tr:hover td.teal,.ui.table tr td.selectable.teal:hover,.ui.ui.selectable.table tr.teal:hover{background:#d8ffff;color:#10a3a3}.ui.blue.table{border-top:.2em solid #2185d0}.ui.inverted.blue.table{background-color:#2185d0;color:#fff}.ui.ui.table td.blue,.ui.ui.ui.ui.table tr.blue{-webkit-box-shadow:0 0 0 #1a69a4 inset;box-shadow:0 0 0 #1a69a4 inset;background:#ddf4ff;color:#2185d0}.ui.selectable.table tr:hover td.blue,.ui.table tr td.selectable.blue:hover,.ui.ui.selectable.table tr.blue:hover{background:#d3f1ff;color:#2185d0}.ui.violet.table{border-top:.2em solid #6435c9}.ui.inverted.violet.table{background-color:#6435c9;color:#fff}.ui.ui.table td.violet,.ui.ui.ui.ui.table tr.violet{-webkit-box-shadow:0 0 0 #502aa1 inset;box-shadow:0 0 0 #502aa1 inset;background:#ece9fe;color:#6435c9}.ui.selectable.table tr:hover td.violet,.ui.table tr td.selectable.violet:hover,.ui.ui.selectable.table tr.violet:hover{background:#e3deff;color:#6435c9}.ui.purple.table{border-top:.2em solid #a333c8}.ui.inverted.purple.table{background-color:#a333c8;color:#fff}.ui.ui.table td.purple,.ui.ui.ui.ui.table tr.purple{-webkit-box-shadow:0 0 0 #82299f inset;box-shadow:0 0 0 #82299f inset;background:#f8e3ff;color:#a333c8}.ui.selectable.table tr:hover td.purple,.ui.table tr td.selectable.purple:hover,.ui.ui.selectable.table tr.purple:hover{background:#f5d9ff;color:#a333c8}.ui.pink.table{border-top:.2em solid #e03997}.ui.inverted.pink.table{background-color:#e03997;color:#fff}.ui.ui.table td.pink,.ui.ui.ui.ui.table tr.pink{-webkit-box-shadow:0 0 0 #c71f7e inset;box-shadow:0 0 0 #c71f7e inset;background:#ffe8f9;color:#e03997}.ui.selectable.table tr:hover td.pink,.ui.table tr td.selectable.pink:hover,.ui.ui.selectable.table tr.pink:hover{background:#ffdef6;color:#e03997}.ui.brown.table{border-top:.2em solid #a5673f}.ui.inverted.brown.table{background-color:#a5673f;color:#fff}.ui.ui.table td.brown,.ui.ui.ui.ui.table tr.brown{-webkit-box-shadow:0 0 0 #805031 inset;box-shadow:0 0 0 #805031 inset;background:#f7e5d2;color:#a5673f}.ui.selectable.table tr:hover td.brown,.ui.table tr td.selectable.brown:hover,.ui.ui.selectable.table tr.brown:hover{background:#efe0cf;color:#a5673f}.ui.grey.table{border-top:.2em solid #767676}.ui.inverted.grey.table{background-color:#767676;color:#fff}.ui.ui.table td.grey,.ui.ui.ui.ui.table tr.grey{-webkit-box-shadow:0 0 0 #5d5d5d inset;box-shadow:0 0 0 #5d5d5d inset;background:#dcddde;color:#767676}.ui.selectable.table tr:hover td.grey,.ui.table tr td.selectable.grey:hover,.ui.ui.selectable.table tr.grey:hover{background:#c2c4c5;color:#767676}.ui.black.table{border-top:.2em solid #1b1c1d}.ui.inverted.black.table{background-color:#1b1c1d;color:#fff}.ui.ui.table td.black,.ui.ui.ui.ui.table tr.black{-webkit-box-shadow:0 0 0 #020203 inset;box-shadow:0 0 0 #020203 inset;background:#545454;color:#fff}.ui.selectable.table tr:hover td.black,.ui.table tr td.selectable.black:hover,.ui.ui.selectable.table tr.black:hover{background:#000;color:#fff}.ui.one.column.table td{width:100%}.ui.two.column.table td{width:50%}.ui.three.column.table td{width:33.33333333%}.ui.four.column.table td{width:25%}.ui.five.column.table td{width:20%}.ui.six.column.table td{width:16.66666667%}.ui.seven.column.table td{width:14.28571429%}.ui.eight.column.table td{width:12.5%}.ui.nine.column.table td{width:11.11111111%}.ui.ten.column.table td{width:10%}.ui.eleven.column.table td{width:9.09090909%}.ui.twelve.column.table td{width:8.33333333%}.ui.thirteen.column.table td{width:7.69230769%}.ui.fourteen.column.table td{width:7.14285714%}.ui.fifteen.column.table td{width:6.66666667%}.ui.sixteen.column.table td{width:6.25%}.ui.table td.one.wide,.ui.table th.one.wide{width:6.25%}.ui.table td.two.wide,.ui.table th.two.wide{width:12.5%}.ui.table td.three.wide,.ui.table th.three.wide{width:18.75%}.ui.table td.four.wide,.ui.table th.four.wide{width:25%}.ui.table td.five.wide,.ui.table th.five.wide{width:31.25%}.ui.table td.six.wide,.ui.table th.six.wide{width:37.5%}.ui.table td.seven.wide,.ui.table th.seven.wide{width:43.75%}.ui.table td.eight.wide,.ui.table th.eight.wide{width:50%}.ui.table td.nine.wide,.ui.table th.nine.wide{width:56.25%}.ui.table td.ten.wide,.ui.table th.ten.wide{width:62.5%}.ui.table td.eleven.wide,.ui.table th.eleven.wide{width:68.75%}.ui.table td.twelve.wide,.ui.table th.twelve.wide{width:75%}.ui.table td.thirteen.wide,.ui.table th.thirteen.wide{width:81.25%}.ui.table td.fourteen.wide,.ui.table th.fourteen.wide{width:87.5%}.ui.table td.fifteen.wide,.ui.table th.fifteen.wide{width:93.75%}.ui.table td.sixteen.wide,.ui.table th.sixteen.wide{width:100%}.ui.sortable.table thead th{cursor:pointer;white-space:nowrap;border-left:1px solid rgba(34,36,38,.15);color:rgba(0,0,0,.87)}.ui.sortable.table thead th:first-child{border-left:none}.ui.sortable.table thead th.sorted,.ui.sortable.table thead th.sorted:hover{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ui.sortable.table thead th:after{display:none;font-style:normal;font-weight:400;text-decoration:inherit;content:'';height:1em;width:auto;opacity:.8;margin:0 0 0 .5em;font-family:Icons}.ui.sortable.table thead th.ascending:after{content:'\f0d8'}.ui.sortable.table thead th.descending:after{content:'\f0d7'}.ui.sortable.table th.disabled:hover{cursor:auto;color:rgba(40,40,40,.3)}.ui.sortable.table thead th:hover{background:rgba(0,0,0,.05);color:rgba(0,0,0,.8)}.ui.sortable.table thead th.sorted{background:rgba(0,0,0,.05);color:rgba(0,0,0,.95)}.ui.sortable.table thead th.sorted:after{display:inline-block}.ui.sortable.table thead th.sorted:hover{background:rgba(0,0,0,.05);color:rgba(0,0,0,.95)}.ui.inverted.sortable.table thead th.sorted{background:rgba(255,255,255,.15) -webkit-gradient(linear,left top,left bottom,from(transparent),to(rgba(0,0,0,.05)));background:rgba(255,255,255,.15) -webkit-linear-gradient(transparent,rgba(0,0,0,.05));background:rgba(255,255,255,.15) linear-gradient(transparent,rgba(0,0,0,.05));color:#fff}.ui.inverted.sortable.table thead th:hover{background:rgba(255,255,255,.08) -webkit-gradient(linear,left top,left bottom,from(transparent),to(rgba(0,0,0,.05)));background:rgba(255,255,255,.08) -webkit-linear-gradient(transparent,rgba(0,0,0,.05));background:rgba(255,255,255,.08) linear-gradient(transparent,rgba(0,0,0,.05));color:#fff}.ui.inverted.sortable.table thead th{border-left-color:transparent;border-right-color:transparent}.ui.inverted.table{background:#333;color:rgba(255,255,255,.9);border:none}.ui.inverted.table th{background-color:rgba(0,0,0,.15);border-color:rgba(255,255,255,.1);color:rgba(255,255,255,.9)}.ui.inverted.table tr td{border-color:rgba(255,255,255,.1)}.ui.inverted.table tr td.disabled,.ui.inverted.table tr.disabled td,.ui.inverted.table tr.disabled:hover td,.ui.inverted.table tr:hover td.disabled{pointer-events:none;color:rgba(225,225,225,.3)}.ui.inverted.definition.table tfoot:not(.full-width) th:first-child,.ui.inverted.definition.table thead:not(.full-width) th:first-child{background:#fff}.ui.inverted.definition.table tr td:first-child{background:rgba(255,255,255,.02);color:#fff}.ui.collapsing.table{width:auto}.ui.basic.table{background:0 0;border:1px solid rgba(34,36,38,.15);-webkit-box-shadow:none;box-shadow:none}.ui.basic.table tfoot,.ui.basic.table thead{-webkit-box-shadow:none;box-shadow:none}.ui.basic.table th{background:0 0;border-left:none}.ui.basic.table tbody tr{border-bottom:1px solid rgba(0,0,0,.1)}.ui.basic.table td{background:0 0}.ui.basic.striped.table tbody tr:nth-child(2n){background-color:rgba(0,0,0,.05)}.ui[class*="very basic"].table{border:none}.ui[class*="very basic"].table:not(.sortable):not(.striped) td,.ui[class*="very basic"].table:not(.sortable):not(.striped) th{padding:''}.ui[class*="very basic"].table:not(.sortable):not(.striped) td:first-child,.ui[class*="very basic"].table:not(.sortable):not(.striped) th:first-child{padding-left:0}.ui[class*="very basic"].table:not(.sortable):not(.striped) td:last-child,.ui[class*="very basic"].table:not(.sortable):not(.striped) th:last-child{padding-right:0}.ui[class*="very basic"].table:not(.sortable):not(.striped) thead tr:first-child th{padding-top:0}.ui.celled.table tr td,.ui.celled.table tr th{border-left:1px solid rgba(34,36,38,.1)}.ui.celled.table tr td:first-child,.ui.celled.table tr th:first-child{border-left:none}.ui.padded.table th{padding-left:1em;padding-right:1em}.ui.padded.table td,.ui.padded.table th{padding:1em 1em}.ui[class*="very padded"].table th{padding-left:1.5em;padding-right:1.5em}.ui[class*="very padded"].table td{padding:1.5em 1.5em}.ui.compact.table th{padding-left:.7em;padding-right:.7em}.ui.compact.table td{padding:.5em .7em}.ui[class*="very compact"].table th{padding-left:.6em;padding-right:.6em}.ui[class*="very compact"].table td{padding:.4em .6em}.ui.small.table{font-size:.9em}.ui.table{font-size:1em}.ui.large.table{font-size:1.1em}
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/named-outlet/content-attr-params.js:
--------------------------------------------------------------------------------
1 | import { ContentBasicElement } from './content-basic.js';
2 |
3 | class ContentTwoElement extends ContentBasicElement {
4 |
5 | constructor() {
6 | super();
7 | this.description = `
8 | Extends the previous exmaple by passing parameters (HTML attributes) to the customer element assigned to the outlet.
9 | `;
10 | }
11 | }
12 |
13 | customElements.define('content-attr-params', ContentTwoElement);
--------------------------------------------------------------------------------
/examples/named-outlet/content-basic.js:
--------------------------------------------------------------------------------
1 | export class ContentBasicElement extends HTMLElement {
2 |
3 | connectedCallback() {
4 | if (!this._initialized) {
5 | this._initialized = true;
6 | this.render();
7 | }
8 | }
9 |
10 | static get observedAttributes() { return ['param1', 'param2']; }
11 |
12 | attributeChangedCallback(name, oldValue, newValue) {
13 | this.render();
14 | }
15 |
16 | constructor() {
17 | super();
18 |
19 | this.description = `
20 |
21 | A simple example that assigns/injects a native custom element into a named outlet.
22 | If the named outlet exists the custom element will be rendered in th eoutlet.
23 | If the outlet does not exist the assignment is still made and if/when the named outlet first added to the DOM it the assignment will take place. The assignment will wait for the named outlet to exist.
24 |
25 | `;
26 | }
27 |
28 | render() {
29 | this.innerHTML = `
30 |
31 |
32 | ${this.description}
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 | Type
43 | Name
44 | Value
45 |
46 |
47 |
48 |
49 | HTML Attribute
50 | param1
51 | ${this.getAttribute('param1') || 'not set'}
52 |
53 |
54 | HTML Attribute
55 | param2
56 | ${this.getAttribute('param2') || 'not set'}
57 |
58 |
59 | Object Instance property
60 | param3
61 | ${this.param3 || 'not set'}
62 |
63 |
64 | Object Instance property
65 | param4
66 | ${this.param4 || 'not set'}
67 |
68 |
69 |
70 |
71 |
72 | `;
73 | }
74 | }
75 |
76 | customElements.define('content-basic', ContentBasicElement);
--------------------------------------------------------------------------------
/examples/named-outlet/content-guards.js:
--------------------------------------------------------------------------------
1 | import { ContentBasicElement } from './content-basic.js';
2 |
3 | class ContentGuardsElement extends ContentBasicElement {
4 |
5 | connectedCallback() {
6 | super.connectedCallback && super.connectedCallback();
7 |
8 | window.addEventListener('onRouteLeave', this.guard);
9 | }
10 |
11 | disconnectedCallback() {
12 | super.disconnectedCallback && super.disconnectedCallback();
13 |
14 | window.removeEventListener('onRouteLeave', this.guard);
15 | }
16 |
17 | constructor() {
18 | super();
19 |
20 | this.guard = this.guard.bind(this);
21 |
22 | this.description = `
23 |
24 |
25 | Disbable this check box to allow navigation away.
26 |
27 |
28 | Will not be able to leave this route whilst checkbox is enabled.
29 |
30 | Guard code
31 |
32 | window.addEventListener('onRouteLeave', guard);
33 |
34 | guard(event) {
35 | if (document.getElementById('guard').checked) {
36 | event.preventDefault();
37 | }
38 | }
39 |
40 | `;
41 | }
42 |
43 | guard(event) {
44 | if (document.getElementById('guard').checked) {
45 | event.preventDefault();
46 | }
47 | }
48 | }
49 |
50 | customElements.define('content-guards', ContentGuardsElement);
--------------------------------------------------------------------------------
/examples/named-outlet/content-import-byconvention.js:
--------------------------------------------------------------------------------
1 | import { ContentBasicElement } from './content-basic.js';
2 |
3 | class ContentImportByConventionElement extends ContentBasicElement {
4 |
5 | constructor() {
6 | super();
7 |
8 | this.description = `
9 |
10 | So far we've seen import links where the url explicitly defines the names of the custom element and the optional script file to import that contains the custom element. This can get a little much to type. Consider the following:
11 |
12 | <a is="router-link" href="(main:content-import-byconvention(/a-wc-router/examples/named-outlet/content-import-byconvention.js))">Import By Convention</a>
13 |
14 | This can be simplified. You can also create links that import custom elements by using a convention based approach. This is an optional opinionated approach but is more terse.
15 | This will work with kebab case script names that are exactly the same as the custom element they define (with the .js extension).
16 |
17 | <a is="router-link" href="(main:/a-wc-router/examples/named-outlet/content-import-byconvention)">Import By Convention</a>
18 |
19 | With some creative routing to the script files on the server, you could end up with something like:
20 |
21 | <a is="router-link" href="(main:/admin/user-details:id=3)">Import By Convention</a>
22 | `;
23 | }
24 | }
25 |
26 | customElements.define('content-import-byconvention', ContentImportByConventionElement);
--------------------------------------------------------------------------------
/examples/named-outlet/content-import.js:
--------------------------------------------------------------------------------
1 | import { ContentBasicElement } from './content-basic.js';
2 |
3 | class ContentImportElement extends ContentBasicElement {
4 |
5 | constructor() {
6 | super();
7 |
8 | this.description = `
9 |
10 | Note that absolute paths must be used. This is due to the import being performed in the router code so the relative path will be relative to the location of the router script.
11 |
(/a-wc-router/examples/named-outlet/content-import.js)
12 | The placement of the import source comes just after the tag name in the url.
13 |
14 |
15 | Example of defered importing (upfront and pre-cache). This helps users implement the PRPL pattern.
16 | We want the first page of a web application to load as fast as possible. In order to do this we want to only load the resources required to render the first page and do so as fast as possible and defer everything that's not required for first render.
17 | Importing a custom element script allows us to defer the loading of the script.
18 |
19 |
20 | Named outlets automatically provide two types of defered importing:
21 |
22 |
23 | The import is required immediately in order to render the page. The custom element script is imported as soon as possbile.
24 | In the screen grab below, the last script was done via an import and was loaded BEFORE the page load because it was needed to render the page.
25 |
26 |
27 |
28 |
29 |
30 | The import is not currently required to render the page but a link exists on the page containing the import. Here we pre-cache the script so when the user clicks the link the script is already downloaded. However, the import is not critical so the import is made after the first page load but before the user can click the link. This is part of PRPL design.
31 | In the screen grab below, the last script was done via an import and was loaded AFTER the page load because it was not needed to render the page but it was still loaded so it is ready if the user clocks the link for that page.
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Here is how the network time line would look if we used the import feature for all of the links on this page:
41 |
42 |
43 |
44 | The current page being loaded was the Basic Outlet Assignment page. You can see that the content-basic.js was loaded as soon as possible. The other scripts used for the pages not rendered were loaded after the page was ready.
45 |
46 | `;
47 | }
48 | }
49 |
50 | customElements.define('content-import', ContentImportElement);
--------------------------------------------------------------------------------
/examples/named-outlet/content-nested.js:
--------------------------------------------------------------------------------
1 | import { ContentBasicElement } from './content-basic.js';
2 |
3 | class ContentNestedElement extends ContentBasicElement {
4 |
5 | constructor() {
6 | super();
7 | this.description = `
8 |
9 |
10 |
11 | `;
12 | }
13 | }
14 |
15 | customElements.define('content-nested', ContentNestedElement);
--------------------------------------------------------------------------------
/examples/named-outlet/content-overview.js:
--------------------------------------------------------------------------------
1 | import { ContentBasicElement } from './content-basic.js';
2 |
3 | class ContentOverviewElement extends ContentBasicElement {
4 |
5 | constructor() {
6 | super();
7 | this.description = `
8 |
9 | The syntax of a links href attribute targeting named outlets as follows:
10 |
11 | Basic Example:
12 | <a href="(outlet-name:your-tag)" >link</a>
13 |
14 |
15 | Complete Example:
16 |
<a href="(outlet-name:your-tag(/path/to/script.js):attr-name=attrValue&.propName=propValue)" >link</a>
17 |
18 |
19 | Syntax:
20 |
<a href="({outlet-name}:{tag}[{(import-path)}]:[attr-name=attrValue&.prop-name=propValue...])" >
21 |
22 |
23 |
24 |
25 | Name
26 | Required
27 | Description
28 | Example
29 |
30 |
31 |
32 |
33 | outlet-name
34 | Required
35 | The name of the outlet whose content will be replaced with the custom element.
36 |
37 |
38 |
39 | tag
40 | Required
41 | The tag name to insert into the named outlet
42 |
43 |
44 |
45 | import-path
46 | Optional
47 | Configure if the script that defines the custom element is to be loaded on demand. Path should be absolute and wrapped in parentheses
48 | (/full/path/yourelement.js)
49 |
50 |
51 | Parameters
52 | Optional
53 | Parameters to be set on the custom element. Can be HTML attributes or JavaScript instance properties. NOTE: the outlet rendering will not replace the content if it is being replaced with the same custom element tag. Instead it will update the paramters. Make your custom element tracks attribute and property changes and performs rendering.
54 | my-attribute=value1&.myProperty=value2
55 |
56 |
57 |
58 |
59 | `;
60 | }
61 | }
62 |
63 | customElements.define('content-overview', ContentOverviewElement);
--------------------------------------------------------------------------------
/examples/named-outlet/content-prop-params.js:
--------------------------------------------------------------------------------
1 | import { ContentBasicElement } from './content-basic.js';
2 |
3 | class ContentPropParamsElement extends ContentBasicElement {
4 |
5 | constructor() {
6 | super();
7 | this.description = `
8 | Extends the previous exmaple by passing parameters (Object properties) to the customer element assigned to the outlet.
9 | `;
10 | }
11 | }
12 |
13 | customElements.define('content-prop-params', ContentPropParamsElement);
--------------------------------------------------------------------------------
/examples/named-outlet/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/named-outlet/named-outlet-example.js:
--------------------------------------------------------------------------------
1 | // import { RouterElement } from '../../build/es6-bundled/src/router.js'
2 | // import '../../src/routes-link.js';
3 | import '../../src/routes-outlet.js';
4 | import '../../src/routes-link.js';
5 |
6 | import './../shared/main-menu.js';
7 | import './../shared/code-example.js';
8 |
9 | import './content-overview.js';
10 | import './content-basic.js';
11 | import './content-nested.js';
12 | import './content-attr-params.js';
13 | import './content-prop-params.js';
14 | import './content-guards.js';
15 |
16 | class NamedOutletExampleElement extends HTMLElement {
17 |
18 | connectedCallback() {
19 | if (!this._initialized) {
20 | this._initialized = true;
21 | this.render();
22 |
23 | // Register links so they can have active state for styling
24 | // RouterElement.registerLinks(this.querySelectorAll('.ui.menu a'));
25 | // window.dispatchEvent(
26 | // new CustomEvent(
27 | // 'routerLinksAdded', {
28 | // detail: {
29 | // links: this.querySelectorAll('[code-example] a') }}));
30 | }
31 | }
32 |
33 | constructor(){
34 | super();
35 | }
36 |
37 | render() {
38 | this.innerHTML = `
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
58 |
Outlet not assigned yet. Please click a link above to assign content to this outlet.
59 |
60 |
61 |
62 | `;
63 | }
64 | }
65 |
66 | customElements.define('named-oulet-example', NamedOutletExampleElement);
--------------------------------------------------------------------------------
/examples/named-outlet/nested-one.js:
--------------------------------------------------------------------------------
1 | class NestedOne extends HTMLElement {
2 |
3 | connectedCallback() {
4 | this.innerHTML = `
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Hello Template
18 | catach all - route not found
19 |
20 |
21 |
22 | `;
23 | }
24 |
25 | constructor() {
26 | super();
27 | }
28 | }
--------------------------------------------------------------------------------
/examples/router/content-guards.js:
--------------------------------------------------------------------------------
1 | import { ContentBasicElement } from './../named-outlet/content-basic.js';
2 |
3 | class ContentGuardsElement extends ContentBasicElement {
4 |
5 | connectedCallback() {
6 | super.connectedCallback && super.connectedCallback();
7 |
8 | window.addEventListener('onRouteLeave', this.guard);
9 | }
10 |
11 | disconnectedCallback() {
12 | super.disconnectedCallback && super.disconnectedCallback();
13 |
14 | window.removeEventListener('onRouteLeave', this.guard);
15 | }
16 |
17 | constructor() {
18 | super();
19 |
20 | this.guard = this.guard.bind(this);
21 |
22 | this.description = `
23 |
24 |
25 | Disbable this check box to allow navigation away.
26 |
27 |
28 | Will not be able to leave this route whilst checkbox is enabled.
29 |
30 | Guard code
31 |
32 | window.addEventListener('onRouteLeave', guard);
33 |
34 | guard(event) {
35 | if (document.getElementById('guard').checked) {
36 | event.preventDefault();
37 | }
38 | }
39 |
40 | `;
41 | }
42 |
43 | guard(event) {
44 | if (document.getElementById('guard').checked) {
45 | event.preventDefault();
46 | }
47 | }
48 | }
49 |
50 | customElements.define('content-guards', ContentGuardsElement);
--------------------------------------------------------------------------------
/examples/router/content-named.js:
--------------------------------------------------------------------------------
1 | import './content-nested-named-viewone-example.js';
2 | import './content-nested-named-viewtwo-example.js';
3 |
4 | class ContentNamed extends HTMLElement {
5 |
6 | connectedCallback() {
7 | this.innerHTML = `
8 |
9 | This view contains a named router. There are two links below. Both links use named relative URLs to target the nested router.
10 |
11 | <a is="router-link" href="(nestedrouter:/view_one)">Nested View 1</a>
12 | <a is="router-link" href="(nestedrouter:/view_two)">Nested View 2</a>
13 |
14 | The href is made up of (RouterName:/RoutePath).
15 | You can have as many levels of nestings as you require.
16 | Using named routers is a good way to have Routers on different parts of the page (auxiliary reouters).
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | `;
31 | }
32 |
33 | constructor() {
34 | super();
35 | }
36 | }
37 |
38 | customElements.define('content-named', ContentNamed);
--------------------------------------------------------------------------------
/examples/router/content-nested-example.js:
--------------------------------------------------------------------------------
1 | class ContenNestedExample extends HTMLElement {
2 |
3 | connectedCallback() {
4 | this.innerHTML = `
5 |
6 | Content ${this.getAttribute('pagenum')}
7 |
8 | `;
9 | }
10 |
11 | constructor() {
12 | super();
13 | }
14 | }
15 |
16 | customElements.define('content-nested-example', ContenNestedExample);
--------------------------------------------------------------------------------
/examples/router/content-nested-named-viewone-example.js:
--------------------------------------------------------------------------------
1 | class ContenNestedViewoneExample extends HTMLElement {
2 |
3 | connectedCallback() {
4 | this.innerHTML = `
5 |
6 |
View One - targeted via a named router
7 | Example of a named nested routing. Only the path of the route being targeted is required as the router is targeted by name.
8 | In this case:
9 | Router Name -> nestedrouter
10 | Route -> view_one
11 | So the link was -> href="(nestedrouter:view_one)"
12 |
13 | `;
14 | }
15 |
16 | constructor() {
17 | super();
18 | }
19 | }
20 |
21 | customElements.define('content-nested-named-viewone-example', ContenNestedViewoneExample);
--------------------------------------------------------------------------------
/examples/router/content-nested-named-viewtwo-example.js:
--------------------------------------------------------------------------------
1 | class ContenNestedViewtwoExample extends HTMLElement {
2 |
3 | connectedCallback() {
4 | this.innerHTML = `
5 |
6 |
View Two - targeted via a named router
7 | Example of a named nested routing. Only the path of the route being targeted is required as the router is targeted by name.
8 | In this case:
9 | Router Name -> nestedrouter
10 | Route -> view_two
11 | So the link was -> href="(nestedrouter:view_two)"
12 |
13 | `;
14 | }
15 |
16 | constructor() {
17 | super();
18 | }
19 | }
20 |
21 | customElements.define('content-nested-named-viewtwo-example', ContenNestedViewtwoExample);
--------------------------------------------------------------------------------
/examples/router/content-nested-viewone-example.js:
--------------------------------------------------------------------------------
1 | class ContenNestedViewoneExample extends HTMLElement {
2 |
3 | connectedCallback() {
4 | this.innerHTML = `
5 |
6 |
Nested View One
7 | Example of a nested routing. The full URL most be supplied that includes any parent routes.
8 | In this case:
9 | Base URL -> /a-wc-router/examples/router/
10 | Parent route -> /nested
11 | Child route -> /viewone
12 | So the link was -> /a-wc-router/examples/router/nested/viewone
13 |
14 | `;
15 | }
16 |
17 | constructor() {
18 | super();
19 | }
20 | }
21 |
22 | customElements.define('content-nested-viewone-example', ContenNestedViewoneExample);
--------------------------------------------------------------------------------
/examples/router/content-nested-viewtwo-example.js:
--------------------------------------------------------------------------------
1 | class ContenNestedViewtwoExample extends HTMLElement {
2 |
3 | connectedCallback() {
4 | this.innerHTML = `
5 |
6 |
Nested View Two
7 | Example of a nested routing. The full URL most be supplied that includes any parent routes.
8 | In this case:
9 | Base URL -> /a-wc-router/examples/router/
10 | Parent route -> /nested
11 | Child route -> /viewtwo
12 | So the link was -> /a-wc-router/examples/router/nested/viewtwo
13 |
14 | `;
15 | }
16 |
17 | constructor() {
18 | super();
19 | }
20 | }
21 |
22 | customElements.define('content-nested-viewtwo-example', ContenNestedViewtwoExample);
--------------------------------------------------------------------------------
/examples/router/content-nested.js:
--------------------------------------------------------------------------------
1 | import './content-nested-viewone-example.js';
2 | import './content-nested-viewtwo-example.js';
3 |
4 | class ContentNested extends HTMLElement {
5 |
6 | connectedCallback() {
7 | this.innerHTML = `
8 |
9 | This view contains a nested router. There are two links below. Both links have the full URL specified to target the nested router.
10 |
11 | <a is="router-link" href="/a-wc-router/examples/router/nested/view_one">Nested View 1</a>
12 | <a is="router-link" href="/a-wc-router/examples/router/nested/view_two">Nested View 2</a>
13 |
14 | The href is made up of BaseUrl + ParentRouterUrl + NestedRouterUrl.
15 | You can have as many levels of nestings as you require.
16 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | `;
30 | }
31 |
32 | constructor() {
33 | super();
34 | }
35 | }
36 |
37 | customElements.define('content-nested', ContentNested);
--------------------------------------------------------------------------------
/examples/router/content-overview.js:
--------------------------------------------------------------------------------
1 | import { ContentBasicElement } from './../named-outlet/content-basic.js';
2 |
3 | class ContentOverviewElement extends ContentBasicElement {
4 |
5 | constructor() {
6 | super();
7 | this.description = `
8 |
9 | If a URL starts with the base URL of the page then it is a candidate for client side routing.
10 | Routes are set up like in the code above. You can have as many routes as required.
11 | URL are tested against the routes in the order the routes appear in the DOM. Once a match is found route matching is stopped. If part of the url has still to be matched then child routes are matched against the remainder url. The process continues through subsequent child routes until the url is consumed.
12 |
13 |
14 |
15 |
16 | Attribute
17 | Description
18 | Example
19 |
20 |
21 | path
22 | Required - The path that will activate this route if matched against the url. TBD data params
23 | path="/users/:userId/details"
24 |
25 |
26 | fullmatch
27 | Optional - This route must fully match the url without remainder to successfully match.
28 | fullmatch
29 |
30 |
31 | element
32 | Optional - Specifies the custom element tag to populate the outlet with for this route.
33 | element="my-custom-element"
34 |
35 |
36 | import
37 | Optional - Imports an absolute path to the script that contains the custom element to render. The import will be loaded which ever is sooner: i) when route is rendered ii) when the page has loaded
38 | import="/path/to/my-custom-element.js"
39 |
40 |
41 | lazyload
42 | Optional - Instead of loading the import after page load, it will only load te import whe it is required for the first time (when user clicks a link that requires the custom element).
43 | lazyload="true"
44 |
45 |
46 | redirect
47 | Optional - This route will perform a redirect.
48 | redirect="/users"
49 |
50 |
51 | disablecache
52 | Optional - If one URL matches a route and then another identical URL matches the same route the route contents are not reevaluated. Adding the disablecache will force the route to be evaluated and rend even if the same URL is macthed.
53 | disablecache
54 |
55 |
56 |
57 |
58 |
59 | Event Name
60 | Description
61 | preventDefault
62 | event.detail
63 |
64 |
65 | onRouteLeave
66 | Event that can be cancelled to prevent navigation away from a route. Can be used as a guard.
67 | Stops the routing from taking place
68 |
69 |
70 |
71 | Name
72 | Value
73 |
74 |
75 | route
76 | The new RouteElement being navigated to
77 |
78 |
79 |
80 |
81 |
82 | onRouteMatch
83 |
84 | Will prevent the match for this route. Preventing the match is just like the route not matching in the first place. The router will continue trying to match against other routes.
85 |
86 |
87 |
88 | Name
89 | Value
90 |
91 |
92 | route
93 | The RouteElement that made the match
94 |
95 |
96 | match
97 | The match object. Ant modifications made to the match will take effect. e.g. if you add the redirect property to the match then a redirect will occur.
98 |
99 |
100 | path
101 | The RouteElement path attribute value that had th esuccessful match.
102 |
103 |
104 |
105 |
106 |
107 | onRouteNotHandled
108 | Fire when a url is handled due to it not being the same origin or base url
109 | No effect
110 |
111 |
112 |
113 | Name
114 | Value
115 |
116 |
117 | href
118 | The href that is not being handled due to it not being the same origin or base url
119 |
120 |
121 |
122 |
123 |
124 | onRouteCancelled
125 | Fires when a routing process was cancelled
126 | No effect
127 |
128 |
129 |
130 | Name
131 | Value
132 |
133 |
134 | shortUrl
135 | The url being matched sans base url
136 |
137 |
138 |
139 |
140 |
141 | onLinkActiveStatusUpdated
142 | Fires when HTMLAnchorElement active statuses are being updated as part of a routing
143 | No effect
144 |
145 |
146 |
147 | Name
148 | Value
149 |
150 |
151 | links
152 | Array of all of the HTMLAnchorElement that are currently registered as router links
153 |
154 |
155 |
156 |
157 |
158 | onRouterAdded
159 | Fires when a router is added. Internal event used to plumb together the routers. Do not interfer with.
160 | No effect
161 |
162 |
163 |
164 | Name
165 | Value
166 |
167 |
168 | router
169 | The child RouterElement being added
170 |
171 |
172 |
173 |
174 |
175 | onOutletUpdated
176 | Fire when an OutletElement is updated with a new route or assignment.
177 | No effect
178 |
179 |
180 |
181 | `;
182 | }
183 | }
184 |
185 | customElements.define('content-overview', ContentOverviewElement);
--------------------------------------------------------------------------------
/examples/router/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/router/router-example.js:
--------------------------------------------------------------------------------
1 | // import { RouterElement } from '../../build/es6-bundled/src/router.js'
2 | import '../../src/routes-outlet.js';
3 | import '../../src/routes-link.js';
4 |
5 | import './../shared/main-menu.js';
6 | import './../shared/code-example.js';
7 |
8 | import './content-overview.js';
9 | import './content-nested.js';
10 | import './content-named.js';
11 | import './content-guards.js'
12 |
13 | class RouterExampleElement extends HTMLElement {
14 |
15 | connectedCallback() {
16 | if (!this._initialized) {
17 | this._initialized = true;
18 | this.render();
19 | }
20 | }
21 |
22 | constructor(){
23 | super();
24 | }
25 |
26 | render() {
27 | this.innerHTML = `
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
42 |
43 |
44 | This content never shows because of the last catch all route
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | Hello Template
57 | This route redirects to the Overview route.
58 |
59 |
60 |
61 |
62 | `;
63 | }
64 | }
65 |
66 | customElements.define('router-example', RouterExampleElement);
--------------------------------------------------------------------------------
/examples/shared/code-example.js:
--------------------------------------------------------------------------------
1 | export class CodeExampleElement extends HTMLElement {
2 |
3 | connectedCallback() {
4 | if (!this._initialized) {
5 | this._initialized = true;
6 | this.render();
7 | }
8 | }
9 |
10 | constructor() {
11 | super();
12 | }
13 |
14 | render() {
15 | let html = `
16 |
25 | `;
26 | let codeBlocks = document.querySelectorAll('[code-example]');
27 |
28 | for(let i = 0, iLen = codeBlocks.length; i < iLen; i++) {
29 | html += `
30 |
31 |
${codeBlocks[i].getAttribute('code-example')}
32 |
${prepareHtml(codeBlocks[i].innerHTML)}
33 |
34 | `
35 | }
36 |
37 | this.innerHTML = html;
38 | }
39 | }
40 |
41 | function prepareHtml(htmlStr) {
42 | htmlStr = htmlStr.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
43 | return htmlStr;
44 | }
45 |
46 | customElements.define('code-example', CodeExampleElement);
--------------------------------------------------------------------------------
/examples/shared/common-styles.js:
--------------------------------------------------------------------------------
1 | export class CommonStylesElement extends HTMLElement {
2 |
3 | connectedCallback() {
4 | if (!this._initialized) {
5 | this._initialized = true;
6 | this.render();
7 | }
8 | }
9 |
10 | constructor() {
11 | super();
12 | }
13 |
14 | render() {
15 | this.innerHTML = `
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 | `;
29 | }
30 | }
31 |
32 | customElements.define('common-styles', CommonStylesElement);
--------------------------------------------------------------------------------
/examples/shared/main-menu.js:
--------------------------------------------------------------------------------
1 | import './common-styles.js';
2 |
3 | export class MainMenuElement extends HTMLElement {
4 |
5 | connectedCallback() {
6 | if (!this._initialized) {
7 | this._initialized = true;
8 | this.render();
9 | }
10 | }
11 |
12 | constructor() {
13 | super();
14 | }
15 |
16 | render() {
17 | this.innerHTML = `
18 |
27 | `;
28 | }
29 | }
30 |
31 | customElements.define('main-menu', MainMenuElement);
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "es2020",
5 | "lib": ["esnext.array", "esnext", "es2017", "dom"],
6 | "rootDir": "./",
7 | "moduleResolution": "node",
8 | "checkJs": true
9 | },
10 | "include": [
11 | "src/**/*",
12 | "examples/**/*",
13 | "test/**/*"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/karma-unit.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Mon Apr 22 2019 14:22:52 GMT-0400 (Eastern Daylight Time)
3 |
4 | module.exports = config => {
5 | config.set({
6 | // base path that will be used to resolve all patterns (eg. files, exclude)
7 | basePath: '',
8 |
9 | // frameworks to use
10 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
11 | frameworks: ['jasmine'],
12 |
13 | // list of files / patterns to load in the browser
14 | files: [
15 | {
16 | pattern: 'test/unit/*.js',
17 | type: 'module',
18 | },
19 | {
20 | pattern: 'src/*.js',
21 | type: 'module',
22 | included: false,
23 | },
24 | {
25 | pattern: 'test/assets/*.js',
26 | type: 'module',
27 | included: false,
28 | },
29 | ],
30 |
31 | // list of files / patterns to exclude
32 | exclude: [],
33 |
34 | client: {
35 | jasmine: {
36 | random: false,
37 | },
38 | },
39 |
40 | // preprocess matching files before serving them to the browser
41 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
42 | preprocessors: {},
43 |
44 | // test results reporter to use
45 | // possible values: 'dots', 'progress'
46 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
47 | reporters: ['progress'],
48 |
49 | // web server port
50 | port: 9876,
51 |
52 | // enable / disable colors in the output (reporters and logs)
53 | colors: true,
54 |
55 | // level of logging
56 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
57 | logLevel: config.LOG_INFO,
58 |
59 | // enable / disable watching file and executing tests whenever any file changes
60 | autoWatch: true,
61 |
62 | // start these browsers
63 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
64 | browsers: ['ChromeHeadless'],
65 |
66 | // Continuous Integration mode
67 | // if true, Karma captures browsers, runs the tests and exits
68 | singleRun: true,
69 |
70 | // Concurrency level
71 | // how many browser should be started simultaneous
72 | concurrency: Infinity,
73 | });
74 | };
75 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "a-wc-router",
3 | "version": "2.2.0",
4 | "description": "AWC Router, Simple, Declarative, Decoupled, Web Component Router, PRPL, Router, Routing, Outlet",
5 | "main": "./src/router.js",
6 | "directories": {
7 | "test": "test"
8 | },
9 | "dependencies": {},
10 | "devDependencies": {
11 | "chai": "^4.2.0",
12 | "dev-lib-colscott": "^2.1.0",
13 | "express": "^4.17.1",
14 | "jasmine": "^3.4.0",
15 | "rimraf": "^3.0.2",
16 | "rollup": "^2.57.0",
17 | "typescript": "^4.4.3"
18 | },
19 | "scripts": {
20 | "build": "rollup -c",
21 | "generateTypes": "npx tsc -p d.tsconfig.json",
22 | "removeTypes": "rimraf ./src/**/*d.ts",
23 | "prepublishOnly": "npm run generateTypes",
24 | "start": "node exampleServer",
25 | "test": "npm run test:unit",
26 | "test:unit": "node ./node_modules/karma/bin/karma start karma-unit.conf.js"
27 | },
28 | "repository": {
29 | "type": "git",
30 | "url": "git+https://github.com/colscott/a-wc-router.git"
31 | },
32 | "keywords": [
33 | "routing",
34 | "web",
35 | "component",
36 | "router",
37 | "zero dependencies",
38 | "declarative",
39 | "wc"
40 | ],
41 | "author": "colin scott",
42 | "license": "MIT",
43 | "bugs": {
44 | "url": "https://github.com/colscott/a-wc-router/issues"
45 | },
46 | "homepage": "https://github.com/colscott/a-wc-router#readme"
47 | }
48 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | input: 'src/router.js',
3 | output: [
4 | {
5 | file: 'dist/iife/router.js',
6 | format: 'iife',
7 | },
8 | {
9 | file: 'dist/es/router.js',
10 | format: 'es',
11 | },
12 | {
13 | file: 'dist/cjs/router.js',
14 | format: 'cjs',
15 | },
16 | ],
17 | };
18 |
--------------------------------------------------------------------------------
/src/models.js:
--------------------------------------------------------------------------------
1 | export {};
2 | /**
3 | * @typedef {Object} RouterMatch
4 | * @property {Object} data
5 | * @property {string} redirect
6 | * @property {string} url
7 | * @property {boolean} useCache
8 | * @property {string} remainder
9 | */
10 |
11 | /**
12 | * @typedef {Object} NamedRoutingHandler
13 | * @property {(url: string) => Promise} processNamedUrl
14 | * @property {() => string} getName
15 | * @property {(url: string) => boolean} canLeave
16 | */
17 |
--------------------------------------------------------------------------------
/src/named-routing.js:
--------------------------------------------------------------------------------
1 | // /@ts-check
2 | /**
3 | * @typedef {Object} Assignment
4 | * @property {string} name of item the assignment is targeting
5 | * @property {string} url fragment to be assigned to the item
6 | */
7 | /**
8 | * @typedef {Object} ParseNamedOutletAssignment
9 | * @property {string} elementTag
10 | * @property {Map} data
11 | * @property {Object} options
12 | * @property {string} options.import
13 | */
14 |
15 | /**
16 | * @typedef {Object} NamedMatch
17 | * @property {string} name of the route or outlet to assign to
18 | * @property {string} url - The assignment url that was matched and consumed
19 | * @property {string} urlEscaped - The url that was matched and consumed escaped of certain characters that will break the url on servers.
20 | * @property {boolean} cancelled - If a failed attempt at assignment was made
21 | * @property {ParseNamedOutletAssignment} namedOutlet - Any named outlet assignments found
22 | */
23 | /**
24 | * Registry for named routers and outlets.
25 | * Simplifies nested routing by being able to target specific routers and outlets in a link.
26 | * Can act as a message bus of sorts. Named items being the handlers and assignments as the messages.
27 | */
28 | export class NamedRouting {
29 | /**
30 | * Adds a router or outlet to the registry
31 | * @param {import('./models').NamedRoutingHandler} item to add
32 | */
33 | static async addNamedItem(item) {
34 | const name = item.getName();
35 |
36 | if (name) {
37 | if (NamedRouting.registry[name]) {
38 | throw Error(`Error adding named item ${name}, item with that name already registered`);
39 | }
40 |
41 | NamedRouting.registry[name] = item;
42 |
43 | const assignment = NamedRouting.getAssignment(name);
44 |
45 | if (assignment && item.canLeave(assignment.url)) {
46 | await item.processNamedUrl(assignment.url);
47 | }
48 | }
49 | }
50 |
51 | /** Removes an item by name from the registry if it exists. */
52 | static removeNamedItem(name) {
53 | if (NamedRouting.registry[name]) {
54 | delete NamedRouting.registry[name];
55 | }
56 | }
57 |
58 | /** Gets an item by name from the registry */
59 | static getNamedItem(name) {
60 | return NamedRouting.registry[name];
61 | }
62 |
63 | /** Gets an assignment from the registry */
64 | static getAssignment(name) {
65 | return NamedRouting.assignments[name];
66 | }
67 |
68 | /**
69 | * Add an assignment to the registry. Will override an assignment if one already exists with the same name.
70 | * @param {string} name the name of the named item to target with the assignment
71 | * @param {string} url to assign to the named item
72 | * @returns {Promise} when assignment is completed. false is returned if the assignment was cancelled for some reason.
73 | */
74 | static async addAssignment(name, url) {
75 | const lastAssignment = NamedRouting.assignments[name];
76 | NamedRouting.assignments[name] = { name, url };
77 | const namedItem = NamedRouting.getNamedItem(name);
78 | if (namedItem) {
79 | if (namedItem.canLeave(url) === false) {
80 | NamedRouting.assignments[name] = lastAssignment;
81 | return false;
82 | }
83 |
84 | await namedItem.processNamedUrl(url);
85 | }
86 | return true;
87 | }
88 |
89 | /** Removes an assignment from the registry */
90 | static removeAssignment(name) {
91 | if (NamedRouting.assignments[name]) {
92 | delete NamedRouting.assignments[name];
93 | return true;
94 | }
95 | return false;
96 | }
97 |
98 | /** @returns {string} Serializes the current assignments into URL representation. */
99 | static generateNamedItemsUrl() {
100 | return Object.values(NamedRouting.assignments).reduce(
101 | (url, assignment) => `${url.length ? '::' : ''}${NamedRouting.generateUrlFragment(assignment)}`,
102 | '',
103 | );
104 | }
105 |
106 | /** Serializes an assignment for URL. */
107 | static generateUrlFragment(assignment) {
108 | // Polymer server does not like the period in the import statement
109 | return `(${assignment.name}:${assignment.url.replace(/\./g, '_dot_')})`;
110 | }
111 |
112 | /**
113 | * Parses a URL section and tries to get a named item from it.
114 | * @param {string} url containing the assignment and the named item
115 | * @param {boolean} [suppressAdding] of the assignment and only return the match in a dry run
116 | * @returns {Promise} null if not able to parse. If we are adding the named item then the promise is resolved when item is added and any routing has taken place.
117 | */
118 | static async parseNamedItem(url, suppressAdding) {
119 | let _url = url;
120 | if (_url[0] === '/') {
121 | _url = _url.substr(1);
122 | }
123 |
124 | if (_url[0] === '(') {
125 | _url = _url.substr(1, _url.length - 2);
126 | }
127 |
128 | const match = _url.match(/^\/?\(?([\w_-]+)\:(.*)\)?/);
129 | if (match) {
130 | // Polymer server does not like the period in the import statement
131 | const urlEscaped = match[2].replace(/_dot_/g, '.');
132 | let cancelled = false;
133 | if (suppressAdding !== true) {
134 | if ((await NamedRouting.addAssignment(match[1], urlEscaped)) === false) {
135 | cancelled = true;
136 | }
137 | }
138 | return {
139 | name: match[1],
140 | url: match[2],
141 | urlEscaped,
142 | cancelled,
143 | namedOutlet: NamedRouting.parseNamedOutletUrl(match[2]),
144 | };
145 | }
146 |
147 | return null;
148 | }
149 |
150 | /**
151 | * Takes a url for a named outlet assignment and parses
152 | * @param {string} url
153 | * @returns {ParseNamedOutletAssignment|null} null is returned if the url could not be parsed into a named outlet assignment
154 | */
155 | static parseNamedOutletUrl(url) {
156 | const match = url.match(/^([/\w-]+)(\(.*?\))?(?:\:(.+))?/);
157 | if (match) {
158 | const data = new Map();
159 |
160 | if (match[3]) {
161 | const keyValues = match[3].split('&');
162 | for (let i = 0, iLen = keyValues.length; i < iLen; i++) {
163 | const keyValue = keyValues[i].split('=');
164 | data.set(decodeURIComponent(keyValue[0]), decodeURIComponent(keyValue[1]));
165 | }
166 | }
167 | const elementTag = match[1];
168 | let importPath = match[2] && match[2].substr(1, match[2].length - 2);
169 |
170 | const inferredElementTag = NamedRouting.inferCustomElementTagName(elementTag);
171 | if (inferredElementTag === null) {
172 | return null;
173 | }
174 |
175 | if (!importPath) {
176 | importPath = NamedRouting.inferCustomElementImportPath(elementTag, inferredElementTag);
177 | }
178 |
179 | const options = { import: importPath };
180 | return {
181 | elementTag: inferredElementTag,
182 | data,
183 | options,
184 | };
185 | }
186 | return null;
187 | }
188 |
189 | /**
190 | * @param {string} importStyleTagName
191 | * @param {string} elementTag
192 | * @returns {string} the custom element import path inferred from the import style string
193 | */
194 | static inferCustomElementImportPath(importStyleTagName, elementTag) {
195 | if (customElements.get(elementTag) !== undefined) {
196 | // tag is loaded. no need for import.
197 | return undefined;
198 | }
199 |
200 | let inferredPath = importStyleTagName;
201 |
202 | const lastForwardSlash = inferredPath.lastIndexOf('/');
203 | if (lastForwardSlash === -1) {
204 | inferredPath = `/${inferredPath}`;
205 | }
206 |
207 | const dotIndex = inferredPath.indexOf('.');
208 | if (dotIndex === -1) {
209 | inferredPath += '.js';
210 | }
211 |
212 | return inferredPath;
213 | }
214 |
215 | /**
216 | * @param {string} elementTag
217 | * @returns {string} the custom element tag name inferred from import style string
218 | */
219 | static inferCustomElementTagName(elementTag) {
220 | let inferredTagName = elementTag;
221 |
222 | // get class name from path
223 | const lastForwardSlash = inferredTagName.lastIndexOf('/');
224 | if (lastForwardSlash > -1) {
225 | inferredTagName = inferredTagName.substring(lastForwardSlash + 1);
226 | }
227 |
228 | // get class name from file name
229 | const dotIndex = inferredTagName.indexOf('.');
230 | if (dotIndex > -1) {
231 | inferredTagName = inferredTagName.substring(0, dotIndex - 1);
232 | }
233 |
234 | // to kebab case
235 | inferredTagName = inferredTagName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
236 |
237 | if (inferredTagName.indexOf('-') === -1) {
238 | inferredTagName = null;
239 | }
240 |
241 | return inferredTagName;
242 | }
243 |
244 | /**
245 | * Pre-fetches an import module so that it is available when the link is activated
246 | * @param {NamedMatch} namedAssignment item assignment
247 | * @returns {Promise} resolves when the import is completed
248 | */
249 | static async prefetchNamedOutletImports(namedAssignment) {
250 | if (
251 | namedAssignment.namedOutlet &&
252 | namedAssignment.namedOutlet.options &&
253 | namedAssignment.namedOutlet.options.import
254 | ) {
255 | await NamedRouting.pageReady();
256 | await NamedRouting.importCustomElement(
257 | namedAssignment.namedOutlet.options.import,
258 | namedAssignment.namedOutlet.elementTag,
259 | );
260 | }
261 | }
262 |
263 | /**
264 | * Imports a script for a customer element once the page has loaded
265 | * @param {string} importSrc
266 | * @param {string} tagName
267 | */
268 | static async prefetchImport(importSrc, tagName) {
269 | await NamedRouting.pageReady();
270 | await NamedRouting.importCustomElement(importSrc, tagName);
271 | }
272 |
273 | /**
274 | * Imports a script for a customer element
275 | * @param {string} importSrc
276 | * @param {string} tagName
277 | */
278 | static async importCustomElement(importSrc, tagName) {
279 | if (importSrc && customElements.get(tagName) === undefined) {
280 | // @ts-ignore
281 | await import(/* webpackIgnore: true */ importSrc);
282 | }
283 | }
284 |
285 | /**
286 | *
287 | */
288 | static pageReady() {
289 | if (!NamedRouting.pageReadyPromise) {
290 | NamedRouting.pageReadyPromise =
291 | document.readyState === 'complete'
292 | ? Promise.resolve()
293 | : new Promise((resolve, reject) => {
294 | /** handle readystatechange callback */
295 | const callback = () => {
296 | if (document.readyState === 'complete') {
297 | document.removeEventListener('readystatechange', callback);
298 | resolve();
299 | }
300 | };
301 | document.addEventListener('readystatechange', callback);
302 | });
303 | }
304 |
305 | return NamedRouting.pageReadyPromise;
306 | }
307 |
308 | /**
309 | * Called just before leaving for another route.
310 | * Fires an event 'routeOnLeave' that can be cancelled by preventing default on the event.
311 | * @fires RouteElement#onRouteLeave
312 | * @param {*} newRoute - the new route being navigated to
313 | * @returns bool - if the currently active route can be left
314 | */
315 | static canLeave(newRoute) {
316 | /**
317 | * Event that can be cancelled to prevent this route from being navigated away from.
318 | * @event RouteElement#onRouteLeave
319 | * @type CustomEvent
320 | * @property {Object} details - The event details
321 | * @property {RouteElement} details.route - The RouteElement that performed the match.
322 | */
323 | const canLeaveEvent = new CustomEvent('onRouteLeave', {
324 | bubbles: true,
325 | cancelable: true,
326 | composed: true,
327 | detail: { route: newRoute },
328 | });
329 | // @ts-ignore
330 | // This method is designed to be bound to a Custom Element instance. It located in here for general visibility.
331 | this.dispatchEvent(canLeaveEvent);
332 | return !canLeaveEvent.defaultPrevented;
333 | }
334 | }
335 |
336 | NamedRouting.pageReadyPromise = undefined;
337 | NamedRouting.registry = {};
338 | /** @type {{[k: string]: Assignment}} */
339 | NamedRouting.assignments = {};
340 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import './routes-link.js';
2 |
3 | export * from './named-routing.js';
4 | export * from './routes-router.js';
5 | export * from './routes-route.js';
6 | export * from './routes-outlet.js';
7 |
--------------------------------------------------------------------------------
/src/routes-link.js:
--------------------------------------------------------------------------------
1 | import { RouterElement } from './routes-router.js';
2 |
3 | /** */
4 | class RouterLinkElement extends HTMLAnchorElement {
5 | /** @inheritdoc */
6 | connectedCallback() {
7 | RouterElement.initialize();
8 | this.register();
9 | }
10 |
11 | /** @inheritdoc */
12 | static get observedAttributes() {
13 | return ['href'];
14 | }
15 |
16 | /**
17 | * @inheritdoc
18 | * Listens for href attribute changing. If it does then it re-registers the link.
19 | */
20 | attributeChangedCallback(name, oldValue, newValue) {
21 | if (name === 'href') {
22 | if (oldValue && newValue) {
23 | this.register();
24 | }
25 | }
26 | }
27 |
28 | /** @inheritdoc */
29 | constructor() {
30 | super();
31 | }
32 |
33 | /** Helper to dispatch events that will signal the registering of links. */
34 | register() {
35 | window.dispatchEvent(
36 | new CustomEvent('routerLinksAdded', {
37 | detail: {
38 | links: [this],
39 | },
40 | }),
41 | );
42 | }
43 | }
44 |
45 | window.customElements.define('router-link', RouterLinkElement, { extends: 'a' });
46 |
--------------------------------------------------------------------------------
/src/routes-outlet.js:
--------------------------------------------------------------------------------
1 | import { NamedRouting } from './named-routing.js';
2 | import { RouterElement } from './routes-router.js';
3 | import { RouteElement } from './routes-route.js';
4 |
5 | /** */
6 | export class OutletElement extends HTMLElement {
7 | /** Initialize */
8 | async connectedCallback() {
9 | if (this.isConnected) {
10 | if (!this.created) {
11 | this.created = true;
12 | // var p = document.createElement('p');
13 | // p.textContent = 'Please add your routes!';
14 | // this.appendChild(p);
15 |
16 | await NamedRouting.addNamedItem(this);
17 | }
18 | await RouterElement.initialize();
19 | }
20 | }
21 |
22 | /** Dispose */
23 | disconnectedCallback() {
24 | if (this.getName()) {
25 | NamedRouting.removeNamedItem(this.getName());
26 | }
27 | }
28 |
29 | /** Initialize */
30 | constructor() {
31 | super();
32 |
33 | this.canLeave = NamedRouting.canLeave.bind(this);
34 | }
35 |
36 | /** @returns {string} value of the attribute called name. Can not be changed was set. */
37 | getName() {
38 | if (this.outletName === undefined) {
39 | this.outletName = this.getAttribute('name');
40 | }
41 | return this.outletName;
42 | }
43 |
44 | /**
45 | * @private
46 | * @param {string} url to parse
47 | * @returns url broken into segments
48 | */
49 | _createPathSegments(url) {
50 | return url.replace(/(^\/+|\/+$)/g, '').split('/');
51 | }
52 |
53 | /**
54 | * Replaces the content of this outlet with the supplied new content
55 | * @fires OutletElement#onOutletUpdated
56 | * @param {string|DocumentFragment|Node} content - Content that will replace the current content of the outlet
57 | */
58 | renderOutletContent(content) {
59 | this.innerHTML = '';
60 | // console.info('outlet rendered: ' + this.outletName, content);
61 |
62 | if (typeof content === 'string') {
63 | this.innerHTML = content;
64 | } else {
65 | this.appendChild(content);
66 | }
67 |
68 | this.dispatchOutletUpdated();
69 | }
70 |
71 | /**
72 | * Takes in a url that contains named outlet data and renders the outlet using the information
73 | * @param {string} url to parse and create outlet content for
74 | * @returns {Promise} that was added to the outlet as a result of processing the named url
75 | */
76 | async processNamedUrl(url) {
77 | const details = NamedRouting.parseNamedOutletUrl(url);
78 | const options = details.options || { import: null };
79 | let data = details.data || new Map();
80 |
81 | if (data instanceof Map === false) {
82 | data = new Map(Object.entries(data || {}));
83 | }
84 |
85 | // If same tag name then just set the data
86 | if (this.children && this.children[0] && this.children[0].tagName.toLowerCase() === details.elementTag) {
87 | RouteElement.setData(this.children[0], data);
88 | this.dispatchOutletUpdated();
89 | return;
90 | }
91 |
92 | await NamedRouting.importCustomElement(options.import, details.elementTag);
93 |
94 | const element = document.createElement(details.elementTag);
95 | RouteElement.setData(element, data);
96 |
97 | if (customElements.get(details.elementTag) === undefined) {
98 | console.error(
99 | `Custom Element not found: ${details.elementTag}. Are you missing an import or mis-spelled tag name?`,
100 | );
101 | }
102 |
103 | this.renderOutletContent(element);
104 | }
105 |
106 | /** Dispatch the onOutletUpdate event */
107 | dispatchOutletUpdated() {
108 | /**
109 | * Outlet updated event that fires after an Outlet replaces it's content.
110 | * @event OutletElement#onOutletUpdated
111 | * @type CustomEvent
112 | * @property {any} - Currently no information is passed in the event.
113 | */
114 | this.dispatchEvent(
115 | new CustomEvent('onOutletUpdated', {
116 | bubbles: true,
117 | composed: true,
118 | detail: {},
119 | }),
120 | );
121 | }
122 | }
123 |
124 | window.customElements.define('a-outlet', OutletElement);
125 | window.customElements.define('an-outlet', class extends OutletElement {});
126 |
--------------------------------------------------------------------------------
/src/routes-route.js:
--------------------------------------------------------------------------------
1 | import { NamedRouting } from './named-routing.js';
2 |
3 | /** @typedef {Map|HTMLOrSVGElement['dataset']} MatchData */
4 | /**
5 | * @typedef {Object} Match
6 | * @property {string} url - The url that was matched and consumed by this route. The match.url and the match.remainder will together equal the URL that the route originally matched against.
7 | * @property {string} remainder - If the route performed a partial match, the remainder of the URL that was not attached is stored in this property.
8 | * @property {Map} data - Any data found and matched in the URL.
9 | * @property {?string} redirect - A URL to redirect to.
10 | * @property {boolean} useCache - Indicator as to wether the current HTML content can be reused.
11 | */
12 |
13 | /** */
14 | export class RouteElement extends HTMLElement {
15 | /** Initialize */
16 | connectedCallback() {
17 | if (!this.created) {
18 | this.created = true;
19 | this.style.display = 'none';
20 | const baseElement = document.head.querySelector('base');
21 | this.baseUrl = baseElement && baseElement.getAttribute('href');
22 | }
23 |
24 | if (this.isConnected) {
25 | const onRouteAdded = new CustomEvent('onRouteAdded', {
26 | bubbles: true,
27 | composed: true,
28 | detail: {
29 | route: this,
30 | },
31 | });
32 |
33 | this.dispatchEvent(onRouteAdded);
34 |
35 | const lazyLoad = (this.getAttribute('lazyload') || '').toLowerCase() === 'true' || this.hasAttribute('lazy-load');
36 |
37 | if (lazyLoad === false) {
38 | const importAttr = this.getAttribute('import');
39 | const tagName = this.getAttribute('element');
40 | NamedRouting.prefetchImport(importAttr, tagName);
41 | }
42 | }
43 | }
44 |
45 | /** Initialize */
46 | constructor() {
47 | super();
48 |
49 | this.canLeave = NamedRouting.canLeave.bind(this);
50 |
51 | /** @type {string|DocumentFragment|Node} */
52 | this.content = null;
53 |
54 | this.data = null;
55 | }
56 |
57 | /**
58 | * @private
59 | * @param {string} url to break into segments
60 | * @returns {Array} string broken into segments
61 | */
62 | _createPathSegments(url) {
63 | return url.replace(/(^\/+|\/+$)/g, '').split('/');
64 | }
65 |
66 | /**
67 | * Performs matching and partial matching. In order to successfully match, a RouteElement elements path attribute must match from the start of the URL. A full match would completely match the URL. A partial match would return from the start.
68 | * @fires RouteElement#onROuteMatch
69 | * @param {string} url - The url to perform matching against
70 | * @returns {Match} match - The resulting match. Null will be returned if no match was made.
71 | */
72 | match(url) {
73 | const urlSegments = this._createPathSegments(url);
74 |
75 | const path = this.getAttribute('path');
76 | if (!path) {
77 | console.info('route must contain a path');
78 | throw new Error('Route has no path defined. Add a path attribute to route');
79 | }
80 |
81 | const fullMatch = {
82 | url,
83 | remainder: '',
84 | data: new Map(),
85 | redirect: null,
86 | useCache: false,
87 | };
88 |
89 | let match = fullMatch;
90 |
91 | if (path === '*') {
92 | match = fullMatch;
93 | } else if (path === url) {
94 | match = fullMatch;
95 | } else {
96 | const pathSegments = this._createPathSegments(path);
97 | // console.info(urlSegments, pathSegments);
98 | const data = match.data;
99 |
100 | const max = pathSegments.length;
101 | let i = 0;
102 | for (; i < max; i++) {
103 | if (pathSegments[i] && pathSegments[i].charAt(0) === ':') {
104 | // Handle bound values
105 | const paramName = pathSegments[i].replace(/(^\:|[+*?]+\S*$)/g, '');
106 | const flags = (pathSegments[i].match(/([+*?])\S*$/) || [])[1] || '';
107 | const oneOrMore = flags.includes('+');
108 | const anyNumber = flags.includes('*');
109 | const oneOrNone = flags.includes('?');
110 | const defaultValue = oneOrNone && (pathSegments[i].match(/[+*?]+(\S+)$/) || [])[1] || '';
111 | let value = urlSegments[i] || '';
112 | const required = !anyNumber && !oneOrNone;
113 | if (!value && defaultValue) {
114 | value = defaultValue;
115 | }
116 | if (!value && required) {
117 | match = null;
118 | break;
119 | }
120 | data.set(paramName, decodeURIComponent(value));
121 | if (oneOrMore || anyNumber) {
122 | data.set(
123 | paramName,
124 | urlSegments
125 | .slice(i)
126 | .map(decodeURIComponent)
127 | .join('/'),
128 | );
129 | // increase i so that we know later that we have consumed all of the url segments when we're checking if we have a full match.
130 | i = urlSegments.length;
131 | break;
132 | }
133 | } else if (pathSegments[i] !== urlSegments[i]) {
134 | // Handle path segment
135 | match = null;
136 | break;
137 | }
138 | }
139 |
140 | // Check all required path segments were fulfilled
141 | if (match) {
142 | if (i >= urlSegments.length) {
143 | // Full match
144 | } else if (this.hasAttribute('fullmatch')) {
145 | // Partial match but needed full match
146 | match = null;
147 | } else if (i === max) {
148 | // Partial match
149 | match = match || fullMatch;
150 | match.data = data;
151 | match.url = urlSegments.slice(0, i).join('/');
152 | match.remainder = urlSegments.slice(i).join('/');
153 | } else {
154 | // No match
155 | match = null;
156 | }
157 | }
158 | }
159 |
160 | if (match !== null) {
161 | /**
162 | * Route Match event that fires after a route has performed successful matching. The event can be cancelled to prevent the match.
163 | * @event RouteElement#onRouteMatch
164 | * @type CustomEvent
165 | * @property {Object} details - The event details
166 | * @property {RouteElement} details.route - The RouteElement that performed the match.
167 | * @property {Match} details.match - The resulting match. Warning, modifications to the Match will take effect.
168 | * @property {string} details.path - The RouteElement path attribute value that was matched against.
169 | */
170 | const routeMatchedEvent = new CustomEvent('onRouteMatch', {
171 | bubbles: true,
172 | cancelable: true,
173 | composed: true,
174 | detail: { route: this, match, path },
175 | });
176 | this.dispatchEvent(routeMatchedEvent);
177 |
178 | if (routeMatchedEvent.defaultPrevented) {
179 | match = null;
180 | }
181 |
182 | if (this.hasAttribute('redirect')) {
183 | match.redirect = this.getAttribute('redirect');
184 | }
185 | }
186 |
187 | if (match) {
188 | const useCache = this.lastMatch && this.lastMatch.url === match.url && !this.hasAttribute('disableCache');
189 | match.useCache = !!useCache;
190 | }
191 |
192 | this.lastMatch = match;
193 |
194 | return match;
195 | }
196 |
197 | /** Clear the last match which will reset cache state */
198 | clearLastMatch() {
199 | this.lastMatch = null;
200 | }
201 |
202 | /**
203 | * Generates content for this route.
204 | * @param {Map} [attributes] - Object of properties that will be applied to the content. Only applies if the content was not generated form a Template.
205 | * @returns {Promise} - The resulting generated content.
206 | */
207 | async getContent(attributes) {
208 | let { content } = this;
209 |
210 | if (!content) {
211 | const importAttr = this.getAttribute('import');
212 | const tagName = this.getAttribute('element');
213 |
214 | await NamedRouting.importCustomElement(importAttr, tagName);
215 |
216 | if (tagName) {
217 | // TODO support if tagName is a function that is called and will return the content
218 | // content = tagName(attributes);
219 | content = document.createElement(tagName);
220 | if (customElements.get(tagName) === undefined) {
221 | console.error(`Custom Element not found: ${tagName}. Are you missing an import or mis-spelled the tag name?`);
222 | }
223 | }
224 |
225 | const template = this.children[0];
226 | if (template && template instanceof HTMLTemplateElement) {
227 | return template.content.cloneNode(true);
228 | }
229 | }
230 |
231 | if (this.data && content instanceof HTMLElement) {
232 | Object.entries(this.data).forEach(([name, value]) => {
233 | content[name] = value;
234 | });
235 | }
236 |
237 | RouteElement.setData(content, this.dataset);
238 |
239 | // Set attributes last so they override any static properties with the same name
240 | if (attributes) {
241 | RouteElement.setData(content, attributes);
242 | }
243 |
244 | this.content = content;
245 | return this.content;
246 | }
247 |
248 | /**
249 | * @param {string|DocumentFragment|Node} target element to set the data on
250 | * @param {MatchData} data to set on the element
251 | */
252 | static setData(target, data) {
253 | if (data && target instanceof Element) {
254 | /**
255 | * @param {string} key property name to set the value for
256 | * @param {unknown} value value to set
257 | */
258 | const setProperty = (key, value) => {
259 | if (key[0] === '.') {
260 | target[key.substring(1)] = value;
261 | } else {
262 | target.setAttribute(key, value.toString());
263 | }
264 | }
265 |
266 | if (data instanceof Map) {
267 | data.forEach(((value, key) => setProperty(key, value)));
268 | } else {
269 | Object.entries(data).forEach(([key, value]) => setProperty(key, value));
270 | }
271 | }
272 | }
273 | }
274 |
275 | window.customElements.define('a-route', RouteElement);
276 |
--------------------------------------------------------------------------------
/test/assets/test-dummy-two.js:
--------------------------------------------------------------------------------
1 |
2 | class TestDummyTwoElement extends HTMLElement {
3 |
4 | connectedCallback(){
5 | if (!this.created) {
6 | this.created = true;
7 | var p = document.createElement('p');
8 | var content = 'Test Element Two';
9 |
10 | p.textContent = content;
11 | this.appendChild(p);
12 | }
13 | }
14 |
15 | constructor() {
16 | super();
17 | }
18 | }
19 |
20 | window.customElements.define('test-dummy-two', TestDummyTwoElement);
--------------------------------------------------------------------------------
/test/assets/test-dummy.js:
--------------------------------------------------------------------------------
1 |
2 | class TestDummyElement extends HTMLElement {
3 |
4 | render() {
5 | this.innerHTML = `Test Element${this.getRequiredParam()}
`;
6 | }
7 |
8 | getRequiredParam() {
9 | if (this.hasAttribute('requiredParam')) {
10 | return ' ' + this.getAttribute('requiredParam');
11 | }
12 |
13 | return '';
14 | }
15 |
16 | static get observedAttributes() { return ['requiredparam']; }
17 |
18 | attributeChangedCallback(name, oldValue, newValue) {
19 | if (name == 'requiredparam') {
20 | this.render();
21 | }
22 | }
23 |
24 | // TODO test fails because same element instance is reused but the attributes are not ebing rendered after beind dynamically changed.
25 |
26 | connectedCallback() {
27 | this.render();
28 | }
29 |
30 | constructor() {
31 | super();
32 | }
33 | }
34 |
35 | window.customElements.define('test-dummy', TestDummyElement);
--------------------------------------------------------------------------------
/test/unit/end2end.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | /* globals expect,describe,afterEach,it */
3 | import { NamedRouting } from '../../src/named-routing.js';
4 | import { RouterElement } from '../../src/routes-router.js';
5 | import '../../src/routes-route.js';
6 | import '../../src/routes-outlet.js';
7 |
8 | const testDelay = 100;
9 | /** Web Component be be used by suite of end to end tests */
10 | class End2EndElement extends HTMLElement {
11 | /** Initialize */
12 | connectedCallback() {
13 | if (this.isConnected) {
14 | this.render();
15 | }
16 | }
17 |
18 | /** Set up HTML for tests */
19 | render() {
20 | this.innerHTML = `
21 |
22 |
23 |
24 |
25 | Hello Template
26 | Only hit if template route cancelled
27 |
28 |
29 |
30 | Content with nested router
31 |
32 |
33 | Hello Nested
34 |
35 |
36 |
37 | Nested router with data
38 |
39 |
40 |
41 |
42 |
43 |
44 | catch all - NotFound1
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Foobar
56 | Barfoo
57 | catch all - NotFound2
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | Main View 1
69 |
70 |
71 | Main View 2
72 |
73 |
74 |
75 |
76 |
77 | Main2 View 1
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | Sec View 1
91 |
92 |
93 | Sec View 2
94 |
95 |
96 |
97 |
98 |
99 | Sec2 View 1
100 |
101 |
102 |
103 |
104 |
105 |
106 | `;
107 | }
108 | }
109 |
110 | window.customElements.define('end-to-end', End2EndElement);
111 | const baseUrl = document.createElement('base');
112 | baseUrl.setAttribute('href', '/myapp/');
113 | document.head.appendChild(baseUrl);
114 | document.body.appendChild(document.createElement('end-to-end'));
115 |
116 | document.querySelector('#data-property').data = {
117 | propA: 'foobar',
118 | propB: 123,
119 | propC: true,
120 | propD: null,
121 | propE: undefined,
122 | propF: new Date(),
123 | };
124 |
125 | /**
126 | * @param {HTMLAnchorElement} link
127 | * @returns resulting navigate event
128 | */
129 | function fireNavigate(link) {
130 | const navigateEvent = new CustomEvent('navigate', {
131 | detail: {
132 | href: link,
133 | onNavigated: undefined,
134 | },
135 | });
136 | window.dispatchEvent(navigateEvent);
137 | return navigateEvent;
138 | }
139 |
140 | /**
141 | * @param {string|HTMLAnchorElement|{ href: string }} href url or link to click and navigate to
142 | * @param {() => void} [done]
143 | * @returns {Promise} onNavigated promise of the navigate event
144 | */
145 | function click(href, done) {
146 | let link = href instanceof HTMLAnchorElement ? href : undefined;
147 | let removeLink = false;
148 | if (!link) {
149 | link = /** @type {HTMLAnchorElement} */ (document.createElement('A'));
150 | link.href = typeof href === 'string' ? href : href.href;
151 | document.body.appendChild(link);
152 | removeLink = true;
153 | }
154 | const navEvent = fireNavigate(link);
155 | console.info(navEvent.detail.onNavigated, link.href, link);
156 | return navEvent.detail.onNavigated.then(() => {
157 | removeLink && link.remove();
158 | done && done();
159 | return navEvent.detail.onNavigated;
160 | });
161 | }
162 |
163 | /**
164 | * @param {{ href: string }} linkDetails to navigate to
165 | * @param {string|((HTMLElement) => void)} expectedTextOrHtmlContent to test is present
166 | * @param {'myoutlet1'|'outletA'|'outletB'|'outletC'} outletId of the outlet that will be updated bu the navigation
167 | * @param {() => void} [done]
168 | * @returns {Promise}
169 | */
170 | function clickAndTest(linkDetails, expectedTextOrHtmlContent, outletId, done) {
171 | const _outletId = outletId || 'outletA';
172 | console.group(`test: ${linkDetails.href}`);
173 |
174 | /** @type {Promise} */
175 | let onNavigated = null;
176 |
177 | /** @param {CustomEvent} event */
178 | function callback(event) {
179 | const target = event.target instanceof HTMLElement && event.target;
180 | document.body.removeEventListener('onOutletUpdated', callback);
181 | console.info(`callback for ${linkDetails.href}`);
182 | console.info(`outlet updated: ${target.id}-----${target.innerText}`);
183 | if (!_outletId || _outletId === target.id) {
184 | if (expectedTextOrHtmlContent instanceof Function) {
185 | expectedTextOrHtmlContent(event.target);
186 | } else {
187 | expect(target.innerHTML).toContain(expectedTextOrHtmlContent);
188 | }
189 | }
190 | }
191 |
192 | document.body.addEventListener('onOutletUpdated', callback);
193 |
194 | onNavigated = click(linkDetails, done).then(event => {
195 | document.body.removeEventListener('onOutletUpdated', callback);
196 | console.info(`call done for ${linkDetails.href}`);
197 | console.groupEnd();
198 | });
199 |
200 | return onNavigated;
201 | }
202 |
203 | /**
204 | * @param {{href: string}} linkDetails
205 | * @param {() => void} [done]
206 | * @returns {Promise}
207 | */
208 | function clickAndNotHandle(linkDetails, done) {
209 | console.info(`clickAndNotHandle: ${linkDetails.href}`);
210 | const clickCallback = /** @param {Event} event */ event => {
211 | window.removeEventListener('click', clickCallback);
212 | event.preventDefault();
213 | };
214 | window.addEventListener('click', clickCallback, false);
215 |
216 | let notHandled = false;
217 | const notHandledCallback = /** @param {Event} event */ event => {
218 | notHandled = true;
219 | };
220 | document.body.addEventListener('onRouteNotHandled', notHandledCallback);
221 |
222 | return click(linkDetails).then(() => {
223 | document.body.removeEventListener('onRouteNotHandled', notHandledCallback);
224 | expect(notHandled).toBe(true);
225 | console.info(`route not handled: ${linkDetails.href}`);
226 | console.info(`call done for ${linkDetails.href}`);
227 | done && done();
228 | });
229 | }
230 |
231 | afterEach(done => setTimeout(done, testDelay));
232 |
233 | describe('link state', () => {
234 | it('link should be active for nested routes', async () => {
235 | const link1 = document.createElement('a');
236 | link1.href = 'nested/webcomponent_nested';
237 | document.body.appendChild(link1);
238 |
239 | const link2 = document.createElement('a');
240 | link2.href = 'nested2/webcomponent_nested';
241 | document.body.appendChild(link2);
242 |
243 | await RouterElement.registerLinks([link1, link2], 'active');
244 |
245 | let onLinkActiveStatusUpdated = 0;
246 | // eslint-disable-next-line require-jsdoc
247 | const handler = () => {
248 | onLinkActiveStatusUpdated += 1;
249 | };
250 |
251 | window.addEventListener('onLinkActiveStatusUpdated', handler);
252 | await click(link1);
253 | expect(link1.className).toEqual('active');
254 | expect(link2.className).toEqual('');
255 | console.info(`There are ${document.querySelectorAll('a').length} links`);
256 | expect(onLinkActiveStatusUpdated).toBeGreaterThan(0);
257 | link1.remove();
258 | link2.remove();
259 | window.removeEventListener('onLinkActiveStatusUpdated', handler);
260 | });
261 |
262 | it('link should be active for named outlet', () => {
263 | const link1 = document.createElement('a');
264 | document.body.appendChild(link1);
265 | link1.href = '(myoutlet1:test-dummy(/base/test/assets/test-dummy.js))';
266 | const link2 = document.createElement('a');
267 | document.body.appendChild(link2);
268 | link2.href = '(myoutlet:tests-dummy(/base/test/assets/test-dummy.js))';
269 | RouterElement.registerLinks([link1, link2], 'active');
270 |
271 | let linkStatusesUpdate = false;
272 | // eslint-disable-next-line require-jsdoc
273 | const onLinkActiveStatusUpdatedCallback = event => {
274 | linkStatusesUpdate = true;
275 | };
276 | window.addEventListener('onLinkActiveStatusUpdated', onLinkActiveStatusUpdatedCallback);
277 |
278 | return click({ href: '(myoutlet1:test-dummy(/base/test/assets/test-dummy.js))' }).then(() => {
279 | window.removeEventListener('onLinkActiveStatusUpdated', onLinkActiveStatusUpdatedCallback);
280 | NamedRouting.removeAssignment('myoutlet1');
281 | expect(linkStatusesUpdate).toBe(true);
282 | expect(link1.className).toEqual('active');
283 | expect(link2.className).toEqual('');
284 | link1.remove();
285 | link2.remove();
286 | });
287 | });
288 |
289 | it('link should be active for named outlet with data', () => {
290 | const link1 = document.createElement('a');
291 | document.body.appendChild(link1);
292 | link1.href = '(myoutlet1:test-dummy(/base/test/assets/test-dummy.js):param1=value1)';
293 | const link2 = document.createElement('a');
294 | document.body.appendChild(link2);
295 | link2.href = '(myoutlet1:test-dummy(/base/test/assets/test-dummy.js)):param1=value2';
296 | const link3 = document.createElement('a');
297 | document.body.appendChild(link3);
298 | link3.href = '(myoutlet1:test-dummy(/base/test/assets/test-dummy.js))';
299 | RouterElement.registerLinks([link1, link2, link3], 'active');
300 |
301 | let linkStatusesUpdate = false;
302 | // eslint-disable-next-line require-jsdoc
303 | const outletUpdateCallback = event => {
304 | linkStatusesUpdate = true;
305 | };
306 | window.addEventListener('onLinkActiveStatusUpdated', outletUpdateCallback);
307 |
308 | return click({ href: '(myoutlet1:test-dummy(/base/test/assets/test-dummy.js):param1=value1)' }).then(() => {
309 | window.removeEventListener('onLinkActiveStatusUpdated', outletUpdateCallback);
310 | NamedRouting.removeAssignment('myoutlet1');
311 | expect(linkStatusesUpdate).toBe(true);
312 | expect(link1.className).toEqual('active');
313 | expect(link2.className).toEqual('');
314 | expect(link3.className).toEqual('active');
315 | link1.remove();
316 | link2.remove();
317 | link3.remove();
318 | });
319 | });
320 |
321 | it('link should be active for named routes', () => {
322 | const link1 = document.createElement('a');
323 | document.body.appendChild(link1);
324 | link1.href = '(router-a-b:template2_nested)';
325 | const link2 = document.createElement('a');
326 | document.body.appendChild(link2);
327 | link2.href = '(router-a-b:template_nested)';
328 | RouterElement.registerLinks([link1, link2], 'active');
329 | return click({ href: 'nested/webcomponent_nested' }).then(() => {
330 | return clickAndTest({ href: '(router-a-b:template_nested)' }, 'Hello Nested', 'outletA').then(() => {
331 | NamedRouting.removeAssignment('router-a-b');
332 | expect(link1.className).toEqual('');
333 | expect(link2.className).toEqual('active');
334 | link1.remove();
335 | link2.remove();
336 | });
337 | });
338 | });
339 | });
340 |
341 | describe('named outlets', () => {
342 | it('should show named outlet', () => {
343 | return click({ href: '(myoutlet1:test-dummy(/base/test/assets/test-dummy.js))' }).then(() => {
344 | const content = document.querySelector("[name='myoutlet1']").innerHTML;
345 | expect(content).toContain('test-dummy');
346 | });
347 | });
348 |
349 | it('updates for clicked links', () => {
350 | return clickAndTest(
351 | { href: '(myoutlet1:test-dummy(/base/test/assets/test-dummy.js):requiredParam=named outlet,testing)' },
352 | 'Test Element named outlet,testing',
353 | 'myoutlet1',
354 | );
355 | });
356 |
357 | it('should support convention based importing', () => {
358 | return clickAndTest({ href: '(myoutlet1:/base/test/assets/test-dummy-two)' }, 'Test Element Two', 'myoutlet1');
359 | });
360 | });
361 |
362 | describe('named routers', () => {
363 | it('updates for clicked links', async () => {
364 | await click({ href: 'nested/webcomponent_nested' });
365 | await clickAndTest({ href: '(router-a-b:template_nested)' }, 'Hello Nested', 'outletB');
366 | NamedRouting.removeAssignment('router-a-b');
367 | });
368 | });
369 |
370 | describe('routes-router', () => {
371 | describe('simple flat', () => {
372 | it('template based route works', () => clickAndTest({ href: 'template' }, 'Hello Template', 'outletA'));
373 |
374 | it('web component based route works with import', () =>
375 | clickAndTest({ href: 'webcomponent' }, 'Test Element
', 'outletA'));
376 |
377 | it('nomatch should hit catch all', () => clickAndTest({ href: 'nomatch' }, 'catch all - NotFound2', 'outletA'));
378 |
379 | it('404 if no match and no catch all', () => {
380 | const route = document.getElementById('catch-all');
381 | route.setAttribute('path', 'other');
382 | return clickAndTest({ href: 'exception' }, '404', 'outletA').then(() => {
383 | route.setAttribute('path', '*');
384 | });
385 | });
386 |
387 | // TODO
388 | // it('only white listed routes should match', function (done) {
389 | // var route = document.getElementById('catch-all');
390 | // route.setAttribute('path', 'other');
391 | // clickAndTest(
392 | // { href: "exception" },
393 | // (outlet) => {
394 | // expect(outlet.innerHTML).toContain('404');
395 | // route.setAttribute('path', '*');
396 | // },
397 | // done);
398 | // });
399 |
400 | it('absolute path should match', () => clickAndTest({ href: '/myapp/template' }, 'Hello Template', 'outletA'));
401 |
402 | it("shouldn't handle different base urls", () => {
403 | return clickAndNotHandle({ href: '/myotherapp/template' });
404 | });
405 |
406 |
407 | it('should correctly match multi level path name', async () => {
408 | await clickAndTest({ href: 'a/b' }, 'Foobar', 'outletA');
409 | await clickAndTest({ href: 'a/c' }, 'Barfoo', 'outletA');
410 | });
411 | });
412 |
413 | describe('auxiliary routing', () => {
414 | it('Should route auxiliary', async () => {
415 | const routerA = document.getElementById('router-a');
416 | // @ts-ignore
417 | const auxiliaryRouting = document.getElementById('auxiliary-routing').content.cloneNode(true);
418 | routerA.parentNode.insertBefore(auxiliaryRouting, routerA.nextSibling);
419 | await click({
420 | href: '(template)::main/(main_view1/10::main2_view1/99)::secondary/(sec_view1/54::secondary2_view1/43)',
421 | }).then(() => {
422 | expect(document.getElementById('router-a').innerText).toContain('Hello Template');
423 | const routerB = document.getElementById('routerb');
424 | expect(routerB.innerText).toContain('Main View 1 Main2 View 1');
425 | const routerC = document.getElementById('routerc');
426 | expect(routerC.innerText).toContain('Sec View 1 Sec2 View 1');
427 | routerB.parentNode.removeChild(routerB);
428 | routerC.parentNode.removeChild(routerC);
429 | });
430 | });
431 | });
432 |
433 | describe('nested routes', () => {
434 | it('should match nested', () =>
435 | clickAndTest({ href: 'nested/webcomponent_nested' }, 'Test Element
', 'outletB'));
436 |
437 | describe('with data', () => {
438 | it('should work with nested data', () =>
439 | clickAndTest({ href: 'nested/nested2/value1/webcomponent-data2/value2' }, 'Test Element value2', 'outletC'));
440 | });
441 | });
442 |
443 | describe('data', () => {
444 | it('should require data', () =>
445 | clickAndTest(
446 | { href: 'webcomponent-data1/paramValue1' },
447 | 'Test Element paramValue1
',
448 | 'outletA',
449 | ));
450 |
451 | it('supports optional data', () =>
452 | clickAndTest(
453 | { href: 'webcomponent-data2' },
454 | 'Test Element
',
455 | 'outletA',
456 | ));
457 |
458 | it('supports one or more data', () =>
459 | clickAndTest(
460 | { href: 'webcomponent-data3/paramValue1' },
461 | 'Test Element
',
462 | 'outletA',
463 | ));
464 |
465 | it('supports zero or more data', () =>
466 | clickAndTest(
467 | { href: 'webcomponent-data4/paramValue1' },
468 | 'Test Element
',
469 | 'outletA',
470 | ));
471 |
472 | it('supports multiple data params', () =>
473 | clickAndTest(
474 | { href: 'webcomponent-data5/paramValue1/paramVlaue2/paramValue3' },
475 | 'Test Element
',
476 | 'outletA',
477 | ));
478 |
479 | it('Supports setting data properties', () =>
480 | clickAndTest(
481 | { href: 'webcomponent-data5/paramValue1/paramVlaue2/paramValue3' },
482 | outlet => {
483 | const testDummy = outlet.querySelector('test-dummy');
484 | expect(testDummy.thirdparam).toEqual('paramValue3');
485 | expect(testDummy.getAttribute('firstParam')).toEqual('paramValue1');
486 | expect(testDummy.getAttribute('secondParam')).toEqual('paramVlaue2');
487 | },
488 | 'outletA',
489 | ));
490 |
491 | it('Supports setting dataset attributes', () =>
492 | clickAndTest(
493 | { href: 'webcomponent-data6' },
494 | outlet => {
495 | const testDummy = outlet.querySelector('test-dummy');
496 | expect(testDummy.getAttribute('firstParam')).toEqual('foobar');
497 | expect(testDummy.getAttribute('secondParam')).toEqual('foo');
498 | expect(testDummy.getAttribute('thirdParam')).toEqual('bar');
499 | },
500 | 'outletA',
501 | ));
502 |
503 | it('Supports setting data properties', () =>
504 | clickAndTest(
505 | { href: 'webcomponent-data7' },
506 | outlet => {
507 | const testDummy = outlet.querySelector('test-dummy');
508 | expect(testDummy.getAttribute('firstParam')).toEqual('foobar');
509 | expect(testDummy.propA).toBe('foobar');
510 | expect(testDummy.propB).toBe(123);
511 | expect(testDummy.propC).toBeTrue();
512 | expect(testDummy.propD).toBeNull();
513 | expect(testDummy.propE).toBeUndefined();
514 | expect(testDummy.propF).toBeInstanceOf(Date);
515 | },
516 | 'outletA',
517 | ));
518 | });
519 |
520 | describe('Route Caching', () => {
521 | it('should use cache if url is same', async () => {
522 | await click({ href: 'nested/nested2/value1/webcomponent-data2/value3' });
523 | document.getElementById('outletC').setAttribute('test', '123');
524 | await clickAndTest(
525 | { href: 'nested/nested2/value1/webcomponent-data2/value2' },
526 | outlet => {
527 | expect(outlet.getAttribute('test')).toEqual('123');
528 | },
529 | 'outletC',
530 | );
531 | });
532 |
533 | it('should not use cache if url is different', async () => {
534 | await click({ href: 'nested/nested2/value2/webcomponent-data2/value3' });
535 | const outletCBefore = document.getElementById('outletC');
536 | outletCBefore.setAttribute('test', '123');
537 | await click({ href: 'nested/nested2/value1/webcomponent-data2/value2' });
538 | const outletCAfter = document.getElementById('outletC');
539 | expect(outletCAfter).not.toEqual(outletCBefore);
540 | expect(outletCAfter.getAttribute('test')).not.toEqual('123');
541 | });
542 | });
543 |
544 | describe('Route Guards', () => {
545 | it('will not leave', done => {
546 | // eslint-disable-next-line require-jsdoc
547 | const check = () => {
548 | // eslint-disable-next-line require-jsdoc
549 | const routeLeaveCallback = event => {
550 | document.body.removeEventListener('onRouteLeave', routeLeaveCallback);
551 | event.preventDefault();
552 | // eslint-disable-next-line require-jsdoc
553 | const routeCancelledCallback = _ => {
554 | document.body.removeEventListener('onRouteCancelled', routeCancelledCallback);
555 | console.groupEnd();
556 | };
557 | document.body.addEventListener('onRouteCancelled', routeCancelledCallback);
558 | };
559 | document.body.addEventListener('onRouteLeave', routeLeaveCallback);
560 | clickAndTest({ href: 'nested/nested2/value2/webcomponent-data2/value2' }, outlet => {}, 'outletC').then(() =>
561 | done(),
562 | );
563 | };
564 | clickAndTest(
565 | { href: 'nested/nested2/value1/webcomponent-data2/value3' },
566 | outlet => {
567 | outlet.setAttribute('test', '123');
568 | },
569 | 'outletC',
570 | ).then(() => check());
571 | });
572 | });
573 |
574 | describe('Router Events', () => {
575 | // it('should fire onRouterAdded', done => {
576 | // });
577 | // it('should fire onRouteCancelled', done => {
578 | // });
579 | });
580 |
581 | describe('Route Events', () => {
582 | // it('should fire onRouteLeave', done => {
583 | // clickAndTest({ href: 'nested/template_nested' }, 'Hello Nested', done);
584 | // });
585 |
586 | it('should cancel match with onRouteMatch', async () => {
587 | const templateRoute = document.getElementById('template-route');
588 | // eslint-disable-next-line require-jsdoc
589 | const routeMatchedCallback = event => {
590 | templateRoute.removeEventListener('onRouteMatch', routeMatchedCallback);
591 | event.preventDefault();
592 | };
593 | templateRoute.addEventListener('onRouteMatch', routeMatchedCallback);
594 | await clickAndTest({ href: 'template' }, 'Only hit if template route cancelled', 'outletA');
595 | });
596 | });
597 |
598 | // describe('Outlet Events', () => {
599 | // it('should match nested', done => {
600 | // clickAndTest({ href: 'nested/template_nested' }, 'Hello Nested', done);
601 | // });
602 | // });
603 |
604 | // describe('test hash routing', () => {
605 | // it('should match', done => {
606 | // clickAndTest({ href: 'nested/template_nested' }, 'Hello Nested');
607 | // });
608 | // });
609 | });
610 |
611 | describe('routes-route', () => {
612 | describe('route matching', () => {
613 | // it('full matches', function () {
614 | // });
615 | });
616 |
617 | it('should apply dataset properties' , () => {
618 |
619 | });
620 | });
621 |
--------------------------------------------------------------------------------
/test/unit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Tests
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/test/unit/routes-route.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | /* globals expect,describe,afterEach,it */
3 | import { RouteElement } from '../../src/routes-route.js';
4 |
5 | /** @returns {RouteElement} */
6 | function createRouteElement() {
7 | return /** @type {RouteElement} */(document.createElement('a-route'));
8 | }
9 |
10 | describe('routes-route', () => {
11 | it('match multi level pathname', async () => {
12 | const routeElement = createRouteElement();
13 | routeElement.setAttribute('path', '/a/b/:t?');
14 | expect(routeElement.match('/a/b').url).toBe('/a/b');
15 | expect(routeElement.match('/a/b/1').url).toBe('/a/b/1');
16 | expect(routeElement.match('/a/c')).toBeNull();
17 | expect(routeElement.match('/a/b/1/e/f').url).toBe('a/b/1');
18 | expect(routeElement.match('/a/b/1/e/f').remainder).toBe('e/f');
19 | });
20 |
21 | it('optional parameters', () => {
22 | const routeElement = createRouteElement();
23 |
24 | routeElement.setAttribute('path', '/a/b/:t?');
25 | expect(routeElement.match('/a/b').url).toBe('/a/b');
26 | expect(routeElement.match('/a/b/1').url).toBe('/a/b/1');
27 |
28 | routeElement.setAttribute('path', '/a/b/:t?/c/:v?');
29 |
30 | let result = routeElement.match('/a/b/3/c/4');
31 | expect(result.url).toBe('/a/b/3/c/4');
32 | expect(result.data).toEqual(new Map([['t', '3'], ['v', '4']]));
33 |
34 | result = routeElement.match('/a/b/3/c');
35 | expect(result.url).toBe('/a/b/3/c');
36 | expect(result.data).toEqual(new Map([['t', '3'], ['v', '']]));
37 | expect(routeElement.match('/a/b/c')).toBeNull();
38 |
39 | result = routeElement.match('/a/b/3/c/4/d/5');
40 | expect(result.url).toBe('a/b/3/c/4');
41 | expect(result.remainder).toBe('d/5');
42 | expect(result.data).toEqual(new Map([['t', '3'], ['v', '4']]));
43 |
44 | routeElement.setAttribute('fullMatch', '');
45 | result = routeElement.match('/a/b/3/c/4/d/5');
46 | expect(result).toBeNull();
47 | });
48 |
49 | it('required parameters', () => {
50 | const routeElement = createRouteElement();
51 |
52 | routeElement.setAttribute('path', '/a/b/:t');
53 | expect(routeElement.match('/a/b')).toBeNull();
54 | expect(routeElement.match('/a/b/1').url).toBe('/a/b/1');
55 | expect(routeElement.match('/a/b/1').data).toEqual(new Map([['t', '1']]));
56 |
57 | routeElement.setAttribute('path', '/a/b/:t/c/:v');
58 |
59 | let result = routeElement.match('/a/b/3/c/4');
60 | expect(result.url).toBe('/a/b/3/c/4');
61 | expect(result.data).toEqual(new Map([['t', '3'], ['v', '4']]));
62 |
63 | result = routeElement.match('/a/b/3/c');
64 | expect(result).toBeNull();
65 | expect(routeElement.match('/a/b/c')).toBeNull();
66 |
67 | result = routeElement.match('/a/b/3/c/4/d/5');
68 | expect(result.url).toBe('a/b/3/c/4');
69 | expect(result.remainder).toBe('d/5');
70 | expect(result.data).toEqual(new Map([['t', '3'], ['v', '4']]));
71 | });
72 |
73 | it('one or more parameters', () => {
74 | const routeElement = createRouteElement();
75 |
76 | routeElement.setAttribute('path', '/a/b/:t+');
77 | expect(routeElement.match('/a/b')).toBeNull();
78 | expect(routeElement.match('/a/b/1/2/3').url).toBe('/a/b/1/2/3');
79 | expect(routeElement.match('/a/b/1/2/3').data).toEqual(new Map([['t', '1/2/3']]));
80 | });
81 |
82 | it('any number of parameters', () => {
83 | const routeElement = createRouteElement();
84 |
85 | routeElement.setAttribute('path', '/a/b/:t*');
86 | let match = routeElement.match('/a/b');
87 | expect(match.url).toBe('/a/b');
88 | expect(match.data).toEqual(new Map([['t', '']]));
89 | match = routeElement.match('/a/b/1/2/3');
90 | expect(match.url).toBe('/a/b/1/2/3');
91 | expect(match.data).toEqual(new Map([['t', '1/2/3']]));
92 | });
93 |
94 | it('default parameter values', () => {
95 | const routeElement = createRouteElement();
96 |
97 | routeElement.setAttribute('path', '/a/b/:t?foobar');
98 | let match = routeElement.match('/a/b');
99 | expect(match.url).toBe('/a/b');
100 | expect(match.data).toEqual(new Map([['t', 'foobar']]));
101 | match = routeElement.match('/a/b/1');
102 | expect(match.url).toBe('/a/b/1');
103 | expect(match.data).toEqual(new Map([['t', '1']]));
104 | });
105 | });
106 |
--------------------------------------------------------------------------------