@webqit/oohtml 1.10.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.gitignore +3 -3
  2. package/LICENSE +20 -20
  3. package/README.md +399 -396
  4. package/dist/context-api.js +2 -0
  5. package/dist/context-api.js.map +7 -0
  6. package/dist/html-imports.js +1 -2
  7. package/dist/html-imports.js.map +3 -3
  8. package/dist/html-modules.js +1 -2
  9. package/dist/html-modules.js.map +3 -3
  10. package/dist/main.js +26 -14
  11. package/dist/main.js.map +3 -3
  12. package/dist/namespaced-html.js +1 -2
  13. package/dist/namespaced-html.js.map +3 -3
  14. package/dist/scoped-js.js +27 -0
  15. package/dist/scoped-js.js.map +7 -0
  16. package/dist/state-api.js +1 -2
  17. package/dist/state-api.js.map +3 -3
  18. package/package.json +76 -76
  19. package/src/context-api/HTMLContext.js +158 -0
  20. package/src/context-api/HTMLContextManager.js +77 -0
  21. package/src/context-api/_ContextRequestEvent.js +26 -0
  22. package/src/context-api/index.js +53 -0
  23. package/src/{namespaced-html/browser-entry.js → context-api/targets.browser.js} +9 -9
  24. package/src/html-imports/_HTMLImportElement.js +216 -0
  25. package/src/html-imports/index.js +92 -557
  26. package/src/{browser-entry.js → html-imports/targets.browser.js} +10 -10
  27. package/src/html-modules/HTMLExportsManager.js +191 -0
  28. package/src/html-modules/_HTMLImportsContext.js +114 -0
  29. package/src/html-modules/index.js +133 -384
  30. package/src/{html-imports/browser-entry.js → html-modules/targets.browser.js} +9 -9
  31. package/src/index.js +34 -39
  32. package/src/namespaced-html/index.js +130 -144
  33. package/src/namespaced-html/targets.browser.js +10 -0
  34. package/src/scoped-js/index.js +382 -0
  35. package/src/scoped-js/targets.browser.js +10 -0
  36. package/src/state-api/index.js +55 -142
  37. package/src/state-api/targets.browser.js +10 -0
  38. package/src/{html-modules/browser-entry.js → targets.browser.js} +10 -10
  39. package/src/util.js +20 -180
  40. package/test/imports.test.js +194 -0
  41. package/test/index.js +119 -0
  42. package/test/modules.test.js +198 -0
  43. package/test/namespaced-html.test.js +50 -0
  44. package/test/scoped-js.js +57 -0
  45. package/test/state-api.test.js +34 -0
  46. package/test/test.html +69 -0
  47. package/dist/subscript.js +0 -15
  48. package/dist/subscript.js.map +0 -7
  49. package/src/state-api/browser-entry.js +0 -10
  50. package/src/subscript/Element.js +0 -103
  51. package/src/subscript/browser-entry.js +0 -10
  52. package/src/subscript/index.js +0 -70
  53. package/test/all.test.js +0 -0
