@nrg-ui/ember-media 0.1.1
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.
- package/LICENSE.md +9 -0
- package/README.md +132 -0
- package/addon-main.cjs +5 -0
- package/declarations/index.d.ts +4 -0
- package/declarations/index.d.ts.map +1 -0
- package/declarations/services/media.d.ts +50 -0
- package/declarations/services/media.d.ts.map +1 -0
- package/declarations/template-registry.d.ts +1 -0
- package/declarations/template-registry.d.ts.map +1 -0
- package/declarations/test-support/index.d.ts +2 -0
- package/declarations/test-support/index.d.ts.map +1 -0
- package/dist/_app_/services/media.js +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/services/media.js +122 -0
- package/dist/services/media.js.map +1 -0
- package/dist/template-registry.js +2 -0
- package/dist/template-registry.js.map +1 -0
- package/dist/test-support/index.js +22 -0
- package/dist/test-support/index.js.map +1 -0
- package/package.json +120 -0
- package/src/index.ts +4 -0
- package/src/services/media.ts +166 -0
- package/src/template-registry.ts +10 -0
- package/src/test-support/index.ts +24 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# @nrg-ui/ember-media
|
|
2
|
+
|
|
3
|
+
An Ember addon for safe media query handling.
|
|
4
|
+
|
|
5
|
+
## Compatibility
|
|
6
|
+
|
|
7
|
+
- Ember.js v5.12 or above
|
|
8
|
+
- Embroider or ember-auto-import v2
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
ember install @nrg-ui/ember-media
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### Breakpoints
|
|
19
|
+
|
|
20
|
+
There are 6 breakpoints available, with the following media queries:
|
|
21
|
+
|
|
22
|
+
| Breakpoint | Media Query |
|
|
23
|
+
| ---------- | --------------------------------------------- |
|
|
24
|
+
| xsmall | `(min-width: 0px) and (max-width: 575px)` |
|
|
25
|
+
| small | `(min-width: 576px) and (max-width: 767px)` |
|
|
26
|
+
| medium | `(min-width: 768px) and (max-width: 991px)` |
|
|
27
|
+
| large | `(min-width: 992px) and (max-width: 1199px)` |
|
|
28
|
+
| xlarge | `(min-width: 1200px) and (max-width: 1399px)` |
|
|
29
|
+
| xxlarge | `(min-width: 1400px)` |
|
|
30
|
+
|
|
31
|
+
There is a corresponding property on the media service for each breakpoint, e.g. `isSmall`, `isMedium`, etc.
|
|
32
|
+
|
|
33
|
+
The queries can be adjusted via the Embroider build config, if needed:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
// ember-cli-build.js
|
|
37
|
+
module.exports = function (defaults) {
|
|
38
|
+
let app = new EmberApp(defaults, {
|
|
39
|
+
'@embroider/macros': {
|
|
40
|
+
'@nrg-ui/ember-media': {
|
|
41
|
+
breakpoints: {
|
|
42
|
+
small: '(min-width: 600px) and (max-width: 799px)',
|
|
43
|
+
// other breakpoints...
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return app;
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Events
|
|
54
|
+
|
|
55
|
+
The media service emits a `change` event whenever the matched media queries change. You can listen for this event to react to changes in screen size.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
export default class MyComponent extends Component {
|
|
59
|
+
@service
|
|
60
|
+
declare media: MediaService;
|
|
61
|
+
|
|
62
|
+
constructor(owner: Owner, args: unknown) {
|
|
63
|
+
super(owner, args);
|
|
64
|
+
|
|
65
|
+
this.media.on('change', () => {
|
|
66
|
+
console.log('Matched media queries:', Array.from(this.media.matches));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Testing
|
|
73
|
+
|
|
74
|
+
When testing components or services that depend on the media service, you can use the provided test helpers to simulate different screen sizes.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { setupRenderingTest } from 'ember-qunit';
|
|
78
|
+
import { module, test } from 'qunit';
|
|
79
|
+
import { setBreakpoint } from '@nrg-ui/ember-media/test-support';
|
|
80
|
+
|
|
81
|
+
module('Integration | Component | my-component', function (hooks) {
|
|
82
|
+
setupRenderingTest(hooks);
|
|
83
|
+
|
|
84
|
+
test('my test', async function (assert) {
|
|
85
|
+
const service = this.owner.lookup('service:media');
|
|
86
|
+
|
|
87
|
+
await setBreakpoint('small');
|
|
88
|
+
// ...
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Example
|
|
94
|
+
|
|
95
|
+
Inject the media service into your component, controller, or route:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import Component from '@glimmer/component';
|
|
99
|
+
import { service } from '@ember/service';
|
|
100
|
+
|
|
101
|
+
import type { Owner } from '@ember/owner';
|
|
102
|
+
import type MediaService from '@nrg-ui/ember-media/services/media';
|
|
103
|
+
|
|
104
|
+
export default class MyComponent extends Component {
|
|
105
|
+
@service
|
|
106
|
+
declare media: MediaService;
|
|
107
|
+
|
|
108
|
+
constructor(owner: unknown, args: unknown) {
|
|
109
|
+
super(owner, args);
|
|
110
|
+
|
|
111
|
+
this.media.on('change', this.handleMediaChange);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
onClick = () => {
|
|
115
|
+
if (this.media.isLarge) {
|
|
116
|
+
// Do something for large screens
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
handleMediaChange = () => {
|
|
121
|
+
console.log('Matched media queries:', Array.from(this.media.matches));
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Contributing
|
|
127
|
+
|
|
128
|
+
See the [Contributing](CONTRIBUTING.md) guide for details.
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
This project is licensed under the [MIT License](LICENSE.md).
|
package/addon-main.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AAEpD,YAAY,EAAE,YAAY,EAAE,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import Service from '@ember/service';
|
|
2
|
+
import { TrackedSet } from 'tracked-built-ins';
|
|
3
|
+
import type Owner from '@ember/owner';
|
|
4
|
+
type Fn = () => unknown | Promise<unknown>;
|
|
5
|
+
type Callbacks = {
|
|
6
|
+
change: Set<Fn>;
|
|
7
|
+
};
|
|
8
|
+
export declare const defaultBreakpoints: Readonly<{
|
|
9
|
+
xsmall: "(min-width: 0px) and (max-width: 575px)";
|
|
10
|
+
small: "(min-width: 576px) and (max-width: 767px)";
|
|
11
|
+
medium: "(min-width: 768px) and (max-width: 991px)";
|
|
12
|
+
large: "(min-width: 992px) and (max-width: 1199px)";
|
|
13
|
+
xlarge: "(min-width: 1200px) and (max-width: 1399px)";
|
|
14
|
+
xxlarge: "(min-width: 1400px)";
|
|
15
|
+
}>;
|
|
16
|
+
export default class Media extends Service {
|
|
17
|
+
#private;
|
|
18
|
+
_mockedBreakpoint: string;
|
|
19
|
+
_matches: TrackedSet<string>;
|
|
20
|
+
mocked: boolean;
|
|
21
|
+
callbacks: Callbacks;
|
|
22
|
+
breakpoints: {
|
|
23
|
+
xsmall: string;
|
|
24
|
+
small: string;
|
|
25
|
+
medium: string;
|
|
26
|
+
large: string;
|
|
27
|
+
xlarge: string;
|
|
28
|
+
xxlarge: string;
|
|
29
|
+
};
|
|
30
|
+
constructor(owner: Owner);
|
|
31
|
+
get matches(): Set<string>;
|
|
32
|
+
private set matches(value);
|
|
33
|
+
get isXSmall(): boolean;
|
|
34
|
+
get isSmall(): boolean;
|
|
35
|
+
get isMedium(): boolean;
|
|
36
|
+
get isLarge(): boolean;
|
|
37
|
+
get isXLarge(): boolean;
|
|
38
|
+
get isXXLarge(): boolean;
|
|
39
|
+
on(name: keyof Callbacks, callback: Fn): void;
|
|
40
|
+
off(name: keyof Callbacks, callback: Fn): void;
|
|
41
|
+
trigger(name: keyof Callbacks): void;
|
|
42
|
+
match(name: string, query: string): void;
|
|
43
|
+
}
|
|
44
|
+
declare module '@ember/service' {
|
|
45
|
+
interface Registry {
|
|
46
|
+
media: Media;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/services/media.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,gBAAgB,CAAC;AASrC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,KAAK,EAAE,GAAG,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAC3C,KAAK,SAAS,GAAG;IACf,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;CACjB,CAAC;AAMF,eAAO,MAAM,kBAAkB;;;;;;;EAO7B,CAAC;AAEH,MAAM,CAAC,OAAO,OAAO,KAAM,SAAQ,OAAO;;IACxC,iBAAiB,SAAa;IAG9B,QAAQ,qBAA4B;IAGpC,MAAM,UAAmE;IAEzE,SAAS,EAAE,SAAS,CAElB;IAEF,WAAW;;;;;;;MAGT;gBAEU,KAAK,EAAE,KAAK;IAQxB,IAAI,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,CASzB;IAED,OAAO,KAAK,OAAO,QAElB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAUD,EAAE,CAAC,IAAI,EAAE,MAAM,SAAS,EAAE,QAAQ,EAAE,EAAE;IAMtC,GAAG,CAAC,IAAI,EAAE,MAAM,SAAS,EAAE,QAAQ,EAAE,EAAE;IAMvC,OAAO,CAAC,IAAI,EAAE,MAAM,SAAS;IAW7B,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CA+BlC;AAED,OAAO,QAAQ,gBAAgB,CAAC;IAC9B,UAAU,QAAQ;QAChB,KAAK,EAAE,KAAK,CAAC;KACd;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=template-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-registry.d.ts","sourceRoot":"","sources":["../src/template-registry.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test-support/index.ts"],"names":[],"mappings":"AAKA,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,iBAkBrD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@nrg-ui/ember-media/services/media";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { assert } from '@ember/debug';
|
|
2
|
+
import Service from '@ember/service';
|
|
3
|
+
import { macroCondition, isTesting, isDevelopingApp, getOwnConfig } from '@embroider/macros';
|
|
4
|
+
import { tracked } from '@glimmer/tracking';
|
|
5
|
+
import { runTask } from 'ember-lifeline';
|
|
6
|
+
import { TrackedSet } from 'tracked-built-ins';
|
|
7
|
+
import { g, i } from 'decorator-transforms/runtime-esm';
|
|
8
|
+
|
|
9
|
+
const defaultBreakpoints = Object.freeze({
|
|
10
|
+
xsmall: '(min-width: 0px) and (max-width: 575px)',
|
|
11
|
+
small: '(min-width: 576px) and (max-width: 767px)',
|
|
12
|
+
medium: '(min-width: 768px) and (max-width: 991px)',
|
|
13
|
+
large: '(min-width: 992px) and (max-width: 1199px)',
|
|
14
|
+
xlarge: '(min-width: 1200px) and (max-width: 1399px)',
|
|
15
|
+
xxlarge: '(min-width: 1400px)'
|
|
16
|
+
});
|
|
17
|
+
class Media extends Service {
|
|
18
|
+
_mockedBreakpoint = 'desktop';
|
|
19
|
+
static {
|
|
20
|
+
g(this.prototype, "_matches", [tracked], function () {
|
|
21
|
+
return new TrackedSet();
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
#_matches = (i(this, "_matches"), void 0);
|
|
25
|
+
static {
|
|
26
|
+
g(this.prototype, "mocked", [tracked], function () {
|
|
27
|
+
return macroCondition(isTesting() || isDevelopingApp()) ? true : false;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
#mocked = (i(this, "mocked"), void 0);
|
|
31
|
+
callbacks = {
|
|
32
|
+
change: new Set()
|
|
33
|
+
};
|
|
34
|
+
breakpoints = {
|
|
35
|
+
...defaultBreakpoints,
|
|
36
|
+
...getOwnConfig()?.breakpoints
|
|
37
|
+
};
|
|
38
|
+
constructor(owner) {
|
|
39
|
+
super(owner);
|
|
40
|
+
for (const [name, query] of Object.entries(this.breakpoints)) {
|
|
41
|
+
this.match(name, query);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
get matches() {
|
|
45
|
+
if ((macroCondition(isTesting() || isDevelopingApp()) ? true : false) && this.mocked) {
|
|
46
|
+
return new TrackedSet([this._mockedBreakpoint]);
|
|
47
|
+
}
|
|
48
|
+
return this._matches;
|
|
49
|
+
}
|
|
50
|
+
set matches(value) {
|
|
51
|
+
this._matches = new TrackedSet(value);
|
|
52
|
+
}
|
|
53
|
+
get isXSmall() {
|
|
54
|
+
return this.matches.has('xsmall');
|
|
55
|
+
}
|
|
56
|
+
get isSmall() {
|
|
57
|
+
return this.matches.has('small');
|
|
58
|
+
}
|
|
59
|
+
get isMedium() {
|
|
60
|
+
return this.matches.has('medium');
|
|
61
|
+
}
|
|
62
|
+
get isLarge() {
|
|
63
|
+
return this.matches.has('large');
|
|
64
|
+
}
|
|
65
|
+
get isXLarge() {
|
|
66
|
+
return this.matches.has('xlarge');
|
|
67
|
+
}
|
|
68
|
+
get isXXLarge() {
|
|
69
|
+
return this.matches.has('xxlarge');
|
|
70
|
+
}
|
|
71
|
+
#getCallbackList(name) {
|
|
72
|
+
const callbackList = this.callbacks[name];
|
|
73
|
+
assert(`Callback '${name}' is not valid`, callbackList !== undefined);
|
|
74
|
+
return callbackList;
|
|
75
|
+
}
|
|
76
|
+
on(name, callback) {
|
|
77
|
+
const callbackList = this.#getCallbackList(name);
|
|
78
|
+
callbackList.add(callback);
|
|
79
|
+
}
|
|
80
|
+
off(name, callback) {
|
|
81
|
+
const callbackList = this.#getCallbackList(name);
|
|
82
|
+
callbackList.delete(callback);
|
|
83
|
+
}
|
|
84
|
+
trigger(name) {
|
|
85
|
+
const callbackList = this.#getCallbackList(name);
|
|
86
|
+
for (const callback of callbackList) {
|
|
87
|
+
try {
|
|
88
|
+
callback();
|
|
89
|
+
} catch {
|
|
90
|
+
// Ignore
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
match(name, query) {
|
|
95
|
+
if (macroCondition(isTesting() || isDevelopingApp())) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const mediaQueryList = matchMedia(query);
|
|
99
|
+
const listener = matcher => {
|
|
100
|
+
let changed = false;
|
|
101
|
+
if (matcher.matches) {
|
|
102
|
+
if (!this.matches.has(name)) {
|
|
103
|
+
this.matches.add(name);
|
|
104
|
+
changed = true;
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
changed = this.matches.has(name);
|
|
108
|
+
this.matches.delete(name);
|
|
109
|
+
}
|
|
110
|
+
if (changed) {
|
|
111
|
+
runTask(this, () => this.trigger('change'));
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
mediaQueryList.addEventListener('change', event => {
|
|
115
|
+
runTask(this, () => listener(event));
|
|
116
|
+
});
|
|
117
|
+
listener(mediaQueryList);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { Media as default, defaultBreakpoints };
|
|
122
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sources":["../../src/services/media.ts"],"sourcesContent":["import { assert } from '@ember/debug';\nimport Service from '@ember/service';\nimport {\n getOwnConfig,\n isDevelopingApp,\n isTesting,\n macroCondition,\n} from '@embroider/macros';\nimport { tracked } from '@glimmer/tracking';\nimport { runTask } from 'ember-lifeline';\nimport { TrackedSet } from 'tracked-built-ins';\n\nimport type Owner from '@ember/owner';\n\ntype Fn = () => unknown | Promise<unknown>;\ntype Callbacks = {\n change: Set<Fn>;\n};\ntype Matcher = {\n matches: boolean;\n media: string;\n};\n\nexport const defaultBreakpoints = Object.freeze({\n xsmall: '(min-width: 0px) and (max-width: 575px)',\n small: '(min-width: 576px) and (max-width: 767px)',\n medium: '(min-width: 768px) and (max-width: 991px)',\n large: '(min-width: 992px) and (max-width: 1199px)',\n xlarge: '(min-width: 1200px) and (max-width: 1399px)',\n xxlarge: '(min-width: 1400px)',\n});\n\nexport default class Media extends Service {\n _mockedBreakpoint = 'desktop';\n\n @tracked\n _matches = new TrackedSet<string>();\n\n @tracked\n mocked = macroCondition(isTesting() || isDevelopingApp()) ? true : false;\n\n callbacks: Callbacks = {\n change: new Set(),\n };\n\n breakpoints = {\n ...defaultBreakpoints,\n ...getOwnConfig()?.breakpoints,\n };\n\n constructor(owner: Owner) {\n super(owner);\n\n for (const [name, query] of Object.entries(this.breakpoints)) {\n this.match(name, query);\n }\n }\n\n get matches(): Set<string> {\n if (\n (macroCondition(isTesting() || isDevelopingApp()) ? true : false) &&\n this.mocked\n ) {\n return new TrackedSet([this._mockedBreakpoint]);\n }\n\n return this._matches;\n }\n\n private set matches(value: Iterable<string>) {\n this._matches = new TrackedSet(value);\n }\n\n get isXSmall(): boolean {\n return this.matches.has('xsmall');\n }\n\n get isSmall(): boolean {\n return this.matches.has('small');\n }\n\n get isMedium(): boolean {\n return this.matches.has('medium');\n }\n\n get isLarge(): boolean {\n return this.matches.has('large');\n }\n\n get isXLarge(): boolean {\n return this.matches.has('xlarge');\n }\n\n get isXXLarge(): boolean {\n return this.matches.has('xxlarge');\n }\n\n #getCallbackList(name: keyof Callbacks) {\n const callbackList = this.callbacks[name];\n\n assert(`Callback '${name}' is not valid`, callbackList !== undefined);\n\n return callbackList;\n }\n\n on(name: keyof Callbacks, callback: Fn) {\n const callbackList = this.#getCallbackList(name);\n\n callbackList.add(callback);\n }\n\n off(name: keyof Callbacks, callback: Fn) {\n const callbackList = this.#getCallbackList(name);\n\n callbackList.delete(callback);\n }\n\n trigger(name: keyof Callbacks) {\n const callbackList = this.#getCallbackList(name);\n for (const callback of callbackList) {\n try {\n callback();\n } catch {\n // Ignore\n }\n }\n }\n\n match(name: string, query: string) {\n if (macroCondition(isTesting() || isDevelopingApp())) {\n return;\n }\n\n const mediaQueryList = matchMedia(query);\n\n const listener = (matcher: Matcher) => {\n let changed = false;\n\n if (matcher.matches) {\n if (!this.matches.has(name)) {\n this.matches.add(name);\n changed = true;\n }\n } else {\n changed = this.matches.has(name);\n this.matches.delete(name);\n }\n\n if (changed) {\n runTask(this, () => this.trigger('change'));\n }\n };\n\n mediaQueryList.addEventListener('change', (event) => {\n runTask(this, () => listener(event));\n });\n\n listener(mediaQueryList);\n }\n}\n\ndeclare module '@ember/service' {\n interface Registry {\n media: Media;\n }\n}\n"],"names":["defaultBreakpoints","Object","freeze","xsmall","small","medium","large","xlarge","xxlarge","Media","Service","_mockedBreakpoint","g","prototype","tracked","TrackedSet","i","macroCondition","isTesting","isDevelopingApp","callbacks","change","Set","breakpoints","getOwnConfig","constructor","owner","name","query","entries","match","matches","mocked","_matches","value","isXSmall","has","isSmall","isMedium","isLarge","isXLarge","isXXLarge","#getCallbackList","callbackList","assert","undefined","on","callback","add","off","delete","trigger","mediaQueryList","matchMedia","listener","matcher","changed","runTask","addEventListener","event"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-registry.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getContext, settled } from '@ember/test-helpers';
|
|
2
|
+
|
|
3
|
+
async function setBreakpoint(breakpoint) {
|
|
4
|
+
const {
|
|
5
|
+
owner
|
|
6
|
+
} = getContext();
|
|
7
|
+
const media = owner.lookup('service:media');
|
|
8
|
+
if (breakpoint === 'auto') {
|
|
9
|
+
media.mocked = false;
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (Object.keys(media.breakpoints).indexOf(breakpoint) === -1) {
|
|
13
|
+
throw new Error(`Breakpoint "${breakpoint}" not defined as a breakpoint`);
|
|
14
|
+
}
|
|
15
|
+
media.mocked = true;
|
|
16
|
+
media._mockedBreakpoint = breakpoint;
|
|
17
|
+
media.trigger('change');
|
|
18
|
+
await settled();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { setBreakpoint };
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/test-support/index.ts"],"sourcesContent":["import { getContext, settled } from '@ember/test-helpers';\n\nimport type Media from '../services/media.ts';\nimport type { TestContext } from '@ember/test-helpers';\n\nexport async function setBreakpoint(breakpoint: string) {\n const { owner } = getContext() as TestContext;\n const media = owner.lookup('service:media') as Media;\n\n if (breakpoint === 'auto') {\n media.mocked = false;\n return;\n }\n\n if (Object.keys(media.breakpoints).indexOf(breakpoint) === -1) {\n throw new Error(`Breakpoint \"${breakpoint}\" not defined as a breakpoint`);\n }\n\n media.mocked = true;\n media._mockedBreakpoint = breakpoint;\n media.trigger('change');\n\n await settled();\n}\n"],"names":["setBreakpoint","breakpoint","owner","getContext","media","lookup","mocked","Object","keys","breakpoints","indexOf","Error","_mockedBreakpoint","trigger","settled"],"mappings":";;AAKO,eAAeA,aAAaA,CAACC,UAAkB,EAAE;EACtD,MAAM;AAAEC,IAAAA;GAAO,GAAGC,UAAU,EAAiB;AAC7C,EAAA,MAAMC,KAAK,GAAGF,KAAK,CAACG,MAAM,CAAC,eAAe,CAAU;EAEpD,IAAIJ,UAAU,KAAK,MAAM,EAAE;IACzBG,KAAK,CAACE,MAAM,GAAG,KAAK;AACpB,IAAA;AACF,EAAA;AAEA,EAAA,IAAIC,MAAM,CAACC,IAAI,CAACJ,KAAK,CAACK,WAAW,CAAC,CAACC,OAAO,CAACT,UAAU,CAAC,KAAK,EAAE,EAAE;AAC7D,IAAA,MAAM,IAAIU,KAAK,CAAC,CAAA,YAAA,EAAeV,UAAU,+BAA+B,CAAC;AAC3E,EAAA;EAEAG,KAAK,CAACE,MAAM,GAAG,IAAI;EACnBF,KAAK,CAACQ,iBAAiB,GAAGX,UAAU;AACpCG,EAAAA,KAAK,CAACS,OAAO,CAAC,QAAQ,CAAC;EAEvB,MAAMC,OAAO,EAAE;AACjB;;;;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nrg-ui/ember-media",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "An Ember addon for safe media query handling",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ember-addon"
|
|
7
|
+
],
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/knoxville-utilities-board/ember-media.git"
|
|
11
|
+
},
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "",
|
|
14
|
+
"imports": {
|
|
15
|
+
"#src/*": "./src/*"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./declarations/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./test-support": {
|
|
23
|
+
"types": "./declarations/test-support/index.d.ts",
|
|
24
|
+
"default": "./dist/test-support/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./addon-main.js": "./addon-main.cjs",
|
|
27
|
+
"./*.css": "./dist/*.css",
|
|
28
|
+
"./*": {
|
|
29
|
+
"types": "./declarations/*.d.ts",
|
|
30
|
+
"default": "./dist/*.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"addon-main.cjs",
|
|
35
|
+
"declarations",
|
|
36
|
+
"dist",
|
|
37
|
+
"src"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@embroider/addon-shim": "^1.10.2",
|
|
41
|
+
"decorator-transforms": "^2.3.1",
|
|
42
|
+
"ember-lifeline": "^7.0.0",
|
|
43
|
+
"tracked-built-ins": "^4.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@babel/core": "^7.28.5",
|
|
47
|
+
"@babel/eslint-parser": "^7.28.5",
|
|
48
|
+
"@babel/plugin-transform-typescript": "^7.28.5",
|
|
49
|
+
"@babel/runtime": "^7.28.4",
|
|
50
|
+
"@ember/app-tsconfig": "^2.0.0",
|
|
51
|
+
"@ember/library-tsconfig": "^2.0.0",
|
|
52
|
+
"@ember/test-helpers": "^5.4.1",
|
|
53
|
+
"@ember/test-waiters": "^4.1.1",
|
|
54
|
+
"@embroider/addon-dev": "^8.2.0",
|
|
55
|
+
"@embroider/compat": "^4.1.12",
|
|
56
|
+
"@embroider/core": "^4.4.2",
|
|
57
|
+
"@embroider/macros": "^1.19.6",
|
|
58
|
+
"@embroider/vite": "^1.5.0",
|
|
59
|
+
"@eslint/js": "^9.39.2",
|
|
60
|
+
"@glimmer/component": "^2.0.0",
|
|
61
|
+
"@glint/ember-tsc": "^1.0.8",
|
|
62
|
+
"@glint/template": "^1.7.3",
|
|
63
|
+
"@glint/tsserver-plugin": "^2.0.8",
|
|
64
|
+
"@nrg-ui/standards": "^0.6.2",
|
|
65
|
+
"@rollup/plugin-babel": "^6.1.0",
|
|
66
|
+
"@types/qunit": "^2.19.13",
|
|
67
|
+
"babel-plugin-ember-template-compilation": "^3.0.1",
|
|
68
|
+
"concurrently": "^9.2.1",
|
|
69
|
+
"ember-page-title": "^9.0.3",
|
|
70
|
+
"ember-qunit": "^9.0.4",
|
|
71
|
+
"ember-source": "^6.9.0",
|
|
72
|
+
"ember-strict-application-resolver": "^0.1.1",
|
|
73
|
+
"ember-template-lint": "^7.9.3",
|
|
74
|
+
"eslint": "^9.39.2",
|
|
75
|
+
"eslint-plugin-decorator-position": "^6.0.0",
|
|
76
|
+
"eslint-plugin-ember": "^12.7.5",
|
|
77
|
+
"eslint-plugin-import": "^2.32.0",
|
|
78
|
+
"eslint-plugin-n": "^17.23.1",
|
|
79
|
+
"eslint-plugin-qunit": "^8.2.5",
|
|
80
|
+
"prettier": "^3.7.4",
|
|
81
|
+
"prettier-plugin-ember-template-tag": "^2.1.2",
|
|
82
|
+
"qunit": "^2.25.0",
|
|
83
|
+
"qunit-dom": "^3.5.0",
|
|
84
|
+
"release-plan": "^0.17.0",
|
|
85
|
+
"rollup": "^4.54.0",
|
|
86
|
+
"testem": "^3.17.0",
|
|
87
|
+
"typescript": "~5.9.3",
|
|
88
|
+
"typescript-eslint": "^8.51.0",
|
|
89
|
+
"vite": "^7.3.0"
|
|
90
|
+
},
|
|
91
|
+
"publishConfig": {
|
|
92
|
+
"access": "public",
|
|
93
|
+
"registry": "https://registry.npmjs.org"
|
|
94
|
+
},
|
|
95
|
+
"ember": {
|
|
96
|
+
"edition": "octane"
|
|
97
|
+
},
|
|
98
|
+
"ember-addon": {
|
|
99
|
+
"version": 2,
|
|
100
|
+
"type": "addon",
|
|
101
|
+
"main": "addon-main.cjs",
|
|
102
|
+
"app-js": {
|
|
103
|
+
"./services/media.js": "./dist/_app_/services/media.js"
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"scripts": {
|
|
107
|
+
"build": "rollup --config",
|
|
108
|
+
"format": "prettier . --cache --write",
|
|
109
|
+
"lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto",
|
|
110
|
+
"lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto && pnpm run format",
|
|
111
|
+
"lint:format": "prettier . --cache --check",
|
|
112
|
+
"lint:hbs": "ember-template-lint . --no-error-on-unmatched-pattern",
|
|
113
|
+
"lint:hbs:fix": "ember-template-lint . --fix --no-error-on-unmatched-pattern",
|
|
114
|
+
"lint:js": "eslint . --cache",
|
|
115
|
+
"lint:js:fix": "eslint . --fix",
|
|
116
|
+
"lint:types": "ember-tsc --noEmit",
|
|
117
|
+
"start": "vite dev",
|
|
118
|
+
"test": "vite build --mode=development --out-dir dist-tests && testem --file testem.cjs ci --port 0"
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { assert } from '@ember/debug';
|
|
2
|
+
import Service from '@ember/service';
|
|
3
|
+
import {
|
|
4
|
+
getOwnConfig,
|
|
5
|
+
isDevelopingApp,
|
|
6
|
+
isTesting,
|
|
7
|
+
macroCondition,
|
|
8
|
+
} from '@embroider/macros';
|
|
9
|
+
import { tracked } from '@glimmer/tracking';
|
|
10
|
+
import { runTask } from 'ember-lifeline';
|
|
11
|
+
import { TrackedSet } from 'tracked-built-ins';
|
|
12
|
+
|
|
13
|
+
import type Owner from '@ember/owner';
|
|
14
|
+
|
|
15
|
+
type Fn = () => unknown | Promise<unknown>;
|
|
16
|
+
type Callbacks = {
|
|
17
|
+
change: Set<Fn>;
|
|
18
|
+
};
|
|
19
|
+
type Matcher = {
|
|
20
|
+
matches: boolean;
|
|
21
|
+
media: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const defaultBreakpoints = Object.freeze({
|
|
25
|
+
xsmall: '(min-width: 0px) and (max-width: 575px)',
|
|
26
|
+
small: '(min-width: 576px) and (max-width: 767px)',
|
|
27
|
+
medium: '(min-width: 768px) and (max-width: 991px)',
|
|
28
|
+
large: '(min-width: 992px) and (max-width: 1199px)',
|
|
29
|
+
xlarge: '(min-width: 1200px) and (max-width: 1399px)',
|
|
30
|
+
xxlarge: '(min-width: 1400px)',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export default class Media extends Service {
|
|
34
|
+
_mockedBreakpoint = 'desktop';
|
|
35
|
+
|
|
36
|
+
@tracked
|
|
37
|
+
_matches = new TrackedSet<string>();
|
|
38
|
+
|
|
39
|
+
@tracked
|
|
40
|
+
mocked = macroCondition(isTesting() || isDevelopingApp()) ? true : false;
|
|
41
|
+
|
|
42
|
+
callbacks: Callbacks = {
|
|
43
|
+
change: new Set(),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
breakpoints = {
|
|
47
|
+
...defaultBreakpoints,
|
|
48
|
+
...getOwnConfig()?.breakpoints,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
constructor(owner: Owner) {
|
|
52
|
+
super(owner);
|
|
53
|
+
|
|
54
|
+
for (const [name, query] of Object.entries(this.breakpoints)) {
|
|
55
|
+
this.match(name, query);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get matches(): Set<string> {
|
|
60
|
+
if (
|
|
61
|
+
(macroCondition(isTesting() || isDevelopingApp()) ? true : false) &&
|
|
62
|
+
this.mocked
|
|
63
|
+
) {
|
|
64
|
+
return new TrackedSet([this._mockedBreakpoint]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return this._matches;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private set matches(value: Iterable<string>) {
|
|
71
|
+
this._matches = new TrackedSet(value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get isXSmall(): boolean {
|
|
75
|
+
return this.matches.has('xsmall');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get isSmall(): boolean {
|
|
79
|
+
return this.matches.has('small');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get isMedium(): boolean {
|
|
83
|
+
return this.matches.has('medium');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get isLarge(): boolean {
|
|
87
|
+
return this.matches.has('large');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get isXLarge(): boolean {
|
|
91
|
+
return this.matches.has('xlarge');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get isXXLarge(): boolean {
|
|
95
|
+
return this.matches.has('xxlarge');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#getCallbackList(name: keyof Callbacks) {
|
|
99
|
+
const callbackList = this.callbacks[name];
|
|
100
|
+
|
|
101
|
+
assert(`Callback '${name}' is not valid`, callbackList !== undefined);
|
|
102
|
+
|
|
103
|
+
return callbackList;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
on(name: keyof Callbacks, callback: Fn) {
|
|
107
|
+
const callbackList = this.#getCallbackList(name);
|
|
108
|
+
|
|
109
|
+
callbackList.add(callback);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
off(name: keyof Callbacks, callback: Fn) {
|
|
113
|
+
const callbackList = this.#getCallbackList(name);
|
|
114
|
+
|
|
115
|
+
callbackList.delete(callback);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
trigger(name: keyof Callbacks) {
|
|
119
|
+
const callbackList = this.#getCallbackList(name);
|
|
120
|
+
for (const callback of callbackList) {
|
|
121
|
+
try {
|
|
122
|
+
callback();
|
|
123
|
+
} catch {
|
|
124
|
+
// Ignore
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
match(name: string, query: string) {
|
|
130
|
+
if (macroCondition(isTesting() || isDevelopingApp())) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const mediaQueryList = matchMedia(query);
|
|
135
|
+
|
|
136
|
+
const listener = (matcher: Matcher) => {
|
|
137
|
+
let changed = false;
|
|
138
|
+
|
|
139
|
+
if (matcher.matches) {
|
|
140
|
+
if (!this.matches.has(name)) {
|
|
141
|
+
this.matches.add(name);
|
|
142
|
+
changed = true;
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
changed = this.matches.has(name);
|
|
146
|
+
this.matches.delete(name);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (changed) {
|
|
150
|
+
runTask(this, () => this.trigger('change'));
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
mediaQueryList.addEventListener('change', (event) => {
|
|
155
|
+
runTask(this, () => listener(event));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
listener(mediaQueryList);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
declare module '@ember/service' {
|
|
163
|
+
interface Registry {
|
|
164
|
+
media: Media;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Easily allow apps, which are not yet using strict mode templates, to consume your Glint types, by importing this file.
|
|
2
|
+
// Add all your components, helpers and modifiers to the template registry here, so apps don't have to do this.
|
|
3
|
+
// See https://typed-ember.gitbook.io/glint/environments/ember/authoring-addons
|
|
4
|
+
|
|
5
|
+
// import type MyComponent from './components/my-component';
|
|
6
|
+
|
|
7
|
+
// Uncomment this once entries have been added! 👇
|
|
8
|
+
// export default interface Registry {
|
|
9
|
+
// MyComponent: typeof MyComponent
|
|
10
|
+
// }
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getContext, settled } from '@ember/test-helpers';
|
|
2
|
+
|
|
3
|
+
import type Media from '../services/media.ts';
|
|
4
|
+
import type { TestContext } from '@ember/test-helpers';
|
|
5
|
+
|
|
6
|
+
export async function setBreakpoint(breakpoint: string) {
|
|
7
|
+
const { owner } = getContext() as TestContext;
|
|
8
|
+
const media = owner.lookup('service:media') as Media;
|
|
9
|
+
|
|
10
|
+
if (breakpoint === 'auto') {
|
|
11
|
+
media.mocked = false;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (Object.keys(media.breakpoints).indexOf(breakpoint) === -1) {
|
|
16
|
+
throw new Error(`Breakpoint "${breakpoint}" not defined as a breakpoint`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
media.mocked = true;
|
|
20
|
+
media._mockedBreakpoint = breakpoint;
|
|
21
|
+
media.trigger('change');
|
|
22
|
+
|
|
23
|
+
await settled();
|
|
24
|
+
}
|