Merge branch 'master' into fix/checked-index-and-value
This commit is contained in:
commit
0ee55369bc
54 changed files with 1080 additions and 15319 deletions
5
.changeset/hungry-mirrors-stare.md
Normal file
5
.changeset/hungry-mirrors-stare.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@lion/form-core': minor
|
||||
---
|
||||
|
||||
remove name property check on lion field for forward compatibility with Form participation api
|
||||
5
.changeset/little-melons-lay.md
Normal file
5
.changeset/little-melons-lay.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@lion/input-tel': patch
|
||||
---
|
||||
|
||||
Make use of awsome-phonenumber, remove local copy
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
'@lion/input-tel-dropdown': patch
|
||||
---
|
||||
|
||||
sync disable state to dropdown for a11y
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
'@lion/switch': patch
|
||||
---
|
||||
|
||||
fix(switch) unregister on disconnectedCallback
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
'@lion/form-core': patch
|
||||
---
|
||||
|
||||
fix: reset the form validators after a form `reset` click or field emptied
|
||||
3
.github/workflows/verify.yml
vendored
3
.github/workflows/verify.yml
vendored
|
|
@ -9,6 +9,9 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Sanity check
|
||||
run: node ./scripts/yarn-lock-scan.js
|
||||
|
||||
- name: Setup Node 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -219,3 +219,37 @@ export const indeterminateChildren = () => html`
|
|||
</lion-checkbox-group>
|
||||
`;
|
||||
```
|
||||
|
||||
You can also use `mixed-state` attribute so your indeterminate checkbox toggles through three states (indeterminate, checked, unchecked), where for indeterminate state the old children states are restored when you toggle back into this.
|
||||
|
||||
```js preview-story
|
||||
export const mixedState = () => html`
|
||||
<lion-checkbox-group name="scientists[]" label="Favorite scientists">
|
||||
<lion-checkbox-indeterminate mixed-state label="Scientists">
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Isaac Newton"
|
||||
.choiceValue=${'Isaac Newton'}
|
||||
></lion-checkbox>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Galileo Galilei"
|
||||
.choiceValue=${'Galileo Galilei'}
|
||||
></lion-checkbox>
|
||||
<lion-checkbox-indeterminate mixed-state slot="checkbox" label="Old Greek scientists">
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Archimedes"
|
||||
.choiceValue=${'Archimedes'}
|
||||
></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Plato" .choiceValue=${'Plato'}></lion-checkbox>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Pythagoras"
|
||||
.choiceValue=${'Pythagoras'}
|
||||
></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`;
|
||||
```
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ export const disabledRotateNavigation = () => html`
|
|||
<lion-option .choiceValue=${'Beets'}>Beets</lion-option>
|
||||
<lion-option .choiceValue=${'Bell pepper'}>Bell pepper</lion-option>
|
||||
<lion-option .choiceValue=${'Broccoli'}>Broccoli</lion-option>
|
||||
<lion-option .choiceValue=${'Brussel sprout'} disabled>Brussels sprout</lion-option>
|
||||
<lion-option .choiceValue=${'Brussels sprout'} disabled>Brussels sprout</lion-option>
|
||||
<lion-option .choiceValue=${'Cabbage'}>Cabbage</lion-option>
|
||||
<lion-option .choiceValue=${'Carrot'}>Carrot</lion-option>
|
||||
</lion-listbox>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# Change Log
|
||||
|
||||
## 0.15.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 558edcb6: Adds a `maxResponseSize` cache option to specify a max size for responses to be cached. The option prevents caching and cache retrieval for responses that are larger than the given maximum as reported in the `Content-Length` header. If this header is missing nothing happens, that is to say caching is not prevented.
|
||||
- efcdf653: Adds a `contentTypes` cache option to specify a whitelist of content types to be cached. The option prevents caching and cache retrieval for responses that do not have one of these values in the `Content-Type` header.
|
||||
- 56af96f1: Add an option "addCaching" to the Ajax config, in order to add cache interceptors when useCache is turned off. In this situation, all requests are cached proactively.
|
||||
|
||||
## 0.14.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@lion/ajax",
|
||||
"version": "0.14.0",
|
||||
"version": "0.15.0",
|
||||
"description": "Thin wrapper around fetch with support for interceptors.",
|
||||
"license": "MIT",
|
||||
"author": "ing-bank",
|
||||
|
|
|
|||
|
|
@ -23,11 +23,12 @@ export class Ajax {
|
|||
*/
|
||||
constructor(config = {}) {
|
||||
/**
|
||||
* @type {Partial<AjaxConfig>}
|
||||
* @type {AjaxConfig}
|
||||
* @private
|
||||
*/
|
||||
this.__config = {
|
||||
addAcceptLanguage: true,
|
||||
addCaching: false,
|
||||
xsrfCookieName: 'XSRF-TOKEN',
|
||||
xsrfHeaderName: 'X-XSRF-TOKEN',
|
||||
jsonPrefix: '',
|
||||
|
|
@ -53,7 +54,7 @@ export class Ajax {
|
|||
}
|
||||
|
||||
const { cacheOptions } = this.__config;
|
||||
if (cacheOptions?.useCache) {
|
||||
if (cacheOptions.useCache || this.__config.addCaching) {
|
||||
const { cacheRequestInterceptor, cacheResponseInterceptor } = createCacheInterceptors(
|
||||
cacheOptions.getCacheIdentifier,
|
||||
cacheOptions,
|
||||
|
|
@ -65,7 +66,7 @@ export class Ajax {
|
|||
|
||||
/**
|
||||
* Configures the Ajax instance
|
||||
* @param {Partial<AjaxConfig>} config configuration for the Ajax instance
|
||||
* @param {AjaxConfig} config configuration for the Ajax instance
|
||||
*/
|
||||
set options(config) {
|
||||
this.__config = config;
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ export const extendCacheOptions = ({
|
|||
requestIdFunction = DEFAULT_GET_REQUEST_ID,
|
||||
invalidateUrls,
|
||||
invalidateUrlsRegex,
|
||||
contentTypes,
|
||||
maxResponseSize,
|
||||
}) => ({
|
||||
useCache,
|
||||
methods,
|
||||
|
|
@ -89,6 +91,8 @@ export const extendCacheOptions = ({
|
|||
requestIdFunction,
|
||||
invalidateUrls,
|
||||
invalidateUrlsRegex,
|
||||
contentTypes,
|
||||
maxResponseSize,
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -101,6 +105,8 @@ export const validateCacheOptions = ({
|
|||
requestIdFunction,
|
||||
invalidateUrls,
|
||||
invalidateUrlsRegex,
|
||||
contentTypes,
|
||||
maxResponseSize,
|
||||
} = {}) => {
|
||||
if (useCache !== undefined && typeof useCache !== 'boolean') {
|
||||
throw new Error('Property `useCache` must be a `boolean`');
|
||||
|
|
@ -112,14 +118,20 @@ export const validateCacheOptions = ({
|
|||
throw new Error('Property `maxAge` must be a finite `number`');
|
||||
}
|
||||
if (invalidateUrls !== undefined && !Array.isArray(invalidateUrls)) {
|
||||
throw new Error('Property `invalidateUrls` must be an `Array` or `falsy`');
|
||||
throw new Error('Property `invalidateUrls` must be an `Array` or `undefined`');
|
||||
}
|
||||
if (invalidateUrlsRegex !== undefined && !(invalidateUrlsRegex instanceof RegExp)) {
|
||||
throw new Error('Property `invalidateUrlsRegex` must be a `RegExp` or `falsy`');
|
||||
throw new Error('Property `invalidateUrlsRegex` must be a `RegExp` or `undefined`');
|
||||
}
|
||||
if (requestIdFunction !== undefined && typeof requestIdFunction !== 'function') {
|
||||
throw new Error('Property `requestIdFunction` must be a `function`');
|
||||
}
|
||||
if (contentTypes !== undefined && !Array.isArray(contentTypes)) {
|
||||
throw new Error('Property `contentTypes` must be an `Array` or `undefined`');
|
||||
}
|
||||
if (maxResponseSize !== undefined && !Number.isFinite(maxResponseSize)) {
|
||||
throw new Error('Property `maxResponseSize` must be a finite `number`');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,6 +10,42 @@ import {
|
|||
isCurrentSessionId,
|
||||
} from '../cacheManager.js';
|
||||
|
||||
/**
|
||||
* Tests whether the request method is supported according to the `cacheOptions`
|
||||
* @param {ValidatedCacheOptions} cacheOptions
|
||||
* @param {string} method
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isMethodSupported = (cacheOptions, method) =>
|
||||
cacheOptions.methods.includes(method.toLowerCase());
|
||||
|
||||
/**
|
||||
* Tests whether the response content type is supported by the `contentTypes` whitelist
|
||||
* @param {Response} response
|
||||
* @param {CacheOptions} cacheOptions
|
||||
* @returns {boolean} `true` if the contentTypes property is not an array, or if the value of the Content-Type header is in the array
|
||||
*/
|
||||
const isResponseContentTypeSupported = (response, { contentTypes } = {}) => {
|
||||
if (!Array.isArray(contentTypes)) return true;
|
||||
|
||||
return contentTypes.includes(String(response.headers.get('Content-Type')));
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests whether the response size is not too large to be cached according to the `maxResponseSize` property
|
||||
* @param {Response} response
|
||||
* @param {CacheOptions} cacheOptions
|
||||
* @returns {boolean} `true` if the `maxResponseSize` property is not larger than zero, or if the Content-Length header is not present, or if the value of the header is not larger than the `maxResponseSize` property
|
||||
*/
|
||||
const isResponseSizeSupported = (response, { maxResponseSize } = {}) => {
|
||||
const responseSize = +(response.headers.get('Content-Length') || 0);
|
||||
|
||||
if (!maxResponseSize) return true;
|
||||
if (!responseSize) return true;
|
||||
|
||||
return responseSize <= maxResponseSize;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request interceptor to return relevant cached requests
|
||||
* @param {function(): string} getCacheId used to invalidate cache if identifier is changed
|
||||
|
|
@ -36,9 +72,8 @@ const createCacheRequestInterceptor =
|
|||
}
|
||||
|
||||
const requestId = cacheOptions.requestIdFunction(request);
|
||||
const isMethodSupported = cacheOptions.methods.includes(request.method.toLowerCase());
|
||||
|
||||
if (!isMethodSupported) {
|
||||
if (!isMethodSupported(cacheOptions, request.method)) {
|
||||
invalidateMatchingCache(requestId, cacheOptions);
|
||||
return request;
|
||||
}
|
||||
|
|
@ -50,7 +85,11 @@ const createCacheRequestInterceptor =
|
|||
}
|
||||
|
||||
const cachedResponse = ajaxCache.get(requestId, cacheOptions.maxAge);
|
||||
if (cachedResponse) {
|
||||
if (
|
||||
cachedResponse &&
|
||||
isResponseContentTypeSupported(cachedResponse, cacheOptions) &&
|
||||
isResponseSizeSupported(cachedResponse, cacheOptions)
|
||||
) {
|
||||
// Return the response from cache
|
||||
request.cacheOptions = request.cacheOptions ?? { useCache: false };
|
||||
/** @type {CacheResponse} */
|
||||
|
|
@ -81,13 +120,14 @@ const createCacheResponseInterceptor =
|
|||
...response.request.cacheOptions,
|
||||
});
|
||||
|
||||
if (!response.fromCache && isMethodSupported(cacheOptions, response.request.method)) {
|
||||
const requestId = cacheOptions.requestIdFunction(response.request);
|
||||
const isAlreadyFromCache = !!response.fromCache;
|
||||
const isCacheActive = cacheOptions.useCache;
|
||||
const isMethodSupported = cacheOptions.methods.includes(response.request?.method.toLowerCase());
|
||||
|
||||
if (!isAlreadyFromCache && isCacheActive && isMethodSupported) {
|
||||
if (isCurrentSessionId(response.request.cacheSessionId)) {
|
||||
if (
|
||||
isCurrentSessionId(response.request.cacheSessionId) &&
|
||||
isResponseContentTypeSupported(response, cacheOptions) &&
|
||||
isResponseSizeSupported(response, cacheOptions)
|
||||
) {
|
||||
// Cache the response
|
||||
ajaxCache.set(requestId, response.clone());
|
||||
}
|
||||
|
|
@ -95,6 +135,7 @@ const createCacheResponseInterceptor =
|
|||
// Mark the pending request as resolved
|
||||
pendingRequestStore.resolve(requestId);
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ describe('Ajax', () => {
|
|||
};
|
||||
const expected = {
|
||||
addAcceptLanguage: true,
|
||||
addCaching: false,
|
||||
xsrfCookieName: 'XSRF-TOKEN',
|
||||
xsrfHeaderName: 'X-XSRF-TOKEN',
|
||||
jsonPrefix: ")]}',",
|
||||
|
|
@ -303,6 +304,44 @@ describe('Ajax', () => {
|
|||
getCacheIdentifier = () => String(cacheId);
|
||||
});
|
||||
|
||||
it('does not add cache interceptors when useCache is turned off', () => {
|
||||
const customAjax = new Ajax({
|
||||
cacheOptions: {
|
||||
maxAge: 100,
|
||||
getCacheIdentifier,
|
||||
},
|
||||
});
|
||||
|
||||
expect(customAjax._requestInterceptors.length).to.equal(2);
|
||||
expect(customAjax._responseInterceptors.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('adds cache interceptors when useCache is turned on', () => {
|
||||
const customAjax = new Ajax({
|
||||
cacheOptions: {
|
||||
useCache: true,
|
||||
maxAge: 100,
|
||||
getCacheIdentifier,
|
||||
},
|
||||
});
|
||||
|
||||
expect(customAjax._requestInterceptors.length).to.equal(3);
|
||||
expect(customAjax._responseInterceptors.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('adds cache interceptors when addCaching is turned on', () => {
|
||||
const customAjax = new Ajax({
|
||||
addCaching: true,
|
||||
cacheOptions: {
|
||||
maxAge: 100,
|
||||
getCacheIdentifier,
|
||||
},
|
||||
});
|
||||
|
||||
expect(customAjax._requestInterceptors.length).to.equal(3);
|
||||
expect(customAjax._responseInterceptors.length).to.equal(1);
|
||||
});
|
||||
|
||||
describe('caching interceptors', async () => {
|
||||
/**
|
||||
* @type {Ajax}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ describe('cacheManager', () => {
|
|||
requestIdFunction,
|
||||
invalidateUrls: invalidateUrlsResult,
|
||||
invalidateUrlsRegex: invalidateUrlsRegexResult,
|
||||
contentTypes,
|
||||
maxResponseSize,
|
||||
} = extendCacheOptions({ invalidateUrls, invalidateUrlsRegex });
|
||||
// Assert
|
||||
expect(useCache).to.be.false;
|
||||
|
|
@ -82,6 +84,8 @@ describe('cacheManager', () => {
|
|||
expect(typeof requestIdFunction).to.eql('function');
|
||||
expect(invalidateUrlsResult).to.equal(invalidateUrls);
|
||||
expect(invalidateUrlsRegexResult).to.equal(invalidateUrlsRegex);
|
||||
expect(contentTypes).to.be.undefined;
|
||||
expect(maxResponseSize).to.be.undefined;
|
||||
});
|
||||
|
||||
it('the DEFAULT_GET_REQUEST_ID function throws when called with no arguments', () => {
|
||||
|
|
@ -129,18 +133,22 @@ describe('cacheManager', () => {
|
|||
it('does not accept null as argument', () => {
|
||||
expect(() => validateCacheOptions(null)).to.throw(TypeError);
|
||||
});
|
||||
|
||||
it('accepts an empty object', () => {
|
||||
expect(() => validateCacheOptions({})).not.to.throw(
|
||||
'Property `useCache` must be a `boolean`',
|
||||
);
|
||||
});
|
||||
|
||||
describe('the useCache property', () => {
|
||||
it('accepts a boolean', () => {
|
||||
expect(() => validateCacheOptions({ useCache: false })).not.to.throw;
|
||||
});
|
||||
|
||||
it('accepts undefined', () => {
|
||||
expect(() => validateCacheOptions({ useCache: undefined })).not.to.throw;
|
||||
});
|
||||
|
||||
it('does not accept anything else', () => {
|
||||
// @ts-ignore
|
||||
expect(() => validateCacheOptions({ useCache: '' })).to.throw(
|
||||
|
|
@ -148,13 +156,16 @@ describe('cacheManager', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the methods property', () => {
|
||||
it('accepts an array with the value `get`', () => {
|
||||
expect(() => validateCacheOptions({ methods: ['get'] })).not.to.throw;
|
||||
});
|
||||
|
||||
it('accepts undefined', () => {
|
||||
expect(() => validateCacheOptions({ methods: undefined })).not.to.throw;
|
||||
});
|
||||
|
||||
it('does not accept anything else', () => {
|
||||
expect(() => validateCacheOptions({ methods: [] })).to.throw(
|
||||
'Cache can only be utilized with `GET` method',
|
||||
|
|
@ -167,13 +178,16 @@ describe('cacheManager', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the maxAge property', () => {
|
||||
it('accepts a finite number', () => {
|
||||
expect(() => validateCacheOptions({ maxAge: 42 })).not.to.throw;
|
||||
});
|
||||
|
||||
it('accepts undefined', () => {
|
||||
expect(() => validateCacheOptions({ maxAge: undefined })).not.to.throw;
|
||||
});
|
||||
|
||||
it('does not accept anything else', () => {
|
||||
// @ts-ignore
|
||||
expect(() => validateCacheOptions({ maxAge: 'string' })).to.throw(
|
||||
|
|
@ -184,6 +198,7 @@ describe('cacheManager', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the invalidateUrls property', () => {
|
||||
it('accepts an array', () => {
|
||||
// @ts-ignore Typescript requires this to be an array of string, but this is not checked by validateCacheOptions
|
||||
|
|
@ -191,31 +206,37 @@ describe('cacheManager', () => {
|
|||
validateCacheOptions({ invalidateUrls: [6, 'elements', 'in', 1, true, Array] }),
|
||||
).not.to.throw;
|
||||
});
|
||||
|
||||
it('accepts undefined', () => {
|
||||
expect(() => validateCacheOptions({ invalidateUrls: undefined })).not.to.throw;
|
||||
});
|
||||
|
||||
it('does not accept anything else', () => {
|
||||
// @ts-ignore
|
||||
expect(() => validateCacheOptions({ invalidateUrls: 'not-an-array' })).to.throw(
|
||||
'Property `invalidateUrls` must be an `Array` or `falsy`',
|
||||
'Property `invalidateUrls` must be an `Array` or `undefined`',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the invalidateUrlsRegex property', () => {
|
||||
it('accepts a regular expression', () => {
|
||||
expect(() => validateCacheOptions({ invalidateUrlsRegex: /this is a very picky regex/ }))
|
||||
.not.to.throw;
|
||||
});
|
||||
|
||||
it('accepts undefined', () => {
|
||||
expect(() => validateCacheOptions({ invalidateUrlsRegex: undefined })).not.to.throw;
|
||||
});
|
||||
|
||||
it('does not accept anything else', () => {
|
||||
// @ts-ignore
|
||||
expect(() =>
|
||||
validateCacheOptions({ invalidateUrlsRegex: 'a string is not a regex' }),
|
||||
).to.throw('Property `invalidateUrlsRegex` must be a `RegExp` or `falsy`');
|
||||
).to.throw('Property `invalidateUrlsRegex` must be a `RegExp` or `undefined`');
|
||||
});
|
||||
});
|
||||
|
||||
describe('the requestIdFunction property', () => {
|
||||
it('accepts a function', () => {
|
||||
// @ts-ignore Typescript requires the requestIdFunction to return a string, but this is not checked by validateCacheOptions
|
||||
|
|
@ -223,9 +244,11 @@ describe('cacheManager', () => {
|
|||
validateCacheOptions({ requestIdFunction: () => ['this-is-ok-outside-typescript'] }),
|
||||
).not.to.throw;
|
||||
});
|
||||
|
||||
it('accepts undefined', () => {
|
||||
expect(() => validateCacheOptions({ requestIdFunction: undefined })).not.to.throw;
|
||||
});
|
||||
|
||||
it('does not accept anything else', () => {
|
||||
// @ts-ignore
|
||||
expect(() => validateCacheOptions({ requestIdFunction: 'not a function' })).to.throw(
|
||||
|
|
@ -233,6 +256,45 @@ describe('cacheManager', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the contentTypes property', () => {
|
||||
it('accepts an array', () => {
|
||||
// @ts-ignore Typescript requires this to be an array of string, but this is not checked by validateCacheOptions
|
||||
expect(() => validateCacheOptions({ contentTypes: [6, 'elements', 'in', 1, true, Array] }))
|
||||
.not.to.throw;
|
||||
});
|
||||
|
||||
it('accepts undefined', () => {
|
||||
expect(() => validateCacheOptions({ contentTypes: undefined })).not.to.throw;
|
||||
});
|
||||
|
||||
it('does not accept anything else', () => {
|
||||
// @ts-ignore
|
||||
expect(() => validateCacheOptions({ contentTypes: 'not-an-array' })).to.throw(
|
||||
'Property `contentTypes` must be an `Array` or `undefined`',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the maxResponseSize property', () => {
|
||||
it('accepts a finite number', () => {
|
||||
expect(() => validateCacheOptions({ maxResponseSize: 42 })).not.to.throw;
|
||||
});
|
||||
|
||||
it('accepts undefined', () => {
|
||||
expect(() => validateCacheOptions({ maxResponseSize: undefined })).not.to.throw;
|
||||
});
|
||||
|
||||
it('does not accept anything else', () => {
|
||||
// @ts-ignore
|
||||
expect(() => validateCacheOptions({ maxResponseSize: 'string' })).to.throw(
|
||||
'Property `maxResponseSize` must be a finite `number`',
|
||||
);
|
||||
expect(() => validateCacheOptions({ maxResponseSize: Infinity })).to.throw(
|
||||
'Property `maxResponseSize` must be a finite `number`',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalidateMatchingCache', () => {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ describe('cache interceptors', () => {
|
|||
let cacheId;
|
||||
/** @type {sinon.SinonStub} */
|
||||
let fetchStub;
|
||||
/** @type {Response} */
|
||||
let mockResponse;
|
||||
const getCacheIdentifier = () => String(cacheId);
|
||||
/** @type {sinon.SinonSpy} */
|
||||
let ajaxRequestSpy;
|
||||
|
|
@ -51,8 +53,9 @@ describe('cache interceptors', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
ajax = new Ajax();
|
||||
mockResponse = new Response('mock response');
|
||||
fetchStub = sinon.stub(window, 'fetch');
|
||||
fetchStub.returns(Promise.resolve(new Response('mock response')));
|
||||
fetchStub.resolves(mockResponse);
|
||||
ajaxRequestSpy = sinon.spy(ajax, 'fetch');
|
||||
});
|
||||
|
||||
|
|
@ -150,14 +153,12 @@ describe('cache interceptors', () => {
|
|||
expect(fetchStub.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
// TODO: Check if this is the behaviour we want
|
||||
it('all calls with non-default `maxAge` are cached proactively', async () => {
|
||||
it('all calls are cached proactively', async () => {
|
||||
// Given
|
||||
newCacheId();
|
||||
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: false,
|
||||
maxAge: 100,
|
||||
});
|
||||
|
||||
// When
|
||||
|
|
@ -169,11 +170,7 @@ describe('cache interceptors', () => {
|
|||
expect(fetchStub.callCount).to.equal(1);
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test', {
|
||||
cacheOptions: {
|
||||
useCache: true,
|
||||
},
|
||||
});
|
||||
await ajax.fetch('/test');
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
|
|
@ -297,7 +294,7 @@ describe('cache interceptors', () => {
|
|||
);
|
||||
|
||||
// @ts-ignore not an actual valid CacheResponse object
|
||||
await cacheResponseInterceptor({ request: { method: 'get' } })
|
||||
await cacheResponseInterceptor({ request: { method: 'get' }, headers: new Headers() })
|
||||
.then(() => expect('everything').to.be.ok)
|
||||
.catch(err =>
|
||||
expect.fail(
|
||||
|
|
@ -305,6 +302,259 @@ describe('cache interceptors', () => {
|
|||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('caches concurrent requests', async () => {
|
||||
newCacheId();
|
||||
|
||||
const clock = sinon.useFakeTimers();
|
||||
|
||||
fetchStub.onFirstCall().returns(returnResponseOnTick(900, 1));
|
||||
fetchStub.onSecondCall().returns(returnResponseOnTick(1900, 2));
|
||||
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
maxAge: 750,
|
||||
});
|
||||
|
||||
const firstRequest = ajax.fetch('/test').then(r => r.text());
|
||||
const concurrentFirstRequest1 = ajax.fetch('/test').then(r => r.text());
|
||||
const concurrentFirstRequest2 = ajax.fetch('/test').then(r => r.text());
|
||||
|
||||
clock.tick(1000);
|
||||
|
||||
// firstRequest is cached at tick 1000 in the next line!
|
||||
const firstResponses = await Promise.all([
|
||||
firstRequest,
|
||||
concurrentFirstRequest1,
|
||||
concurrentFirstRequest2,
|
||||
]);
|
||||
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
|
||||
const cachedFirstRequest = ajax.fetch('/test').then(r => r.text());
|
||||
|
||||
clock.tick(500);
|
||||
|
||||
const cachedFirstResponse = await cachedFirstRequest;
|
||||
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
|
||||
const secondRequest = ajax.fetch('/test').then(r => r.text());
|
||||
const secondConcurrentRequest = ajax.fetch('/test').then(r => r.text());
|
||||
|
||||
clock.tick(1000);
|
||||
|
||||
const secondResponses = await Promise.all([secondRequest, secondConcurrentRequest]);
|
||||
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
|
||||
expect(firstResponses).to.eql(['mock response 1', 'mock response 1', 'mock response 1']);
|
||||
|
||||
expect(cachedFirstResponse).to.equal('mock response 1');
|
||||
|
||||
expect(secondResponses).to.eql(['mock response 2', 'mock response 2']);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('preserves status and headers when returning cached response', async () => {
|
||||
newCacheId();
|
||||
fetchStub.returns(
|
||||
Promise.resolve(
|
||||
new Response('mock response', { status: 206, headers: { 'x-foo': 'x-bar' } }),
|
||||
),
|
||||
);
|
||||
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
maxAge: 100,
|
||||
});
|
||||
|
||||
const response1 = await ajax.fetch('/test');
|
||||
const response2 = await ajax.fetch('/test');
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
expect(response1.status).to.equal(206);
|
||||
expect(response1.headers.get('x-foo')).to.equal('x-bar');
|
||||
expect(response2.status).to.equal(206);
|
||||
expect(response2.headers.get('x-foo')).to.equal('x-bar');
|
||||
});
|
||||
|
||||
it('does save to the cache when `contentTypes` is specified and a supported content type is returned', async () => {
|
||||
// Given
|
||||
newCacheId();
|
||||
mockResponse.headers.set('content-type', 'application/xml');
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
contentTypes: ['application/json', 'application/xml'],
|
||||
});
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test');
|
||||
await ajax.fetch('/test');
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('does save to the cache when `maxResponseSize` is specified and the response size is within the threshold', async () => {
|
||||
// Given
|
||||
newCacheId();
|
||||
mockResponse.headers.set('content-length', '20000');
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
maxResponseSize: 50000,
|
||||
});
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test');
|
||||
await ajax.fetch('/test');
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('does save to the cache when `maxResponseSize` is specified and the response size is unknown', async () => {
|
||||
// Given
|
||||
newCacheId();
|
||||
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
maxResponseSize: 50000,
|
||||
});
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test');
|
||||
await ajax.fetch('/test');
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bypassing the cache', () => {
|
||||
it('caches response but does not return it when expiration time is 0', async () => {
|
||||
newCacheId();
|
||||
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
maxAge: 0,
|
||||
});
|
||||
|
||||
const clock = sinon.useFakeTimers();
|
||||
|
||||
await ajax.fetch('/test');
|
||||
|
||||
expect(ajaxRequestSpy.calledOnce).to.be.true;
|
||||
|
||||
expect(ajaxRequestSpy.calledWith('/test')).to.be.true;
|
||||
|
||||
clock.tick(1);
|
||||
|
||||
await ajax.fetch('/test');
|
||||
|
||||
clock.restore();
|
||||
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('does not use cache when cacheOption `useCache: false` is passed to fetch method', async () => {
|
||||
// Given
|
||||
newCacheId();
|
||||
addCacheInterceptors(ajax, { useCache: true });
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test');
|
||||
await ajax.fetch('/test');
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test', { cacheOptions: { useCache: false } });
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('does not save to the cache when `contentTypes` is specified and an unsupported content type is returned', async () => {
|
||||
// Given
|
||||
newCacheId();
|
||||
mockResponse.headers.set('content-type', 'text/html');
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
contentTypes: ['application/json', 'application/xml'],
|
||||
});
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test');
|
||||
await ajax.fetch('/test', { cacheOptions: { contentTypes: ['text/html'] } });
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('does not read from the cache when `contentTypes` is specified and an unsupported content type is returned', async () => {
|
||||
// Given
|
||||
newCacheId();
|
||||
mockResponse.headers.set('content-type', 'application/json');
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
contentTypes: ['application/json', 'application/xml'],
|
||||
});
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test');
|
||||
await ajax.fetch('/test');
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test', { cacheOptions: { contentTypes: [] } });
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('does not save to the cache when `maxResponseSize` is specified and a larger content-length is specified in the response', async () => {
|
||||
// Given
|
||||
newCacheId();
|
||||
mockResponse.headers.set('content-length', '80000');
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
maxResponseSize: 50000,
|
||||
});
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test');
|
||||
await ajax.fetch('/test', { cacheOptions: { maxResponseSize: 100000 } });
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('does not read from the cache when `maxResponseSize` is specified and a larger content-length is specified in the response', async () => {
|
||||
// Given
|
||||
newCacheId();
|
||||
mockResponse.headers.set('content-length', '80000');
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
maxResponseSize: 100000,
|
||||
});
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test');
|
||||
await ajax.fetch('/test');
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test', { cacheOptions: { maxResponseSize: 50000 } });
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Cache invalidation', () => {
|
||||
|
|
@ -449,101 +699,6 @@ describe('cache interceptors', () => {
|
|||
expect(fetchStub.callCount).to.equal(3);
|
||||
});
|
||||
|
||||
it('caches response but does not return it when expiration time is 0', async () => {
|
||||
newCacheId();
|
||||
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
maxAge: 0,
|
||||
});
|
||||
|
||||
const clock = sinon.useFakeTimers();
|
||||
|
||||
await ajax.fetch('/test');
|
||||
|
||||
expect(ajaxRequestSpy.calledOnce).to.be.true;
|
||||
|
||||
expect(ajaxRequestSpy.calledWith('/test')).to.be.true;
|
||||
|
||||
clock.tick(1);
|
||||
|
||||
await ajax.fetch('/test');
|
||||
|
||||
clock.restore();
|
||||
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('does not use cache when cacheOption `useCache: false` is passed to fetch method', async () => {
|
||||
// Given
|
||||
addCacheInterceptors(ajax, { useCache: true });
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test');
|
||||
await ajax.fetch('/test');
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
|
||||
// When
|
||||
await ajax.fetch('/test', { cacheOptions: { useCache: false } });
|
||||
|
||||
// Then
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('caches concurrent requests', async () => {
|
||||
newCacheId();
|
||||
|
||||
const clock = sinon.useFakeTimers();
|
||||
|
||||
fetchStub.onFirstCall().returns(returnResponseOnTick(900, 1));
|
||||
fetchStub.onSecondCall().returns(returnResponseOnTick(1900, 2));
|
||||
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
maxAge: 750,
|
||||
});
|
||||
|
||||
const firstRequest = ajax.fetch('/test').then(r => r.text());
|
||||
const concurrentFirstRequest1 = ajax.fetch('/test').then(r => r.text());
|
||||
const concurrentFirstRequest2 = ajax.fetch('/test').then(r => r.text());
|
||||
|
||||
clock.tick(1000);
|
||||
|
||||
// firstRequest is cached at tick 1000 in the next line!
|
||||
const firstResponses = await Promise.all([
|
||||
firstRequest,
|
||||
concurrentFirstRequest1,
|
||||
concurrentFirstRequest2,
|
||||
]);
|
||||
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
|
||||
const cachedFirstRequest = ajax.fetch('/test').then(r => r.text());
|
||||
|
||||
clock.tick(500);
|
||||
|
||||
const cachedFirstResponse = await cachedFirstRequest;
|
||||
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
|
||||
const secondRequest = ajax.fetch('/test').then(r => r.text());
|
||||
const secondConcurrentRequest = ajax.fetch('/test').then(r => r.text());
|
||||
|
||||
clock.tick(1000);
|
||||
|
||||
const secondResponses = await Promise.all([secondRequest, secondConcurrentRequest]);
|
||||
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
|
||||
expect(firstResponses).to.eql(['mock response 1', 'mock response 1', 'mock response 1']);
|
||||
|
||||
expect(cachedFirstResponse).to.equal('mock response 1');
|
||||
|
||||
expect(secondResponses).to.eql(['mock response 2', 'mock response 2']);
|
||||
});
|
||||
|
||||
it('discards responses that are requested in a different cache session', async () => {
|
||||
newCacheId();
|
||||
|
||||
|
|
@ -569,27 +724,5 @@ describe('cache interceptors', () => {
|
|||
expect(ajaxCache._cachedRequests).to.deep.equal({});
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('preserves status and headers when returning cached response', async () => {
|
||||
newCacheId();
|
||||
fetchStub.returns(
|
||||
Promise.resolve(
|
||||
new Response('mock response', { status: 206, headers: { 'x-foo': 'x-bar' } }),
|
||||
),
|
||||
);
|
||||
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
maxAge: 100,
|
||||
});
|
||||
|
||||
const response1 = await ajax.fetch('/test');
|
||||
const response2 = await ajax.fetch('/test');
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
expect(response1.status).to.equal(206);
|
||||
expect(response1.headers.get('x-foo')).to.equal('x-bar');
|
||||
expect(response2.status).to.equal(206);
|
||||
expect(response2.headers.get('x-foo')).to.equal('x-bar');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
3
packages/ajax/types/types.d.ts
vendored
3
packages/ajax/types/types.d.ts
vendored
|
|
@ -12,6 +12,7 @@ export interface LionRequestInit extends Omit<RequestInit, 'body'> {
|
|||
|
||||
export interface AjaxConfig {
|
||||
addAcceptLanguage: boolean;
|
||||
addCaching: boolean;
|
||||
xsrfCookieName: string | null;
|
||||
xsrfHeaderName: string | null;
|
||||
cacheOptions: CacheOptionsWithIdentifier;
|
||||
|
|
@ -39,6 +40,8 @@ export interface CacheOptions {
|
|||
invalidateUrls?: string[];
|
||||
invalidateUrlsRegex?: RegExp;
|
||||
requestIdFunction?: RequestIdFunction;
|
||||
contentTypes?: string[];
|
||||
maxResponseSize?: number;
|
||||
}
|
||||
|
||||
export interface CacheOptionsWithIdentifier extends CacheOptions {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# Change Log
|
||||
|
||||
## 0.20.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 41297869: Add mixed-state feature to checkbox indeterminate. See https://www.w3.org/TR/wai-aria-practices-1.1/examples/checkbox/checkbox-2/checkbox-2.html for the WAI ARIA pattern.
|
||||
- Updated dependencies [43dd1320]
|
||||
- @lion/form-core@0.17.1
|
||||
|
||||
## 0.20.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@lion/checkbox-group",
|
||||
"version": "0.20.0",
|
||||
"version": "0.20.1",
|
||||
"description": "A container for multiple checkboxes",
|
||||
"license": "MIT",
|
||||
"author": "ing-bank",
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@lion/core": "^0.22.0",
|
||||
"@lion/form-core": "^0.17.0",
|
||||
"@lion/form-core": "^0.17.1",
|
||||
"@lion/input": "^0.17.0"
|
||||
},
|
||||
"keywords": [
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|||
type: Boolean,
|
||||
reflect: true,
|
||||
},
|
||||
mixedState: {
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
attribute: 'mixed-state',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +63,18 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|||
return /** @type LionCheckbox[] */ (checkboxes);
|
||||
}
|
||||
|
||||
_storeIndeterminateState() {
|
||||
this._indeterminateSubStates = this._subCheckboxes.map(checkbox => checkbox.checked);
|
||||
}
|
||||
|
||||
_setOldState() {
|
||||
if (this.indeterminate) {
|
||||
this._oldState = 'indeterminate';
|
||||
} else {
|
||||
this._oldState = this.checked ? 'checked' : 'unchecked';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
|
|
@ -89,6 +106,27 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|||
});
|
||||
}
|
||||
|
||||
_setBasedOnMixedState() {
|
||||
switch (this._oldState) {
|
||||
case 'checked':
|
||||
// --> unchecked
|
||||
this.checked = false;
|
||||
this.indeterminate = false;
|
||||
break;
|
||||
case 'unchecked':
|
||||
// --> indeterminate
|
||||
this.checked = false;
|
||||
this.indeterminate = true;
|
||||
break;
|
||||
case 'indeterminate':
|
||||
// --> checked
|
||||
this.checked = true;
|
||||
this.indeterminate = false;
|
||||
break;
|
||||
// no default
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} ev
|
||||
* @private
|
||||
|
|
@ -97,15 +135,44 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const _ev = /** @type {CustomEvent} */ (ev);
|
||||
|
||||
// If the model value change event is coming from out own _inputNode
|
||||
// and we're not already setting our own (mixed) state programmatically
|
||||
if (_ev.detail.formPath[0] === this && !this.__settingOwnChecked) {
|
||||
const allEqual = (/** @type {any[]} */ arr) => arr.every(val => val === arr[0]);
|
||||
// If our child checkboxes states are all the same, we shouldn't be able to reach indeterminate (mixed) state
|
||||
if (this.mixedState && !allEqual(this._indeterminateSubStates)) {
|
||||
this._setBasedOnMixedState();
|
||||
}
|
||||
|
||||
this.__settingOwnSubs = true;
|
||||
if (this.indeterminate && this.mixedState) {
|
||||
this._subCheckboxes.forEach((checkbox, i) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
checkbox.checked = this._indeterminateSubStates[i];
|
||||
});
|
||||
} else {
|
||||
this._subCheckboxes.forEach(checkbox => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
checkbox.checked = this._inputNode.checked;
|
||||
});
|
||||
}
|
||||
this.updateComplete.then(() => {
|
||||
this.__settingOwnSubs = false;
|
||||
});
|
||||
} else {
|
||||
this._setOwnCheckedState();
|
||||
this.updateComplete.then(() => {
|
||||
if (!this.__settingOwnSubs && !this.__settingOwnChecked && this.mixedState) {
|
||||
this._storeIndeterminateState();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.mixedState) {
|
||||
this._setOldState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -132,6 +199,9 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|||
this.indeterminate = false;
|
||||
this._onRequestToAddFormElement = this._onRequestToAddFormElement.bind(this);
|
||||
this.__onModelValueChanged = this.__onModelValueChanged.bind(this);
|
||||
/** @type {boolean[]} */
|
||||
this._indeterminateSubStates = [];
|
||||
this.mixedState = false;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
|
|
@ -146,6 +216,15 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|||
this.removeEventListener('form-element-register', this._onRequestToAddFormElement);
|
||||
}
|
||||
|
||||
/** @param {import('lit-element').PropertyValues } changedProperties */
|
||||
firstUpdated(changedProperties) {
|
||||
super.firstUpdated(changedProperties);
|
||||
this._setOldState();
|
||||
if (this.indeterminate) {
|
||||
this._storeIndeterminateState();
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {import('lit-element').PropertyValues } changedProperties */
|
||||
updated(changedProperties) {
|
||||
super.updated(changedProperties);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
|||
import '@lion/checkbox-group/define';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionCheckbox').LionCheckbox} LionCheckbox
|
||||
* @typedef {import('../src/LionCheckboxIndeterminate').LionCheckboxIndeterminate} LionCheckboxIndeterminate
|
||||
* @typedef {import('../src/LionCheckboxGroup').LionCheckboxGroup} LionCheckboxGroup
|
||||
*/
|
||||
|
|
@ -428,4 +429,153 @@ describe('<lion-checkbox-indeterminate>', () => {
|
|||
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false;
|
||||
expect(elIndeterminate?.checked).to.be.true;
|
||||
});
|
||||
|
||||
// https://www.w3.org/TR/wai-aria-practices-1.1/examples/checkbox/checkbox-2/checkbox-2.html
|
||||
describe('mixed-state', () => {
|
||||
it('can have a mixed-state (using mixed-state attribute), none -> indeterminate -> all, cycling through', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate mixed-state label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`);
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('lion-checkbox-indeterminate')
|
||||
);
|
||||
|
||||
expect(elIndeterminate.mixedState).to.be.true;
|
||||
expect(elIndeterminate.checked).to.be.false;
|
||||
expect(elIndeterminate.indeterminate).to.be.true;
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
elIndeterminate._inputNode.click();
|
||||
await elIndeterminate.updateComplete;
|
||||
expect(elIndeterminate.checked).to.be.true;
|
||||
expect(elIndeterminate.indeterminate).to.be.false;
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
elIndeterminate._inputNode.click();
|
||||
await elIndeterminate.updateComplete;
|
||||
expect(elIndeterminate.checked).to.be.false;
|
||||
expect(elIndeterminate.indeterminate).to.be.false;
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
elIndeterminate._inputNode.click();
|
||||
await elIndeterminate.updateComplete;
|
||||
expect(elIndeterminate.checked).to.be.false;
|
||||
expect(elIndeterminate.indeterminate).to.be.true;
|
||||
});
|
||||
|
||||
it('should reset to old child checkbox states when reaching indeterminate state', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate mixed-state label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`);
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('lion-checkbox-indeterminate')
|
||||
);
|
||||
const checkboxEls = /** @type {LionCheckbox[]} */ (
|
||||
Array.from(el.querySelectorAll('lion-checkbox'))
|
||||
);
|
||||
|
||||
expect(checkboxEls.map(checkboxEl => checkboxEl.checked)).to.eql([true, false, false]);
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
elIndeterminate._inputNode.click();
|
||||
await elIndeterminate.updateComplete;
|
||||
expect(checkboxEls.map(checkboxEl => checkboxEl.checked)).to.eql([true, true, true]);
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
elIndeterminate._inputNode.click();
|
||||
await elIndeterminate.updateComplete;
|
||||
expect(checkboxEls.map(checkboxEl => checkboxEl.checked)).to.eql([false, false, false]);
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
elIndeterminate._inputNode.click();
|
||||
await elIndeterminate.updateComplete;
|
||||
expect(checkboxEls.map(checkboxEl => checkboxEl.checked)).to.eql([true, false, false]);
|
||||
});
|
||||
|
||||
it('should no longer reach indeterminate state if the child boxes are all checked or all unchecked during indeterminate state', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate mixed-state label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`);
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('lion-checkbox-indeterminate')
|
||||
);
|
||||
const checkboxEls = /** @type {LionCheckbox[]} */ (
|
||||
Array.from(el.querySelectorAll('lion-checkbox'))
|
||||
);
|
||||
|
||||
// Check when all child boxes in indeterminate state are unchecked
|
||||
// we don't have a tri-state, but a duo-state.
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
checkboxEls[0]._inputNode.click();
|
||||
await elIndeterminate.updateComplete;
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
elIndeterminate._inputNode.click();
|
||||
await elIndeterminate.updateComplete;
|
||||
expect(elIndeterminate.checked).to.be.true;
|
||||
expect(elIndeterminate.indeterminate).to.be.false;
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
elIndeterminate._inputNode.click();
|
||||
await elIndeterminate.updateComplete;
|
||||
expect(elIndeterminate.checked).to.be.false;
|
||||
expect(elIndeterminate.indeterminate).to.be.false;
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
elIndeterminate._inputNode.click();
|
||||
await elIndeterminate.updateComplete;
|
||||
expect(elIndeterminate.checked).to.be.true;
|
||||
expect(elIndeterminate.indeterminate).to.be.false;
|
||||
|
||||
// Check when all child boxes in indeterminate state are getting checked
|
||||
// we also don't have a tri-state, but a duo-state.
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
elIndeterminate._inputNode.click(); // unchecked
|
||||
await elIndeterminate.updateComplete;
|
||||
|
||||
for (const checkEl of checkboxEls) {
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
checkEl._inputNode.click();
|
||||
// Give each checking of the sub checkbox a chance to finish updating
|
||||
// This means indeterminate state will be true for a bit and the state gets stored
|
||||
await checkEl.updateComplete;
|
||||
await elIndeterminate.updateComplete;
|
||||
}
|
||||
|
||||
expect(elIndeterminate.checked).to.be.true;
|
||||
expect(elIndeterminate.indeterminate).to.be.false;
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
elIndeterminate._inputNode.click();
|
||||
await elIndeterminate.updateComplete;
|
||||
expect(elIndeterminate.checked).to.be.false;
|
||||
expect(elIndeterminate.indeterminate).to.be.false;
|
||||
|
||||
// @ts-ignore for testing purposes, we access this protected getter
|
||||
elIndeterminate._inputNode.click();
|
||||
await elIndeterminate.updateComplete;
|
||||
expect(elIndeterminate.checked).to.be.true;
|
||||
expect(elIndeterminate.indeterminate).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -331,7 +331,11 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
|||
* @protected
|
||||
*/
|
||||
this._listboxReceivesNoFocus = true;
|
||||
|
||||
/**
|
||||
* @configure ListboxMixin
|
||||
* @protected
|
||||
*/
|
||||
this._noTypeAhead = true;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# Change Log
|
||||
|
||||
## 0.17.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 43dd1320: fix: reset the form validators after a form `reset` click or field emptied
|
||||
|
||||
## 0.17.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@lion/form-core",
|
||||
"version": "0.17.0",
|
||||
"version": "0.17.1",
|
||||
"description": "Form-core contains all essential building blocks for creating form fields and fieldsets",
|
||||
"license": "MIT",
|
||||
"author": "ing-bank",
|
||||
|
|
|
|||
|
|
@ -156,10 +156,6 @@ const FormRegistrarMixinImplementation = superclass =>
|
|||
// 2. Add children as object key
|
||||
if (this._isFormOrFieldset) {
|
||||
const { name } = child;
|
||||
if (!name) {
|
||||
console.info('Error Node:', child); // eslint-disable-line no-console
|
||||
throw new TypeError('You need to define a name');
|
||||
}
|
||||
if (name === this.name) {
|
||||
console.info('Error Node:', child); // eslint-disable-line no-console
|
||||
throw new TypeError(`You can not have the same name "${name}" as your parent`);
|
||||
|
|
|
|||
|
|
@ -133,27 +133,6 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
]);
|
||||
});
|
||||
|
||||
it('throws if an element without a name tries to register', async () => {
|
||||
const orig = console.info;
|
||||
console.info = () => {};
|
||||
|
||||
let error;
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
try {
|
||||
// we test the api directly as errors thrown from a web component are in a
|
||||
// different context and we can not catch them here => register fake elements
|
||||
el.addFormElement(
|
||||
/** @type {HTMLElement & import('../../types/FormControlMixinTypes').FormControlHost} */ ({}),
|
||||
);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
expect(error).to.be.instanceOf(TypeError);
|
||||
expect(/** @type {TypeError} */ (error).message).to.equal('You need to define a name');
|
||||
|
||||
console.info = orig; // restore original console
|
||||
});
|
||||
|
||||
it('throws if name is the same as its parent', async () => {
|
||||
const orig = console.info;
|
||||
console.info = () => {};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
# @lion/input-tel-dropdown
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8ecfc31c: sync disable state to dropdown for a11y
|
||||
- @lion/input-tel@0.1.2
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@lion/input-tel-dropdown",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"description": "Input field for entering phone numbers with the help of a dropdown region list",
|
||||
"license": "MIT",
|
||||
"author": "ing-bank",
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@lion/core": "0.22.0",
|
||||
"@lion/input-tel": "0.1.1",
|
||||
"@lion/input-tel": "0.1.2",
|
||||
"@lion/localize": "0.24.0"
|
||||
},
|
||||
"keywords": [
|
||||
|
|
|
|||
|
|
@ -20,13 +20,12 @@ import { localize } from '@lion/localize';
|
|||
* @typedef {import('lit/directives/ref.js').Ref} Ref
|
||||
* @typedef {import('@lion/core').RenderOptions} RenderOptions
|
||||
* @typedef {import('@lion/form-core/types/FormatMixinTypes').FormatHost} FormatHost
|
||||
* @typedef {import('@lion/input-tel/types').FormatStrategy} FormatStrategy
|
||||
* @typedef {import('@lion/input-tel/types').RegionCode} RegionCode
|
||||
* @typedef {import('../types').TemplateDataForDropdownInputTel} TemplateDataForDropdownInputTel
|
||||
* @typedef {import('../types').OnDropdownChangeEvent} OnDropdownChangeEvent
|
||||
* @typedef {import('../types').DropdownRef} DropdownRef
|
||||
* @typedef {import('../types').RegionMeta} RegionMeta
|
||||
* @typedef {* & import('@lion/input-tel/src/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
|
||||
* @typedef {* & import('awesome-phonenumber').default} AwesomePhoneNumber
|
||||
* @typedef {import('@lion/select-rich').LionSelectRich} LionSelectRich
|
||||
* @typedef {import('@lion/overlays').OverlayController} OverlayController
|
||||
* @typedef {TemplateDataForDropdownInputTel & {data: {regionMetaList:RegionMeta[]}}} TemplateDataForIntlInputTel
|
||||
|
|
|
|||
2
packages/input-tel-dropdown/types/index.d.ts
vendored
2
packages/input-tel-dropdown/types/index.d.ts
vendored
|
|
@ -1,4 +1,4 @@
|
|||
import { RegionCode } from '@lion/input-tel/types/types';
|
||||
import { RegionCode } from '@lion/input-tel/types';
|
||||
import { LionSelectRich } from '@lion/select-rich';
|
||||
import { LionCombobox } from '@lion/combobox';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
# @lion/input-tel
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [43dd1320]
|
||||
- @lion/form-core@0.17.1
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@lion/input-tel",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"description": "Input field for entering phone numbers, including validation, formatting and mobile keyboard support.",
|
||||
"license": "MIT",
|
||||
"author": "ing-bank",
|
||||
|
|
@ -38,9 +38,10 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@lion/core": "0.22.0",
|
||||
"@lion/form-core": "0.17.0",
|
||||
"@lion/form-core": "0.17.1",
|
||||
"@lion/input": "0.17.0",
|
||||
"@lion/localize": "0.24.0"
|
||||
"@lion/localize": "0.24.0",
|
||||
"awesome-phonenumber": "^3.0.1"
|
||||
},
|
||||
"keywords": [
|
||||
"input",
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ import { PhoneNumber } from './validators.js';
|
|||
import { localizeNamespaceLoader } from './localizeNamespaceLoader.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../types').FormatStrategy} FormatStrategy
|
||||
* @typedef {import('../types').RegionCode} RegionCode
|
||||
* @typedef {import('../types').PhoneNumberType} PhoneNumberType
|
||||
* @typedef {import('awesome-phonenumber').PhoneNumberFormat} PhoneNumberFormat
|
||||
* @typedef {import('awesome-phonenumber').PhoneNumberTypes} PhoneNumberTypes
|
||||
* @typedef {import('@lion/form-core/types/FormatMixinTypes').FormatOptions} FormatOptions
|
||||
* @typedef {* & import('@lion/input-tel/src/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
|
||||
* @typedef {FormatOptions & {regionCode: RegionCode; formatStrategy: FormatStrategy}} FormatOptionsTel
|
||||
* @typedef {* & import('awesome-phonenumber').default} AwesomePhoneNumber
|
||||
* @typedef {FormatOptions & {regionCode: RegionCode; formatStrategy: PhoneNumberFormat}} FormatOptionsTel
|
||||
*/
|
||||
|
||||
export class LionInputTel extends LocalizeMixin(LionInput) {
|
||||
|
|
@ -65,7 +65,7 @@ export class LionInputTel extends LocalizeMixin(LionInput) {
|
|||
* -'unknown'
|
||||
* See https://www.npmjs.com/package/awesome-phonenumber
|
||||
* @readonly
|
||||
* @property {PhoneNumberType|undefined} activePhoneNumberType
|
||||
* @property {PhoneNumberTypes|undefined} activePhoneNumberTypes
|
||||
*/
|
||||
get activePhoneNumberType() {
|
||||
let pn;
|
||||
|
|
@ -135,7 +135,7 @@ export class LionInputTel extends LocalizeMixin(LionInput) {
|
|||
* Determines what the formatter output should look like.
|
||||
* Formatting strategies as provided by google-libphonenumber
|
||||
* See: https://www.npmjs.com/package/google-libphonenumber
|
||||
* @type {FormatStrategy}
|
||||
* @type {PhoneNumberFormat}
|
||||
*/
|
||||
this.formatStrategy = 'international';
|
||||
|
||||
|
|
@ -286,7 +286,7 @@ export class LionInputTel extends LocalizeMixin(LionInput) {
|
|||
*/
|
||||
_onPhoneNumberUtilReady() {
|
||||
// This should trigger a rerender in shadow dom
|
||||
this._phoneUtil = /** @type {PhoneNumber} */ (PhoneUtilManager.PhoneUtil);
|
||||
this._phoneUtil = /** @type {AwesomePhoneNumber} */ (PhoneUtilManager.PhoneUtil);
|
||||
// This should trigger a rerender in light dom
|
||||
this._scheduleLightDomRender();
|
||||
// Format when libPhoneNumber is loaded
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ let resolveLoaded;
|
|||
*/
|
||||
export class PhoneUtilManager {
|
||||
static async loadLibPhoneNumber() {
|
||||
const PhoneUtil = (await import('./lib/awesome-phonenumber-esm.js')).default;
|
||||
const PhoneUtil = (await import('awesome-phonenumber')).default;
|
||||
this.PhoneUtil = PhoneUtil;
|
||||
resolveLoaded(undefined);
|
||||
return PhoneUtil;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import { PhoneUtilManager } from './PhoneUtilManager.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../types').FormatStrategy} FormatStrategy
|
||||
* @typedef {import('awesome-phonenumber').PhoneNumberFormat} PhoneNumberFormat
|
||||
* @typedef {import('../types').RegionCode} RegionCode
|
||||
* @typedef {* & import('@lion/input-tel/src/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
|
||||
* @typedef {* & import('awesome-phonenumber').default} AwesomePhoneNumber
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} modelValue
|
||||
* @param {object} options
|
||||
* @param {RegionCode} options.regionCode
|
||||
* @param {FormatStrategy} [options.formatStrategy='international']
|
||||
* @param {PhoneNumberFormat} [options.formatStrategy='international']
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatPhoneNumber(modelValue, { regionCode, formatStrategy = 'international' }) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,12 +2,12 @@ import { PhoneUtilManager } from './PhoneUtilManager.js';
|
|||
|
||||
/**
|
||||
* @typedef {import('../types').RegionCode} RegionCode
|
||||
* @typedef {* & import('@lion/input-tel/src/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
|
||||
* @typedef {* & import('awesome-phonenumber').default} AwesomePhoneNumber
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} viewValue
|
||||
* @param {{regionCode:RegionCode;}} options
|
||||
* @param {{regionCode:RegionCode}} options
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parsePhoneNumber(viewValue, { regionCode }) {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import { PhoneUtilManager } from './PhoneUtilManager.js';
|
|||
import { formatPhoneNumber } from './formatters.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../types').FormatStrategy} FormatStrategy
|
||||
* @typedef {import('../types').RegionCode} RegionCode
|
||||
* @typedef {* & import('@lion/input-tel/src/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
|
||||
* @typedef {import('awesome-phonenumber').PhoneNumberFormat} PhoneNumberFormat
|
||||
* @typedef {* & import('awesome-phonenumber').default} AwesomePhoneNumber
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -13,7 +13,7 @@ import { formatPhoneNumber } from './formatters.js';
|
|||
* @param {RegionCode} options.regionCode
|
||||
* @param {string} options.prevViewValue
|
||||
* @param {number} options.currentCaretIndex
|
||||
* @param {FormatStrategy} options.formatStrategy
|
||||
* @param {PhoneNumberFormat} options.formatStrategy
|
||||
* @returns {{viewValue:string; caretIndex:number;}|undefined}
|
||||
*/
|
||||
export function liveFormatPhoneNumber(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { PhoneUtilManager } from './PhoneUtilManager.js';
|
|||
|
||||
/**
|
||||
* @typedef {import('../types').RegionCode} RegionCode
|
||||
* @typedef {* & import('@lion/input-tel/src/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
|
||||
* @typedef {* & import('awesome-phonenumber').default} AwesomePhoneNumber
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from '../test-helpers/mockPhoneUtilManager.js';
|
||||
|
||||
/**
|
||||
* @typedef {* & import('@lion/input-tel/src/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
|
||||
* @typedef {* & import('awesome-phonenumber').default} AwesomePhoneNumber
|
||||
*/
|
||||
|
||||
// For enum output, see: https://www.npmjs.com/package/awesome-phonenumber
|
||||
|
|
|
|||
42
packages/input-tel/types/index.d.ts
vendored
42
packages/input-tel/types/index.d.ts
vendored
|
|
@ -1,46 +1,6 @@
|
|||
/*
|
||||
* Phone number types as provided by google-libphonenumber
|
||||
* See:
|
||||
* - https://www.npmjs.com/package/google-libphonenumber
|
||||
* - https://www.npmjs.com/package/awesome-phonenumber
|
||||
*/
|
||||
export type PhoneNumberType =
|
||||
| 'fixed-line'
|
||||
| 'fixed-line-or-mobile'
|
||||
| 'mobile'
|
||||
| 'pager'
|
||||
| 'personal-number'
|
||||
| 'premium-rate'
|
||||
| 'shared-cost'
|
||||
| 'toll-free'
|
||||
| 'uan'
|
||||
| 'voip'
|
||||
| 'unknown';
|
||||
|
||||
/*
|
||||
* Phone number possibilities as provided by google-libphonenumber
|
||||
* See:
|
||||
* - https://www.npmjs.com/package/google-libphonenumber
|
||||
* - https://www.npmjs.com/package/awesome-phonenumber
|
||||
*/
|
||||
export type PhoneNumberPossibility =
|
||||
| 'is-possible'
|
||||
| 'invalid-country-code'
|
||||
| 'too-long'
|
||||
| 'too-short'
|
||||
| 'unknown';
|
||||
|
||||
/*
|
||||
* Phone number formats / formatting strategies as provided by google-libphonenumber
|
||||
* See:
|
||||
* - https://www.npmjs.com/package/google-libphonenumber
|
||||
* - https://www.npmjs.com/package/awesome-phonenumber
|
||||
*/
|
||||
export type FormatStrategy = 'e164' | 'international' | 'national' | 'rfc3966' | 'significant';
|
||||
|
||||
/**
|
||||
* Supported countries/regions as provided via
|
||||
* `libphonenumber.PhoneNumberUtil.getInstance().getSupportedRegions()`
|
||||
* `libphonenumber.getSupportedRegionCodes()`
|
||||
*/
|
||||
export type RegionCode =
|
||||
| 'AC'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# @lion/listbox
|
||||
|
||||
## 0.13.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a28686ee: Add TypeAhead, so with typing characters you will set an option with matching value active/checked
|
||||
- Updated dependencies [43dd1320]
|
||||
- @lion/form-core@0.17.1
|
||||
|
||||
## 0.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@lion/listbox",
|
||||
"version": "0.13.0",
|
||||
"version": "0.13.1",
|
||||
"description": "A listbox widget presents a list of options and allows a user to select one or more of them",
|
||||
"license": "MIT",
|
||||
"author": "ing-bank",
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@lion/core": "^0.22.0",
|
||||
"@lion/form-core": "^0.17.0"
|
||||
"@lion/form-core": "^0.17.1"
|
||||
},
|
||||
"keywords": [
|
||||
"form",
|
||||
|
|
|
|||
|
|
@ -81,6 +81,9 @@ const ListboxMixinImplementation = superclass =>
|
|||
reflect: true,
|
||||
attribute: 'has-no-default-selected',
|
||||
},
|
||||
_noTypeAhead: {
|
||||
type: Boolean,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -270,7 +273,16 @@ const ListboxMixinImplementation = superclass =>
|
|||
* See: https://www.w3.org/TR/wai-aria-practices/#kbd_selection_follows_focus
|
||||
*/
|
||||
this.selectionFollowsFocus = false;
|
||||
|
||||
/**
|
||||
* When false, a user can type on which the focus will jump to the matching option
|
||||
*/
|
||||
this._noTypeAhead = false;
|
||||
/**
|
||||
* The pending char sequence that will set active list item
|
||||
* @type {number}
|
||||
* @protected
|
||||
*/
|
||||
this._typeAheadTimeout = 1000;
|
||||
/**
|
||||
* @type {number | null}
|
||||
* @protected
|
||||
|
|
@ -327,6 +339,11 @@ const ListboxMixinImplementation = superclass =>
|
|||
* @private
|
||||
*/
|
||||
this.__preventScrollingWithArrowKeys = this.__preventScrollingWithArrowKeys.bind(this);
|
||||
/**
|
||||
* @type {string[]}
|
||||
* @private
|
||||
*/
|
||||
this.__typedChars = [];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
|
|
@ -466,6 +483,39 @@ const ListboxMixinImplementation = superclass =>
|
|||
this.resetInteractionState();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {KeyboardEvent} ev
|
||||
* @param {{setAsChecked:boolean}} options
|
||||
* @protected
|
||||
*/
|
||||
_handleTypeAhead(ev, { setAsChecked }) {
|
||||
const { key, code } = ev;
|
||||
|
||||
if (code.startsWith('Key') || code.startsWith('Digit') || code.startsWith('Numpad')) {
|
||||
ev.preventDefault();
|
||||
this.__typedChars.push(key);
|
||||
const chars = this.__typedChars.join('');
|
||||
const matchedItemIndex =
|
||||
// TODO: consider making this condition overridable for Subclassers by extracting it into protected method
|
||||
this.formElements.findIndex(el => el.modelValue.value.toLowerCase().startsWith(chars));
|
||||
if (matchedItemIndex >= 0) {
|
||||
if (setAsChecked) {
|
||||
this.setCheckedIndex(matchedItemIndex);
|
||||
}
|
||||
this.activeIndex = matchedItemIndex;
|
||||
}
|
||||
if (this.__pendingTypeAheadTimeout) {
|
||||
// Prevent that pending timeouts 'intersect' with new 'typeahead sessions'
|
||||
// @ts-ignore
|
||||
window.clearTimeout(this.__pendingTypeAheadTimeout);
|
||||
}
|
||||
this.__pendingTypeAheadTimeout = setTimeout(() => {
|
||||
// schedule a timeout to reset __typedChars
|
||||
this.__typedChars = [];
|
||||
}, this._typeAheadTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override ChoiceGroupMixin: in the select disabled options are still going to a possible
|
||||
* value, for example when prefilling or programmatically setting it.
|
||||
|
|
@ -621,7 +671,12 @@ const ListboxMixinImplementation = superclass =>
|
|||
ev.preventDefault();
|
||||
this.activeIndex = this._getPreviousEnabledOption(this.formElements.length - 1, 0);
|
||||
break;
|
||||
/* no default */
|
||||
default:
|
||||
if (!this._noTypeAhead) {
|
||||
this._handleTypeAhead(ev, {
|
||||
setAsChecked: this.selectionFollowsFocus && !this.multipleChoice,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const keys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'];
|
||||
|
|
|
|||
|
|
@ -26,10 +26,11 @@ const fixture = /** @type {(arg: TemplateResult) => Promise<LionListbox>} */ (_f
|
|||
/**
|
||||
* @param {HTMLElement} el
|
||||
* @param {string} key
|
||||
* @param {string} code
|
||||
*/
|
||||
function mimicKeyPress(el, key) {
|
||||
el.dispatchEvent(new KeyboardEvent('keydown', { key }));
|
||||
el.dispatchEvent(new KeyboardEvent('keyup', { key }));
|
||||
function mimicKeyPress(el, key, code = '') {
|
||||
el.dispatchEvent(new KeyboardEvent('keydown', { key, code }));
|
||||
el.dispatchEvent(new KeyboardEvent('keyup', { key, code }));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -381,8 +382,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
// TODO: enable when native button is not a child anymore
|
||||
it.skip('[axe]: is accessible when opened', async () => {
|
||||
it('[axe]: is accessible when opened', async () => {
|
||||
const el = await fixture(html`
|
||||
<${tag} label="age" opened>
|
||||
<${optionTag} .choiceValue=${10}>Item 1</${optionTag}>
|
||||
|
|
@ -396,8 +396,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
});
|
||||
|
||||
// NB: regular listbox is always 'opened', but needed for combobox and select-rich
|
||||
// TODO: enable when native button is not a child anymore
|
||||
it.skip('[axe]: is accessible when closed', async () => {
|
||||
it('[axe]: is accessible when closed', async () => {
|
||||
const el = await fixture(html`
|
||||
<${tag} label="age">
|
||||
<${optionTag} .choiceValue=${10}>Item 1</${optionTag}>
|
||||
|
|
@ -442,7 +441,6 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
await el.updateComplete;
|
||||
expect(_activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal('first');
|
||||
mimicKeyPress(_activeDescendantOwnerNode, 'ArrowDown');
|
||||
// _activeDescendantOwnerNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
|
||||
await el.updateComplete;
|
||||
expect(_activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal('second');
|
||||
});
|
||||
|
|
@ -601,7 +599,6 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
// Normalize
|
||||
el.activeIndex = 0;
|
||||
const options = el.formElements;
|
||||
// mimicKeyPress(listbox, 'ArrowUp');
|
||||
|
||||
mimicKeyPress(_listboxNode, 'ArrowUp');
|
||||
|
||||
|
|
@ -609,7 +606,6 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
expect(options[1].active).to.be.false;
|
||||
expect(options[2].active).to.be.false;
|
||||
el.activeIndex = 2;
|
||||
// mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
|
||||
expect(options[0].active).to.be.false;
|
||||
|
|
@ -709,32 +705,138 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
});
|
||||
});
|
||||
// TODO: add key combinations like shift+home/ctrl+A etc etc.
|
||||
// TODO: nice to have. Get from menu impl.
|
||||
it.skip('selects a value with single [character] key', async () => {
|
||||
|
||||
describe('Typeahead', () => {
|
||||
it('activates a value with single [character] key', async () => {
|
||||
const el = await fixture(html`
|
||||
<${tag} opened>
|
||||
<${optionTag} .choiceValue=${'a'}>A</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'b'}>B</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'c'}>C</${optionTag}>
|
||||
<${tag} opened id="color" name="color" label="Favorite color">
|
||||
<${optionTag} .choiceValue=${'red'}>Red</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'teal'}>Teal</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'turquoise'}>Turquoise</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
expect(el.modelValue).to.equal('a');
|
||||
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'C' }));
|
||||
expect(el.modelValue).to.equal('c');
|
||||
// @ts-expect-error [allow-protected-in-tests]
|
||||
if (el._noTypeAhead) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
// Normalize start values between listbox, select and combobox and test interaction below
|
||||
el.activeIndex = 0;
|
||||
|
||||
mimicKeyPress(_listboxNode, 't', 'KeyT');
|
||||
// await aTimeout(0);
|
||||
expect(el.activeIndex).to.equal(1);
|
||||
});
|
||||
it.skip('selects a value with multiple [character] keys', async () => {
|
||||
|
||||
it('activates a value with multiple [character] keys', async () => {
|
||||
const el = await fixture(html`
|
||||
<${tag} opened>
|
||||
<${optionTag} .choiceValue=${'bar'}>Bar</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'far'}>Far</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'foo'}>Foo</${optionTag}>
|
||||
<${tag} opened id="color" name="color" label="Favorite color">
|
||||
<${optionTag} .choiceValue=${'red'}>Red</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'teal'}>Teal</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'turquoise'}>Turquoise</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'F' }));
|
||||
expect(el.modelValue).to.equal('far');
|
||||
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'O' }));
|
||||
expect(el.modelValue).to.equal('foo');
|
||||
// @ts-expect-error [allow-protected-in-tests]
|
||||
if (el._noTypeAhead) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
// Normalize start values between listbox, select and combobox and test interaction below
|
||||
el.activeIndex = 0;
|
||||
|
||||
mimicKeyPress(_listboxNode, 't', 'KeyT');
|
||||
expect(el.activeIndex).to.equal(1);
|
||||
|
||||
mimicKeyPress(_listboxNode, 'u', 'KeyU');
|
||||
expect(el.activeIndex).to.equal(2);
|
||||
});
|
||||
|
||||
it('selects a value with [character] keys and selectionFollowsFocus', async () => {
|
||||
const el = await fixture(html`
|
||||
<${tag} opened id="color" name="color" label="Favorite color" selection-follows-focus>
|
||||
<${optionTag} .choiceValue=${'red'}>Red</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'teal'}>Teal</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'turquoise'}>Turquoise</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
// @ts-expect-error [allow-protected-in-tests]
|
||||
if (el._noTypeAhead) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
// Normalize start values between listbox, select and combobox and test interaction below
|
||||
el.checkedIndex = 0;
|
||||
|
||||
mimicKeyPress(_listboxNode, 't', 'KeyT');
|
||||
expect(el.checkedIndex).to.equal(1);
|
||||
|
||||
mimicKeyPress(_listboxNode, 'u', 'KeyU');
|
||||
expect(el.checkedIndex).to.equal(2);
|
||||
});
|
||||
|
||||
it('clears typedChars after _typeAheadTimeout', async () => {
|
||||
const el = await fixture(html`
|
||||
<${tag} opened id="color" name="color" label="Favorite color">
|
||||
<${optionTag} .choiceValue=${'red'}>Red</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'teal'}>Teal</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'turquoise'}>turquoise</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
// @ts-expect-error [allow-protected-in-tests]
|
||||
if (el._noTypeAhead) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clock = sinon.useFakeTimers();
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
mimicKeyPress(_listboxNode, 't', 'KeyT');
|
||||
// @ts-ignore [allow-private] in test
|
||||
expect(el.__typedChars).to.deep.equal(['t']);
|
||||
|
||||
mimicKeyPress(_listboxNode, 'u', 'KeyU');
|
||||
// @ts-ignore [allow-private] in test
|
||||
expect(el.__typedChars).to.deep.equal(['t', 'u']);
|
||||
|
||||
clock.tick(1000);
|
||||
// @ts-ignore [allow-private] in test
|
||||
expect(el.__typedChars).to.deep.equal([]);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('clears scheduled timeouts', async () => {
|
||||
const el = await fixture(html`
|
||||
<${tag} opened id="color" name="color" label="Favorite color">
|
||||
<${optionTag} .choiceValue=${'red'}>Red</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'teal'}>Teal</${optionTag}>
|
||||
<${optionTag} .choiceValue=${'turquoise'}>Turquoise</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
// @ts-expect-error [allow-protected-in-tests]
|
||||
if (el._noTypeAhead) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
// Normalize start values between listbox, select and combobox and test interaction below
|
||||
el.activeIndex = 0;
|
||||
mimicKeyPress(_listboxNode, 't', 'KeyT');
|
||||
// @ts-expect-error [allow-private-in-tests]
|
||||
const pendingClear = el.__pendingTypeAheadTimeout;
|
||||
const clearTimeoutSpy = sinon.spy(window, 'clearTimeout');
|
||||
mimicKeyPress(_listboxNode, 'u', 'KeyU');
|
||||
expect(clearTimeoutSpy.args[0][0]).to.equal(pendingClear);
|
||||
});
|
||||
});
|
||||
|
||||
it('navigates to first and last option with [Home] and [End] keys', async () => {
|
||||
const el = await fixture(html`
|
||||
<${tag} opened>
|
||||
|
|
@ -1021,7 +1123,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
const options = el.formElements;
|
||||
// Normalize start values between listbox, slect and combobox and test interaction below
|
||||
// Normalize start values between listbox, select and combobox and test interaction below
|
||||
el.activeIndex = 0;
|
||||
el.checkedIndex = 0;
|
||||
expect(el.activeIndex).to.equal(0);
|
||||
|
|
|
|||
|
|
@ -51,10 +51,14 @@ export declare class ListboxHost {
|
|||
|
||||
protected _listboxReceivesNoFocus: boolean;
|
||||
|
||||
protected _noTypeAhead: boolean;
|
||||
|
||||
protected _uncheckChildren(): void;
|
||||
|
||||
private __setupListboxNode(): void;
|
||||
|
||||
protected _handleTypeAhead(ev: KeyboardEvent, { setAsChecked: boolean }): void;
|
||||
|
||||
protected _getPreviousEnabledOption(currentIndex: number, offset?: number): number;
|
||||
|
||||
protected _getNextEnabledOption(currentIndex: number, offset?: number): number;
|
||||
|
|
@ -78,6 +82,8 @@ export declare class ListboxHost {
|
|||
protected get _activeDescendantOwnerNode(): HTMLElement;
|
||||
|
||||
protected _onListboxContentChanged(): void;
|
||||
|
||||
private __pendingTypeAheadTimeout: number | undefined;
|
||||
}
|
||||
|
||||
export declare function ListboxImplementation<T extends Constructor<LitElement>>(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
# Change Log
|
||||
|
||||
## 0.30.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a28686ee: Add TypeAhead, so with typing characters you will set an option with matching value active/checked
|
||||
- Updated dependencies [a28686ee]
|
||||
- Updated dependencies [43dd1320]
|
||||
- @lion/listbox@0.13.1
|
||||
- @lion/form-core@0.17.1
|
||||
|
||||
## 0.30.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@lion/select-rich",
|
||||
"version": "0.30.0",
|
||||
"version": "0.30.1",
|
||||
"description": "Provides a select with options that can contain html",
|
||||
"license": "MIT",
|
||||
"author": "ing-bank",
|
||||
|
|
@ -42,8 +42,8 @@
|
|||
"dependencies": {
|
||||
"@lion/button": "^0.17.0",
|
||||
"@lion/core": "^0.22.0",
|
||||
"@lion/form-core": "^0.17.0",
|
||||
"@lion/listbox": "^0.13.0",
|
||||
"@lion/form-core": "^0.17.1",
|
||||
"@lion/listbox": "^0.13.1",
|
||||
"@lion/overlays": "^0.32.0"
|
||||
},
|
||||
"keywords": [
|
||||
|
|
|
|||
|
|
@ -489,7 +489,10 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
|
|||
this.opened = true;
|
||||
}
|
||||
break;
|
||||
/* no default */
|
||||
default:
|
||||
if (!this._noTypeAhead) {
|
||||
this._handleTypeAhead(ev, { setAsChecked: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -514,7 +517,6 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
|
|||
// Tab can only be caught in keydown
|
||||
this.opened = false;
|
||||
break;
|
||||
/* no default */
|
||||
case 'Escape':
|
||||
this.opened = false;
|
||||
this.__blockListShowDuringTransition();
|
||||
|
|
|
|||
|
|
@ -10,6 +10,16 @@ import '@lion/select-rich/define';
|
|||
* @typedef {import('@lion/listbox').LionOption} LionOption
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} el
|
||||
* @param {string} key
|
||||
* @param {string} code
|
||||
*/
|
||||
function mimicKeyPress(el, key, code = '') {
|
||||
el.dispatchEvent(new KeyboardEvent('keydown', { key, code }));
|
||||
el.dispatchEvent(new KeyboardEvent('keyup', { key, code }));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LionSelectRich} lionSelectEl
|
||||
*/
|
||||
|
|
@ -142,6 +152,27 @@ describe('lion-select-rich interactions', () => {
|
|||
expect(el.checkedIndex).to.equal(0);
|
||||
expectOnlyGivenOneOptionToBeChecked(options, 0);
|
||||
});
|
||||
|
||||
it('checkes a value with [character] keys while listbox unopened', async () => {
|
||||
const el = /** @type {LionSelectRich} */ (
|
||||
await fixture(html`
|
||||
<lion-select-rich interaction-mode="windows/linux">
|
||||
<lion-options slot="input">
|
||||
<lion-option .choiceValue=${'red'}>Red</lion-option>
|
||||
<lion-option .choiceValue=${'teal'}>Teal</lion-option>
|
||||
<lion-option .choiceValue=${'turquoise'}>Turquoise</lion-option>
|
||||
</lion-options>
|
||||
</lion-select-rich>
|
||||
`)
|
||||
);
|
||||
|
||||
// @ts-ignore [allow-private] in test
|
||||
mimicKeyPress(el, 't', 'KeyT');
|
||||
expect(el.checkedIndex).to.equal(1);
|
||||
|
||||
mimicKeyPress(el, 'u', 'KeyU');
|
||||
expect(el.checkedIndex).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Disabled', () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# Change Log
|
||||
|
||||
## 0.20.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 355aabc0: fix(switch) unregister on disconnectedCallback
|
||||
- Updated dependencies [43dd1320]
|
||||
- @lion/form-core@0.17.1
|
||||
|
||||
## 0.20.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@lion/switch",
|
||||
"version": "0.20.0",
|
||||
"version": "0.20.1",
|
||||
"description": "A Switch is used for switching a property or feature on and off",
|
||||
"license": "MIT",
|
||||
"author": "ing-bank",
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@lion/core": "^0.22.0",
|
||||
"@lion/form-core": "^0.17.0",
|
||||
"@lion/form-core": "^0.17.1",
|
||||
"@lion/helpers": "^0.11.0"
|
||||
},
|
||||
"keywords": [
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const yarnLockPath = './yarn.lock';
|
||||
const data = fs.readFileSync(path.resolve(yarnLockPath), 'utf8');
|
||||
if (data.match(/artifactory/g)) {
|
||||
const lockFileContent = fs.readFileSync(path.resolve('./yarn.lock'), 'utf8');
|
||||
|
||||
const allowedRegistries = ['registry.yarnpkg.com', 'registry.npmjs.org'];
|
||||
const resolvedUrls = lockFileContent.match(/"https:.*"/g);
|
||||
resolvedUrls.forEach(url => {
|
||||
const [, registry] = url.match(/^"https:\/\/(.*?)\/.*"$/) || [];
|
||||
if (!allowedRegistries.includes(registry)) {
|
||||
throw new Error(
|
||||
'Artifactory references in your yarn.lock! Please make sure you are using a public npm registry when downloading your dependencies!',
|
||||
`Disallowed registries ("${registry}") in your yarn.lock!
|
||||
Please make sure you are using a public npm registry when downloading your dependencies!`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3430,6 +3430,11 @@ autosize@4.0.2:
|
|||
resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.2.tgz#073cfd07c8bf45da4b9fd153437f5bafbba1e4c9"
|
||||
integrity sha512-jnSyH2d+qdfPGpWlcuhGiHmqBJ6g3X+8T+iRwFrHPLVcdoGJE/x6Qicm6aDHfTsbgZKxyV8UU/YB2p4cjKDRRA==
|
||||
|
||||
awesome-phonenumber@3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/awesome-phonenumber/-/awesome-phonenumber-3.0.1.tgz#8d73aaa1c2b0a660b117567b0d9797623457e1d0"
|
||||
integrity sha512-H5rqjTJ1+HxmyuSKDoPgvHUgP+RBRhtWQ25ccy4BmSLQL5UVg3K+yo2QCX4IlkxiVNst3suGMArV9TH7B1KEPw==
|
||||
|
||||
axe-core@^4.3.3:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.3.tgz#b55cd8e8ddf659fe89b064680e1c6a4dceab0325"
|
||||
|
|
|
|||
Loading…
Reference in a new issue