@@ -0,0 +1,191 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import Observer from '@webqit/observer';
6
+ import { getModulesObject } from './index.js';
7
+ import { _ } from '../util.js';
8
+
9
+ export default class HTMLExportsManager {
10
+
11
+ /**
12
+ * @instance
13
+ */
14
+ static instance( host, params ) {
15
+ return _( host ).get( 'exportsmanager::instance' ) || new this( host, params );;
16
+ }
17
+
18
+ /**
19
+ * @constructor
20
+ */
21
+ constructor( host, params, parent = null, level = 0 ) {
22
+ _( host ).get( `exportsmanager::instance` )?.dispose();
23
+ _( host ).set( `exportsmanager::instance`, this );
24
+ this.host = host;
25
+ this.params = params;
26
+ this.parent = parent;
27
+ this.level = level;
28
+ this.modules = getModulesObject( this.host );
29
+ this.exportId = ( this.host.getAttribute( this.params.template.attr.exportid ) || '' ).trim();
30
+ this.validateExportId( this.exportId );
31
+ const dom = params.window.wq.dom;
32
+ // ----------
33
+ this.realtimeA = dom.realtime( this.host.content ).children( record => {
34
+ this.export( record.entrants, true );
35
+ this.export( record.exits, false );
36
+ }, { live: true, timing: 'sync' } );
37
+ // ----------
38
+ this.realtimeB = dom.realtime( this.host ).attr( [ 'src', 'loading' ], ( ...args ) => this.evaluateLoading( ...args ), {
39
+ live: true,
40
+ atomic: true,
41
+ timing: 'sync',
42
+ lifecycleSignals: true
43
+ } );
44
+ // ----------
45
+ this.realtimeC = this.evalInheritance();
46
+ // ----------
47
+ }
48
+
49
+ /**
50
+ * Validates export ID.
51
+ *
52
+ * @param String exportId
53
+ *
54
+ * @returns Void
55
+ */
56
+ validateExportId( exportId ) {
57
+ if ( [ '@', '#', ':' ].some( token => exportId.includes( token ) ) ) {
58
+ throw new Error( `The export ID "${ exportId }" contains an invalid character.` );
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Maps module contents as exports.
64
+ *
65
+ * @param Array entries
66
+ * @param Bool isConnected
67
+ *
68
+ * @returns Void
69
+ */
70
+ export( entries, isConnected ) {
71
+ const fragmentsExports = new Map;
72
+ entries.forEach( entry => {
73
+ if ( entry.nodeType !== 1 ) return;
74
+ if ( entry.matches( this.params.templateSelector ) ) {
75
+ if ( isConnected ) {
76
+ const moduleExport = new HTMLExportsManager( entry, this.params, this.host, this.level + 1 );
77
+ if ( moduleExport.exportId ) { Observer.set( this.modules, moduleExport.exportId, entry ); }
78
+ } else {
79
+ const moduleExport = HTMLModulesGraph.instance( entry, this.params );
80
+ if ( moduleExport.exportId ) { Observer.deleteProperty( this.modules, moduleExport.exportId ); }
81
+ moduleExport.dispose();
82
+ }
83
+ } else {
84
+ const exportId = ( entry.getAttribute( this.params.export.attr.exportid ) || '' ).trim() || 'default';
85
+ this.validateExportId( exportId );
86
+ if ( !fragmentsExports.has( exportId ) ) { fragmentsExports.set( exportId, [] ); }
87
+ fragmentsExports.get( exportId ).push( entry );
88
+ }
89
+ } );
90
+ // ----------------
91
+ fragmentsExports.forEach( ( fragments, exportId ) => {
92
+ let existingFragments = Observer.get( this.modules, `#${ exportId }` );
93
+ if ( isConnected ) {
94
+ existingFragments = new Set( ( existingFragments ? [ ...existingFragments ] : [] ).concat( fragments ) );
95
+ } else if ( existingFragments ) {
96
+ fragments.forEach( el => existingFragments.delete( el ) );
97
+ }
98
+ if ( !isConnected && !existingFragments.size ) { Observer.deleteProperty( this.modules, `#${ exportId }` ); }
99
+ else { Observer.set( this.modules, `#${ exportId }`, existingFragments ) }
100
+ } );
101
+ }
102
+
103
+ /**
104
+ * Evaluates remote content loading.
105
+ *
106
+ * @param AbortSignal signal
107
+ *
108
+ * @returns Void
109
+ */
110
+ evaluateLoading( [ record1, record2 ], { signal } ) {
111
+ const src = ( record1.value || '' ).trim();
112
+ if ( !src ) return;
113
+ const loading = ( record2.value || '' ).trim();
114
+ if ( loading === 'lazy' ) {
115
+ const interception = Observer.intercept( this.modules, 'get', async ( descriptor, recieved, next ) => {
116
+ await this.load( src, true );
117
+ interception.remove();
118
+ return next();
119
+ }, { signal } );
120
+ } else { this.load( src ); }
121
+ }
122
+
123
+ /**
124
+ * Fetches a module's "src".
125
+ *
126
+ * @param String src
127
+ *
128
+ * @return Promise
129
+ */
130
+ load( src ) {
131
+ const window = this.params.window;
132
+ if ( this.host.content.children.length ) return;
133
+ // Ongoing request?
134
+ if ( this.fetchInFlight?.src === src ) return this.fetchInFlight.request;
135
+ this.fetchInFlight?.controller.abort();
136
+ // The promise
137
+ const controller = new AbortController();
138
+ const fire = ( type, detail ) => this.host.dispatchEvent( new window.CustomEvent( type, { detail } ) );
139
+ const request = window.fetch( src, { signal: controller.signal } ).then( response => {
140
+ return response.ok ? response.text() : Promise.reject( response.statusText );
141
+ }).then( content => {
142
+ this.host.innerHTML = content.trim(); // IMPORTANT: .trim()
143
+ fire( 'load' );
144
+ return this.host;
145
+ } ).catch( e => {
146
+ console.error( `Error fetching the bundle at "${ src }": ${ e.message }` );
147
+ this.fetchInFlight = null;
148
+ fire( 'loaderror' );
149
+ return this.host;
150
+ } );
151
+ this.fetchInFlight = { src, request, controller };
152
+ return request;
153
+ }
154
+
155
+ /**
156
+ * Evaluates module inheritance.
157
+ *
158
+ * @returns Void|AbortController
159
+ */
160
+ evalInheritance( ) {
161
+ let inheritedIds;
162
+ if ( this.parent && ( inheritedIds = ( this.host.getAttribute( this.params.template.attr.inherits ) || '' ).trim() )
163
+ && ( inheritedIds = inheritedIds.split( ' ' ).map( id => id.trim() ) ).length ) {
164
+ const parentExportsObj = getModulesObject( this.parent );
165
+ return Observer.get( parentExportsObj, inheritedIds, records => {
166
+ records.forEach( record => {
167
+ if ( [ 'get'/*initial get*/, 'set', 'defineProperty' ].includes( record.type ) ) {
168
+ Observer[ record.type.replace( 'get', 'set' ) ]( this.modules, record.key, record.value );
169
+ } else if ( record.type === 'deleteProperty' ) {
170
+ Observer.deleteProperty( this.modules, record.key );
171
+ }
172
+ } );
173
+ }, { live: true } );
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Disposes the instance and its processes.
179
+ *
180
+ * @returns Void
181
+ */
182
+ dispose() {
183
+ this.realtimeA.disconnect();
184
+ this.realtimeB.disconnect();
185
+ this.realtimeC?.abort();
186
+ Object.entries( this.modules ).forEach( ( [ key, entry ] ) => {
187
+ if ( key.startsWith( '#' ) ) return;
188
+ HTMLExportsManager.instance( entry ).dispose();
189
+ } );
190
+ }
191
+ }
@@ -0,0 +1,114 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import Observer from '@webqit/observer';
6
+ import { HTMLContextManager, HTMLContext } from '../context-api/index.js';
7
+ import { getModulesObject } from './index.js';
8
+ import { _ } from '../util.js';
9
+
10
+ export default class _HTMLImportsContext extends HTMLContext {
11
+
12
+ /**
13
+ * @createRequest
14
+ */
15
+ static createRequest( fields = {} ) {
16
+ const request = { type: 'HTMLModules', ...fields };
17
+ if ( !request.name && request.detail?.startsWith( '/' ) ) { request.name = 'root'; }
18
+ else if ( request.detail?.startsWith( '@' ) ) {
19
+ const [ contextName, detail ] = request.detail.split( ':' ).map( s => s.trim() );
20
+ request.name = contextName.slice( 1 );
21
+ request.detail = detail;
22
+ }
23
+ return request;
24
+ }
25
+
26
+ /**
27
+ * @modules
28
+ */
29
+ get modules() {
30
+ return getModulesObject( this.host );
31
+ }
32
+
33
+ /**
34
+ * @handle()
35
+ */
36
+ handle( event ) {
37
+ // Any existing event.request.controller? Abort!
38
+ event.request.controller?.abort();
39
+
40
+ // Parse and translate detail
41
+ if ( ( event.request.detail || '' ).trim() === '/' ) return event.respondWith( this.modules );
42
+ const $params = this.constructor.params;
43
+ let path = ( event.request.detail || '' ).split( /\/|(?<=\w)(?=\W)/g ).map( x => x.trim() ).filter( x => x );
44
+ if ( path.length ) { path = path.join( `/${ $params.template.api.modules }/` )?.split( '/' ) || []; }
45
+ // No detail?
46
+ if ( !path.length ) return event.respondWith();
47
+
48
+ // We'll now fulfill request
49
+ const params = { live: event.request.live, descripted: true, midwayResults: true };
50
+ // Find a way to resolve request against two sources
51
+ event.request.controller = Observer.deep( this.modules, path, Observer.get, ( result, { signal } = {} ) => {
52
+ if ( !result.value && this.host.isConnected === false ) return; // Subtree is being disposed
53
+ if ( result.value || !this.altModules ) return event.respondWith( result.value );
54
+ // This superModules binding is automatically aborted by the injected control.signal; see below
55
+ return Observer.deep( this.altModules, path, Observer.get, result => {
56
+ return event.respondWith( result.value );
57
+ }, { signal, ...params } );
58
+ }, params );
59
+ }
60
+
61
+ /**
62
+ * @startRealtime()
63
+ */
64
+ realtimeSources( host ) {
65
+ this.host = host;
66
+ // ----------------
67
+ const update = () => {
68
+ for ( const subscriptionEvent of this.subscriptions ) {
69
+ this.handle( subscriptionEvent );
70
+ }
71
+ };
72
+ // ----------------
73
+ const $params = this.constructor.params;
74
+ if ( !this.host.matches || !$params.context.attr.importscontext ) return;
75
+ // Any existing this.refdSourceController? Abort!
76
+ this.refdSourceController?.disconnect();
77
+ const dom = this.host.ownerDocument.defaultView.wq.dom;
78
+ this.refdSourceController = dom.realtime( this.host ).attr( $params.context.attr.importscontext, ( record, { signal } ) => {
79
+ // No importscontext attr set. But we're still watching
80
+ if ( !record.value ) {
81
+ this.altModules = undefined;
82
+ return update();
83
+ }
84
+ // This superModules contextrequest is automatically aborted by the injected signal below
85
+ const request = this.constructor.createRequest( { detail: record.value.trim(), live: true, signal } );
86
+ HTMLContextManager.instance( this.host ).ask( request, response => {
87
+ this.altModules = !( response && Object.getPrototypeOf( response ) ) ? response : getModulesObject( response );
88
+ update();
89
+ } );
90
+ }, { live: true, timing: 'sync', lifecycleSignals: true } );
91
+ }
92
+
93
+ /**
94
+ * @initialize()
95
+ */
96
+ initialize( host ) {
97
+ // If host has importscontext attr, compute that
98
+ this.realtimeSources( host );
99
+ // Now, listen for contextrequest and contextclaim events
100
+ // And process own claim
101
+ return super.initialize( host );
102
+ }
103
+
104
+ /**
105
+ * @dispose()
106
+ */
107
+ dispose( host ) {
108
+ // Stop listening for sources
109
+ this.refdSourceController?.disconnect();
110
+ // Now, stop listening for contextrequest and contextclaim events
111
+ // And relinquish own subscribers to owner context
112
+ return super.dispose( host );
113
+ }
114
+ }