Skip to content

Commit a56d239

Browse files
committedNov 17, 2023
Require Node.js 18 and add TypeScript types
1 parent 3e36cce commit a56d239

17 files changed

+542
-615
lines changed
 

‎.editorconfig

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
indent_style = tab
5+
end_of_line = lf
6+
charset = utf-8
7+
trim_trailing_whitespace = true
8+
insert_final_newline = true
9+
10+
[*.yml]
11+
indent_style = space
12+
indent_size = 2

‎.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto eol=lf

‎.github/workflows/main.yml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: CI
2+
on:
3+
- push
4+
- pull_request
5+
jobs:
6+
test:
7+
name: Node.js ${{ matrix.node-version }}
8+
runs-on: ubuntu-latest
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
node-version:
13+
- 20
14+
- 18
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-node@v4
18+
with:
19+
node-version: ${{ matrix.node-version }}
20+
- run: npm install
21+
- run: npm test

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
node_modules
2+
yarn.lock

‎.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

‎.travis.yml

-5
This file was deleted.

‎History.md

-75
This file was deleted.

‎LICENSE

+6-20
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,10 @@
1-
(The MIT License)
1+
MIT License
22

3-
Copyright (c) 2014 Component contributors <dev@component.io>
3+
Copyright (c) TJ Holowaychuk <tj@tjholowaychuk.com>
4+
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
45

5-
Permission is hereby granted, free of charge, to any person
6-
obtaining a copy of this software and associated documentation
7-
files (the "Software"), to deal in the Software without
8-
restriction, including without limitation the rights to use,
9-
copy, modify, merge, publish, distribute, sublicense, and/or sell
10-
copies of the Software, and to permit persons to whom the
11-
Software is furnished to do so, subject to the following
12-
conditions:
6+
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:
137

14-
The above copyright notice and this permission notice shall be
15-
included in all copies or substantial portions of the Software.
8+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
169

17-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19-
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21-
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22-
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24-
OTHER DEALINGS IN THE SOFTWARE.
10+
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.

‎Makefile

-7
This file was deleted.

‎Readme.md

-74
This file was deleted.

‎component.json

-14
This file was deleted.

‎index.d.ts

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
type Emitter = {
2+
/**
3+
Register an event handler that listens to a specified event.
4+
5+
@param event - The name of the event to listen to.
6+
@param listener - The function to execute when the event is emitted.
7+
@returns The Emitter instance for method chaining.
8+
*/
9+
on(event: string, listener: (...arguments_: any[]) => void): Emitter;
10+
11+
/**
12+
Register a one-time event handler for a specified event.
13+
14+
@param event - The name of the event to listen to.
15+
@param listener - The function to execute once when the event is emitted.
16+
@returns The Emitter instance for method chaining.
17+
*/
18+
once(event: string, listener: (...arguments_: any[]) => void): Emitter;
19+
20+
/**
21+
Remove a specific event handler for a specified event.
22+
23+
@param event - The name of the event.
24+
@param listener - The specific handler function to remove.
25+
@returns The Emitter instance for method chaining.
26+
*/
27+
off(event: string, listener: (...arguments_: any[]) => void): Emitter; // eslint-disable-line @typescript-eslint/unified-signatures
28+
29+
/**
30+
Remove all event handlers for a specified event.
31+
32+
@param event - The name of the event for which to remove all handlers.
33+
@returns The Emitter instance for method chaining.
34+
*/
35+
off(event: string): Emitter; // eslint-disable-line @typescript-eslint/unified-signatures
36+
37+
/**
38+
Remove all event handlers for all events.
39+
40+
@returns The Emitter instance for method chaining.
41+
*/
42+
off(): Emitter;
43+
44+
/**
45+
Emit an event, invoking all handlers registered for it.
46+
47+
@param event - The name of the event to emit.
48+
@param arguments_ - Arguments to pass to the event handlers.
49+
@returns The Emitter instance for method chaining.
50+
*/
51+
emit(event: string, ...arguments_: any[]): Emitter;
52+
53+
/**
54+
Retrieve the event handlers registered for a specific event.
55+
56+
@param event - The name of the event.
57+
@returns An array of functions registered as handlers for the event.
58+
*/
59+
listeners(event: string): Array<(...arguments_: any[]) => void>;
60+
61+
/**
62+
Check if there are any handlers registered for a specific event.
63+
64+
@param event - The name of the event.
65+
@returns `true` if there are one or more handlers, `false` otherwise.
66+
*/
67+
hasListeners(event: string): boolean;
68+
};
69+
70+
type EmitterConstructor = {
71+
prototype: Emitter;
72+
new (object?: object): Emitter; // eslint-disable-line @typescript-eslint/ban-types
73+
<T extends object>(object: T): T & Emitter; // eslint-disable-line @typescript-eslint/ban-types
74+
};
75+
76+
/**
77+
As an `Emitter` instance:
78+
79+
```
80+
import Emitter from 'component-emitter';
81+
82+
const emitter = new Emitter();
83+
84+
emitter.emit('🦄');
85+
```
86+
87+
As a mixin:
88+
89+
```
90+
import Emitter from 'component-emitter';
91+
92+
const user = {name: 'tobi'};
93+
Emitter(user);
94+
95+
user.emit('I am a user');
96+
```
97+
98+
As a prototype mixin:
99+
100+
```
101+
import Emitter from 'component-emitter';
102+
103+
Emitter(User.prototype);
104+
```
105+
*/
106+
declare const emitter: EmitterConstructor;
107+
108+
export = emitter;

‎index.js

+73-156
Original file line numberDiff line numberDiff line change
@@ -1,174 +1,91 @@
1+
function Emitter(object) {
2+
if (object) {
3+
return mixin(object);
4+
}
15

2-
/**
3-
* Expose `Emitter`.
4-
*/
5-
6-
if (typeof module !== 'undefined') {
7-
module.exports = Emitter;
6+
this._callbacks = {};
87
}
98

10-
/**
11-
* Initialize a new `Emitter`.
12-
*
13-
* @api public
14-
*/
9+
function mixin(object) {
10+
Object.assign(object, Emitter.prototype);
11+
object._callbacks = {};
12+
return object;
13+
}
1514

16-
function Emitter(obj) {
17-
if (obj) return mixin(obj);
15+
Emitter.prototype.on = function (event, listener) {
16+
(this._callbacks['$' + event] = this._callbacks['$' + event] ?? []).push(listener);
17+
return this;
1818
};
1919

20-
/**
21-
* Mixin the emitter properties.
22-
*
23-
* @param {Object} obj
24-
* @return {Object}
25-
* @api private
26-
*/
27-
28-
function mixin(obj) {
29-
for (var key in Emitter.prototype) {
30-
obj[key] = Emitter.prototype[key];
31-
}
32-
return obj;
33-
}
20+
Emitter.prototype.once = function (event, listener) {
21+
const on = (...arguments_) => {
22+
this.off(event, on);
23+
listener.apply(this, arguments_);
24+
};
3425

35-
/**
36-
* Listen on the given `event` with `fn`.
37-
*
38-
* @param {String} event
39-
* @param {Function} fn
40-
* @return {Emitter}
41-
* @api public
42-
*/
43-
44-
Emitter.prototype.on =
45-
Emitter.prototype.addEventListener = function(event, fn){
46-
this._callbacks = this._callbacks || {};
47-
(this._callbacks['$' + event] = this._callbacks['$' + event] || [])
48-
.push(fn);
49-
return this;
26+
on.fn = listener;
27+
this.on(event, on);
28+
return this;
5029
};
5130

52-
/**
53-
* Adds an `event` listener that will be invoked a single
54-
* time then automatically removed.
55-
*
56-
* @param {String} event
57-
* @param {Function} fn
58-
* @return {Emitter}
59-
* @api public
60-
*/
61-
62-
Emitter.prototype.once = function(event, fn){
63-
function on() {
64-
this.off(event, on);
65-
fn.apply(this, arguments);
66-
}
67-
68-
on.fn = fn;
69-
this.on(event, on);
70-
return this;
31+
Emitter.prototype.off = function (event, listener) {
32+
// No arguments: remove all callbacks
33+
if (event === undefined && listener === undefined) {
34+
this._callbacks = {};
35+
return this;
36+
}
37+
38+
// Only event specified: remove all listeners for that event
39+
if (listener === undefined) {
40+
delete this._callbacks['$' + event];
41+
return this;
42+
}
43+
44+
// Both event and listener specified: remove the specific listener
45+
const callbacks = this._callbacks['$' + event];
46+
if (callbacks) {
47+
for (const [index, callback] of callbacks.entries()) {
48+
if (callback === listener || callback.fn === listener) {
49+
callbacks.splice(index, 1);
50+
break;
51+
}
52+
}
53+
54+
// Clean up if no more listeners remain for the event
55+
if (callbacks.length === 0) {
56+
delete this._callbacks['$' + event];
57+
}
58+
}
59+
60+
return this;
7161
};
7262

73-
/**
74-
* Remove the given callback for `event` or all
75-
* registered callbacks.
76-
*
77-
* @param {String} event
78-
* @param {Function} fn
79-
* @return {Emitter}
80-
* @api public
81-
*/
82-
83-
Emitter.prototype.off =
84-
Emitter.prototype.removeListener =
85-
Emitter.prototype.removeAllListeners =
86-
Emitter.prototype.removeEventListener = function(event, fn){
87-
this._callbacks = this._callbacks || {};
88-
89-
// all
90-
if (0 == arguments.length) {
91-
this._callbacks = {};
92-
return this;
93-
}
94-
95-
// specific event
96-
var callbacks = this._callbacks['$' + event];
97-
if (!callbacks) return this;
98-
99-
// remove all handlers
100-
if (1 == arguments.length) {
101-
delete this._callbacks['$' + event];
102-
return this;
103-
}
104-
105-
// remove specific handler
106-
var cb;
107-
for (var i = 0; i < callbacks.length; i++) {
108-
cb = callbacks[i];
109-
if (cb === fn || cb.fn === fn) {
110-
callbacks.splice(i, 1);
111-
break;
112-
}
113-
}
114-
115-
// Remove event specific arrays for event types that no
116-
// one is subscribed for to avoid memory leak.
117-
if (callbacks.length === 0) {
118-
delete this._callbacks['$' + event];
119-
}
120-
121-
return this;
63+
Emitter.prototype.emit = function (event, ...arguments_) {
64+
let callbacks = this._callbacks['$' + event];
65+
if (callbacks) {
66+
callbacks = [...callbacks];
67+
for (const callback of callbacks) {
68+
callback.apply(this, arguments_);
69+
}
70+
}
71+
72+
return this;
12273
};
12374

124-
/**
125-
* Emit `event` with the given args.
126-
*
127-
* @param {String} event
128-
* @param {Mixed} ...
129-
* @return {Emitter}
130-
*/
131-
132-
Emitter.prototype.emit = function(event){
133-
this._callbacks = this._callbacks || {};
134-
135-
var callbacks = this._callbacks['$' + event];
136-
137-
if (callbacks) {
138-
var args = new Array(arguments.length - 1);
139-
for (var i = 1; i < arguments.length; i++) {
140-
args[i - 1] = arguments[i];
141-
}
142-
callbacks = callbacks.slice(0);
143-
for (var i = 0, len = callbacks.length; i < len; ++i) {
144-
callbacks[i].apply(this, args);
145-
}
146-
}
147-
148-
return this;
75+
Emitter.prototype.listeners = function (event) {
76+
return this._callbacks['$' + event] ?? [];
14977
};
15078

151-
/**
152-
* Return array of callbacks for `event`.
153-
*
154-
* @param {String} event
155-
* @return {Array}
156-
* @api public
157-
*/
158-
159-
Emitter.prototype.listeners = function(event){
160-
this._callbacks = this._callbacks || {};
161-
return this._callbacks['$' + event] || [];
79+
Emitter.prototype.hasListeners = function (event) {
80+
return this.listeners(event).length > 0;
16281
};
16382

164-
/**
165-
* Check if this emitter has `event` handlers.
166-
*
167-
* @param {String} event
168-
* @return {Boolean}
169-
* @api public
170-
*/
83+
// Aliases
84+
Emitter.prototype.addEventListener = Emitter.prototype.on;
85+
Emitter.prototype.removeListener = Emitter.prototype.off;
86+
Emitter.prototype.removeEventListener = Emitter.prototype.off;
87+
Emitter.prototype.removeAllListeners = Emitter.prototype.off;
17188

172-
Emitter.prototype.hasListeners = function(event){
173-
return !! this.listeners(event).length;
174-
};
89+
if (typeof module !== 'undefined') {
90+
module.exports = Emitter;
91+
}

‎package.json

+35-25
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
{
2-
"name": "component-emitter",
3-
"description": "Event emitter",
4-
"version": "1.3.1",
5-
"license": "MIT",
6-
"devDependencies": {
7-
"mocha": "*",
8-
"should": "*"
9-
},
10-
"component": {
11-
"scripts": {
12-
"emitter/index.js": "index.js"
13-
}
14-
},
15-
"main": "index.js",
16-
"repository": {
17-
"type": "git",
18-
"url": "https://github.com/sindresorhus/component-emitter.git"
19-
},
20-
"scripts": {
21-
"test": "make test"
22-
},
23-
"files": [
24-
"index.js",
25-
"LICENSE"
26-
]
2+
"name": "component-emitter",
3+
"version": "0.0.0",
4+
"description": "Simple event emitter",
5+
"license": "MIT",
6+
"repository": "sindresorhus/component-emitter",
7+
"funding": "https://github.com/sponsors/sindresorhus",
8+
"exports": {
9+
"types": "./index.d.ts",
10+
"default": "./index.js"
11+
},
12+
"main": "./index.js",
13+
"types": "./index.d.ts",
14+
"sideEffects": false,
15+
"engines": {
16+
"node": ">=18"
17+
},
18+
"scripts": {
19+
"test": "xo && ava"
20+
},
21+
"files": [
22+
"index.js",
23+
"index.d.ts"
24+
],
25+
"keywords": [
26+
""
27+
],
28+
"devDependencies": {
29+
"ava": "^5.3.1",
30+
"xo": "^0.56.0"
31+
},
32+
"xo": {
33+
"rules": {
34+
"unicorn/prefer-module": "off"
35+
}
36+
}
2737
}

‎readme.md

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# component-emitter
2+
3+
> Simple event emitter
4+
5+
## Install
6+
7+
```sh
8+
npm install component-emitter
9+
```
10+
11+
## Usage
12+
13+
As an `Emitter` instance:
14+
15+
```js
16+
import Emitter from 'component-emitter';
17+
18+
const emitter = new Emitter();
19+
20+
emitter.emit('🦄');
21+
```
22+
23+
As a mixin:
24+
25+
```js
26+
import Emitter from 'component-emitter';
27+
28+
const user = {name: 'tobi'};
29+
Emitter(user);
30+
31+
user.emit('I am a user');
32+
```
33+
34+
As a prototype mixin:
35+
36+
```js
37+
import Emitter from 'component-emitter';
38+
39+
Emitter(User.prototype);
40+
```
41+
42+
## API
43+
44+
### Emitter(object)
45+
46+
The `Emitter` may also be used as a mixin. For example a plain object may become an emitter, or you may extend an existing prototype.
47+
48+
### Emitter#on(event, listener)
49+
50+
Register an event handler that listens to a specified event.
51+
52+
### Emitter#once(event, listener)
53+
54+
Register a one-time event handler for a specified event.
55+
56+
### Emitter#off(event, listener)
57+
58+
Remove a specific event handler for a specified event.
59+
60+
### Emitter#off(event)
61+
62+
Remove all event handlers for a specified event.
63+
64+
### Emitter#off()
65+
66+
Remove all event handlers for all events.
67+
68+
### Emitter#emit(event, ...arguments)
69+
70+
Emit an event, invoking all handlers registered for it.
71+
72+
### Emitter#listeners(event)
73+
74+
Retrieve the event handlers registered for a specific event.
75+
76+
### Emitter#hasListeners(event)
77+
78+
Check if there are any handlers registered for a specific event.

‎test.js

+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
const test = require('ava');
2+
const Emitter = require('./index.js');
3+
4+
function Custom() {
5+
Emitter.call(this);
6+
}
7+
8+
Object.setPrototypeOf(Custom.prototype, Emitter.prototype);
9+
10+
test('Custom Emitter: should work with Emitter.call(this)', t => {
11+
t.plan(1);
12+
const emitter = new Custom();
13+
emitter.on('foo', () => t.pass());
14+
emitter.emit('foo');
15+
});
16+
17+
test('Emitter: .on(event, fn) should add listeners', t => {
18+
const emitter = new Emitter();
19+
const calls = [];
20+
21+
emitter.on('foo', value => {
22+
calls.push('one', value);
23+
});
24+
25+
emitter.on('foo', value => {
26+
calls.push('two', value);
27+
});
28+
29+
emitter.emit('foo', 1);
30+
emitter.emit('bar', 1);
31+
emitter.emit('foo', 2);
32+
33+
t.deepEqual(calls, ['one', 1, 'two', 1, 'one', 2, 'two', 2]);
34+
});
35+
36+
test('Emitter: .on(event, fn) should handle Object.prototype method names', t => {
37+
const emitter = new Emitter();
38+
const calls = [];
39+
40+
emitter.on('constructor', value => {
41+
calls.push('one', value);
42+
});
43+
44+
emitter.on('__proto__', value => {
45+
calls.push('two', value);
46+
});
47+
48+
emitter.emit('constructor', 1);
49+
emitter.emit('__proto__', 2);
50+
51+
t.deepEqual(calls, ['one', 1, 'two', 2]);
52+
});
53+
54+
test('Emitter: .once(event, fn) should add a single-shot listener', t => {
55+
const emitter = new Emitter();
56+
const calls = [];
57+
58+
emitter.once('foo', value => {
59+
calls.push('one', value);
60+
});
61+
62+
emitter.emit('foo', 1);
63+
emitter.emit('foo', 2);
64+
emitter.emit('foo', 3);
65+
emitter.emit('bar', 1);
66+
67+
t.deepEqual(calls, ['one', 1]);
68+
});
69+
70+
test('Emitter: .off(event, fn) should remove a listener', t => {
71+
const emitter = new Emitter();
72+
const calls = [];
73+
74+
function one() {
75+
calls.push('one');
76+
}
77+
78+
function two() {
79+
calls.push('two');
80+
}
81+
82+
emitter.on('foo', one);
83+
emitter.on('foo', two);
84+
emitter.off('foo', two);
85+
86+
emitter.emit('foo');
87+
88+
t.deepEqual(calls, ['one']);
89+
});
90+
91+
test('Emitter: .off(event, fn) should work with .once()', t => {
92+
const emitter = new Emitter();
93+
const calls = [];
94+
95+
function one() {
96+
calls.push('one');
97+
}
98+
99+
emitter.once('foo', one);
100+
emitter.once('fee', one);
101+
emitter.off('foo', one);
102+
103+
emitter.emit('foo');
104+
105+
t.deepEqual(calls, []);
106+
});
107+
108+
test('Emitter: .off(event, fn) should work when called from an event', t => {
109+
let called = false;
110+
const emitter = new Emitter();
111+
112+
function b() {
113+
called = true;
114+
}
115+
116+
emitter.on('tobi', () => {
117+
emitter.off('tobi', b);
118+
});
119+
120+
emitter.on('tobi', b);
121+
emitter.emit('tobi');
122+
t.true(called);
123+
called = false;
124+
emitter.emit('tobi');
125+
t.false(called);
126+
});
127+
128+
test('Emitter: .off(event) should remove all listeners for an event', t => {
129+
const emitter = new Emitter();
130+
const calls = [];
131+
132+
function one() {
133+
calls.push('one');
134+
}
135+
136+
function two() {
137+
calls.push('two');
138+
}
139+
140+
emitter.on('foo', one);
141+
emitter.on('foo', two);
142+
emitter.off('foo');
143+
144+
emitter.emit('foo');
145+
emitter.emit('foo');
146+
147+
t.deepEqual(calls, []);
148+
});
149+
150+
test('Emitter: .off() should remove all listeners', t => {
151+
const emitter = new Emitter();
152+
const calls = [];
153+
154+
function one() {
155+
calls.push('one');
156+
}
157+
158+
function two() {
159+
calls.push('two');
160+
}
161+
162+
emitter.on('foo', one);
163+
emitter.on('bar', two);
164+
165+
emitter.emit('foo');
166+
emitter.emit('bar');
167+
168+
emitter.off();
169+
170+
emitter.emit('foo');
171+
emitter.emit('bar');
172+
173+
t.deepEqual(calls, ['one', 'two']);
174+
});
175+
176+
test('Emitter: .listeners(event) should return callbacks when present', t => {
177+
const emitter = new Emitter();
178+
function foo() {}
179+
180+
emitter.on('foo', foo);
181+
t.deepEqual(emitter.listeners('foo'), [foo]);
182+
});
183+
184+
test('Emitter: .listeners(event) should return an empty array when no handlers are present', t => {
185+
const emitter = new Emitter();
186+
t.deepEqual(emitter.listeners('foo'), []);
187+
});
188+
189+
test('Emitter: .hasListeners(event) should return true when handlers are present', t => {
190+
const emitter = new Emitter();
191+
emitter.on('foo', () => {});
192+
t.true(emitter.hasListeners('foo'));
193+
});
194+
195+
test('Emitter: .hasListeners(event) should return false when no handlers are present', t => {
196+
const emitter = new Emitter();
197+
t.false(emitter.hasListeners('foo'));
198+
});
199+
200+
test('Mixin with Emitter(obj) should work', t => {
201+
t.plan(1);
202+
const proto = {};
203+
Emitter(proto); // eslint-disable-line new-cap
204+
proto.on('something', () => t.pass());
205+
proto.emit('something');
206+
});

‎test/emitter.js

-239
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.