@uiloos/core
Version:
The core of the uiloos headless UI
1,489 lines (1,467 loc) • 106 kB
JavaScript
class _LicenseChecker {
constructor() {
this._licenseKey = '';
this._logOnSuccess = false;
this._success = false;
}
activateLicense(licenseKey, options = { logLicenseActivated: true }) {
this._licenseKey = licenseKey;
this._logOnSuccess = options.logLicenseActivated;
}
_checkLicense() {
if (this._success) {
return;
}
if (this._licenseKey) {
const parts = this._licenseKey.split('-');
if (parts.length !== 2) {
console.warn(`uiloos > license > invalid license key detected: ${this._licenseKey}, ${buy}`);
}
else {
const [_, type] = parts;
if (this._logOnSuccess) {
console.log(`uiloos > license > license activated, this license is for use with ${type} developers. We thank you for your support, you can disable this message if you want to. ${owner}`);
}
this._success = true;
}
}
else {
console.warn(`uiloos > license > you are using commercial software, ${buy}`);
}
}
}
let licenseChecker = new _LicenseChecker();
const owner = 'If you are not the owner of this website please ignore this message.';
const buy = `please purchase a license at https://www.uiloos.dev. ${owner}`;
const common$3 = `uiloos > ActiveList >`;
class ActiveListAutoPlayDurationError extends Error {
constructor() {
super(`${common$3} autoPlay > duration cannot be negative or zero`);
this.name = 'ActiveListAutoPlayDurationError';
}
}
class _AutoPlay {
constructor(activeList, config) {
this._autoPlayTimeoutId = null;
this._autoPlayStarted = new Date();
this._pauseStarted = null;
this._autoPlayCurrentDuration = 0;
this._config = null;
this._activeList = activeList;
this._config = config;
}
_setConfig(config) {
this._config = config;
}
_play(inform) {
this._cancelTimer();
if (!this._config || this._activeList.lastActivatedContent === null) {
return;
}
const duration = this._getDuration(this._config, this._activeList.lastActivatedContent);
if (duration <= 0) {
throw new ActiveListAutoPlayDurationError();
}
this._autoPlayCurrentDuration = duration;
if (this._pauseStarted === null) {
this._activeList.autoPlay.duration = duration;
}
else {
this._pauseStarted = null;
}
this._autoPlayStarted = new Date();
this._autoPlayTimeoutId = window.setTimeout(() => {
this._autoPlayTimeoutId = null;
this._activeList.activateNext({ isUserInteraction: false });
}, duration);
this._activeList.autoPlay.isPlaying = true;
if (inform) {
const event = {
type: 'AUTO_PLAY_PLAYING',
time: new Date(),
};
this._activeList._inform(event);
}
}
_pause() {
if (!this._activeList.autoPlay.isPlaying) {
return;
}
this._pauseStarted = new Date();
this._cancelTimer();
this._activeList.autoPlay.isPlaying = false;
const event = {
type: 'AUTO_PLAY_PAUSED',
time: new Date(),
};
this._activeList._inform(event);
}
_stop() {
if (!this._activeList.autoPlay.isPlaying && !this._pauseStarted) {
return;
}
this._cancelTimer();
this._pauseStarted = null;
this._activeList.autoPlay.isPlaying = false;
this._activeList.autoPlay.duration = 0;
this._activeList.autoPlay.hasBeenStoppedBefore = true;
const event = {
type: 'AUTO_PLAY_STOPPED',
time: new Date(),
};
this._activeList._inform(event);
}
_cancelTimer() {
if (this._autoPlayTimeoutId !== null) {
window.clearTimeout(this._autoPlayTimeoutId);
this._autoPlayTimeoutId = null;
}
}
_onDeactivation(activationOptions) {
if (this._activeList.lastActivatedContent === null) {
this._stop();
return;
}
if (!this._config) {
return;
}
if (this._shouldStopOnUserInteraction(activationOptions, this._config)) {
this._stop();
return;
}
}
onActiveIndexChanged(index, activationOptions) {
if (!this._config) {
return;
}
if (this._shouldStopOnUserInteraction(activationOptions, this._config)) {
this._stop();
}
else if (this._activeList.isCircular === false &&
index === this._activeList.getLastIndex()) {
this._stop();
}
else {
this._play(false);
}
}
_shouldStopOnUserInteraction(activationOptions, config) {
return !!(activationOptions &&
activationOptions.isUserInteraction !== false &&
config.stopsOnUserInteraction);
}
_getDuration(config, lastActivatedContent) {
if (this._pauseStarted) {
return (this._autoPlayCurrentDuration -
(this._pauseStarted.getTime() - this._autoPlayStarted.getTime()));
}
if (typeof config.duration === 'number') {
return config.duration;
}
else {
return config.duration({
index: lastActivatedContent.index,
content: lastActivatedContent,
value: lastActivatedContent.value,
activeList: this._activeList,
});
}
}
}
class ActiveListContent {
constructor(activeList, index, value) {
this.isActive = false;
this.hasBeenActiveBefore = false;
this.isFirst = false;
this.isLast = false;
this.hasNext = false;
this.hasPrevious = false;
this.isNext = false;
this.isPrevious = false;
this.activeList = activeList;
this.index = index;
this.value = value;
}
activate(activationOptions) {
this.activeList.activateByIndex(this.index, activationOptions);
}
deactivate(activationOptions) {
this.activeList.deactivateByIndex(this.index, activationOptions);
}
toggle(activationOptions) {
this.activeList.toggleByIndex(this.index, activationOptions);
}
remove() {
return this.activeList.removeByIndex(this.index);
}
swapWith(item) {
const itemIndex = this.activeList.getIndex(item);
this.swapWithByIndex(itemIndex);
}
swapWithByIndex(index) {
this.activeList.swapByIndex(this.index, index);
}
swapWithNext() {
const nextIndex = this.activeList._getBoundedNextIndex(this.index);
this.swapWithByIndex(nextIndex);
}
swapWithPrevious() {
const previousIndex = this.activeList._getBoundedPreviousIndex(this.index);
this.swapWithByIndex(previousIndex);
}
moveToIndex(to) {
this.activeList.moveByIndex(this.index, to);
}
moveToPredicate(predicate, options) {
this.activeList.moveByIndexByPredicate(this.index, predicate, options);
}
moveToFirst() {
this.activeList.moveByIndex(this.index, 0);
}
moveToLast() {
this.activeList.moveByIndex(this.index, this.activeList.getLastIndex());
}
}
class ActiveListCooldownDurationError extends Error {
constructor() {
super(`${common$3} cooldown > duration cannot be negative or zero`);
this.name = "ActiveListCooldownDurationError";
}
}
class _CooldownTimer {
constructor(activeList, cooldown) {
this._cooldownTimeoutId = null;
this._cooldown = undefined;
if (typeof cooldown === 'number') {
this._assertDuration(cooldown);
}
this._activeList = activeList;
this._cooldown = cooldown;
}
_isActive(activationOptions) {
if (activationOptions.isUserInteraction === false) {
return false;
}
return this._activeList.cooldown.isActive;
}
_setCooldown(activationOptions, content) {
if (activationOptions.isUserInteraction === false) {
return;
}
const duration = this._getDuration(activationOptions, content);
if (duration === -1) {
this._stopCooldown();
return;
}
this._activeList.cooldown.isActive = true;
this._activeList.cooldown.duration = duration;
this._cooldownTimeoutId = window.setTimeout(() => {
this._stopCooldown();
}, duration);
const event = {
type: 'COOLDOWN_STARTED',
time: new Date(),
};
this._activeList._inform(event);
}
_getDuration(activationOptions, content) {
let duration = -1;
if (activationOptions.cooldown !== undefined) {
duration = this._getDurationFromConfig(activationOptions.cooldown, content);
}
else if (this._cooldown !== undefined) {
duration = this._getDurationFromConfig(this._cooldown, content);
}
else {
return -1;
}
this._assertDuration(duration);
return duration;
}
_getDurationFromConfig(cooldownConfig, content) {
if (typeof cooldownConfig === 'number') {
return cooldownConfig;
}
else {
return cooldownConfig({
index: content.index,
content: content,
value: content.value,
activeList: this._activeList,
});
}
}
_assertDuration(cooldownValue) {
if (cooldownValue <= 0) {
throw new ActiveListCooldownDurationError();
}
}
_stopCooldown() {
if (this._cooldownTimeoutId) {
window.clearTimeout(this._cooldownTimeoutId);
}
if (!this._activeList.cooldown.isActive) {
return;
}
this._activeList.cooldown.isActive = false;
this._activeList.cooldown.duration = 0;
const event = {
type: 'COOLDOWN_ENDED',
time: new Date(),
};
this._activeList._inform(event);
}
}
class ActiveListActivationLimitReachedError extends Error {
constructor() {
super(`${common$3} activateByIndex > activation limit reached`);
this.name = 'ActiveListActivationLimitReachedError';
}
}
class ActiveListIndexOutOfBoundsError extends Error {
constructor(message) {
super(message);
this.name = "ActiveListIndexOutOfBoundsError";
}
}
function throwIndexOutOfBoundsError(method, indexName) {
throw new ActiveListIndexOutOfBoundsError(`${common$3} ${method} > "${indexName}" is out of bounds`);
}
class ActiveListItemNotFoundError extends Error {
constructor() {
super(`${common$3} getIndex > index cannot be found, item is not in contents array`);
this.name = 'ActiveListItemNotFoundError';
}
}
class _History {
constructor() {
this._events = [];
this._keepHistoryFor = 0;
}
_push(event) {
if (this._keepHistoryFor > 0) {
this._events.push(event);
if (this._events.length - 1 === this._keepHistoryFor) {
this._events.shift();
}
}
}
_setKeepHistoryFor(_keepHistoryFor = 0) {
this._keepHistoryFor = _keepHistoryFor;
}
}
class _Observer {
constructor() {
this._subscribers = [];
}
_subscribe(subscriber) {
this._subscribers.push(subscriber);
return () => {
this._unsubscribe(subscriber);
};
}
_unsubscribe(subscriber) {
this._subscribers = this._subscribers.filter((s) => subscriber !== s);
}
_clear() {
this._subscribers.length = 0;
}
_inform(observable, event) {
this._subscribers.forEach((subscriber) => subscriber(observable, event));
}
}
class ActiveList {
constructor(config = {}, subscriber) {
this._isInitializing = false;
this.contents = [];
this.maxActivationLimit = 1;
this.maxActivationLimitBehavior = 'circular';
this.active = [];
this.activeContents = [];
this.activeIndexes = [];
this.lastActivated = null;
this.lastActivatedContent = null;
this.lastActivatedIndex = -1;
this.lastDeactivated = null;
this.lastDeactivatedContent = null;
this.lastDeactivatedIndex = -1;
this.isCircular = false;
this.direction = 'right';
this.oppositeDirection = 'left';
this._history = new _History();
this.history = this._history._events;
this._observer = new _Observer();
this.hasActiveChangedAtLeastOnce = false;
this.cooldown = {
isActive: false,
duration: 0,
};
this.autoPlay = {
isPlaying: false,
duration: 0,
hasBeenStoppedBefore: false,
};
licenseChecker._checkLicense();
if (subscriber) {
this.subscribe(subscriber);
}
this.initialize(config);
}
subscribe(subscriber) {
return this._observer._subscribe(subscriber);
}
unsubscribe(subscriber) {
this._observer._unsubscribe(subscriber);
}
unsubscribeAll() {
this._observer._clear();
}
initialize(config) {
this._isInitializing = true;
this.maxActivationLimit =
config.maxActivationLimit !== undefined ? config.maxActivationLimit : 1;
this.maxActivationLimitBehavior = config.maxActivationLimitBehavior
? config.maxActivationLimitBehavior
: 'circular';
this.isCircular = !!config.isCircular;
const contents = config.contents ? config.contents : [];
this.contents.length = 0;
contents.forEach((c, index) => {
this.contents[index] = this._initializeABrokenContent(c, index, contents);
});
this._directions = config.directions
? config.directions
: { next: 'right', previous: 'left' };
this._history._events.length = 0;
this._history._setKeepHistoryFor(config.keepHistoryFor);
this._becameEmpty();
this._activationCooldownTimer = new _CooldownTimer(this, config.cooldown);
this.cooldown.isActive = false;
this.cooldown.duration = 0;
if (config.active !== undefined) {
if (Array.isArray(config.active)) {
config.active.forEach((active) => this.activate(active, { isUserInteraction: false }));
}
else {
this.activate(config.active, { isUserInteraction: false });
}
}
else if (config.activeIndexes !== undefined) {
if (Array.isArray(config.activeIndexes)) {
config.activeIndexes.forEach((index) => this.activateByIndex(index, {
isUserInteraction: false,
}));
}
else {
this.activateByIndex(config.activeIndexes, {
isUserInteraction: false,
});
}
}
this._emptyLastDeactivated();
this.hasActiveChangedAtLeastOnce = false;
this.direction = this._directions.next;
this.oppositeDirection = this._directions.previous;
this.autoPlay.isPlaying = false;
this.autoPlay.duration = 0;
this.autoPlay.hasBeenStoppedBefore = false;
this._autoPlay = new _AutoPlay(this, config.autoPlay ? config.autoPlay : null);
this._autoPlay._play(false);
this._isInitializing = false;
const event = {
type: 'INITIALIZED',
values: [...this.active],
indexes: [...this.activeIndexes],
time: new Date(),
};
this._inform(event);
}
_initializeABrokenContent(value, index, contents) {
const content = new ActiveListContent(this, index, value);
this._repairContent(content, index, contents);
return content;
}
activateByIndex(index, activationOptions = {
isUserInteraction: true,
cooldown: undefined,
}) {
const previousDeactivatedIndex = this.lastDeactivatedIndex;
const activatedContent = this._doActivateByIndex(index, activationOptions);
if (!activatedContent) {
return;
}
let deactivatedIndex = -1;
let deactivatedValue = null;
if (previousDeactivatedIndex !== this.lastDeactivatedIndex) {
deactivatedIndex = this.lastDeactivatedIndex;
deactivatedValue = this.lastDeactivated;
}
const event = {
type: 'ACTIVATED',
value: activatedContent.value,
index,
deactivatedIndex,
deactivatedValue,
time: new Date(),
};
this._inform(event);
this._activationCooldownTimer._setCooldown(activationOptions, this.lastActivatedContent);
}
_doActivateByIndex(index, activationOptions) {
if (this._checkIndex(index)) {
throwIndexOutOfBoundsError('activateByIndex', 'index');
}
if (this.activeIndexes.includes(index)) {
return null;
}
if (this._activationCooldownTimer._isActive(activationOptions)) {
return null;
}
const limitReached = this.maxActivationLimit === false
? false
: this.maxActivationLimit === this.activeIndexes.length;
if (limitReached) {
if (this.maxActivationLimitBehavior === 'error') {
throw new ActiveListActivationLimitReachedError();
}
else if (this.maxActivationLimitBehavior === 'ignore') {
return null;
}
}
const nextIndex = this._getUnboundedNextIndex(index);
const previousIndex = this._getUnboundedPreviousIndex(index);
this.contents.forEach((content, i) => {
content.isActive = content.isActive ? content.isActive : index === i;
content.isNext = nextIndex === i;
content.isPrevious = previousIndex === i;
if (index === i) {
this.activeIndexes.push(i);
this.activeContents.push(content);
this.active.push(content.value);
if (limitReached) {
this.activeIndexes.shift();
this.active.shift();
const content = this.activeContents.shift();
if (content) {
this._deactivateContent(content);
}
}
this.direction = this._getDirectionWhenMovingToIndex(i);
this.oppositeDirection =
this.direction === this._directions.next
? this._directions.previous
: this._directions.next;
this.lastActivated = content.value;
this.lastActivatedContent = content;
this.lastActivatedIndex = i;
content.hasBeenActiveBefore = true;
}
});
if (this._autoPlay) {
this._autoPlay.onActiveIndexChanged(index, activationOptions);
}
this.hasActiveChangedAtLeastOnce = true;
return this.lastActivatedContent;
}
activate(item, activationOptions) {
const index = this.getIndex(item);
this.activateByIndex(index, activationOptions);
}
activateByPredicate(predicate, activationOptions = {
isUserInteraction: true,
cooldown: undefined,
}) {
if (this._activationCooldownTimer._isActive(activationOptions)) {
return undefined;
}
const previousActiveIndexes = [...this.activeIndexes];
let lastActivated = null;
let error = null;
this._execPred(predicate, (index) => {
try {
const content = this._doActivateByIndex(index, activationOptions);
if (content) {
lastActivated = content;
}
}
catch (e) {
if (e instanceof ActiveListActivationLimitReachedError) {
error = e;
return true;
}
}
});
if (lastActivated) {
const values = [];
const indexes = [];
this.activeIndexes.forEach((current) => {
const isNewlyActivated = previousActiveIndexes.every((prev) => prev !== current);
if (isNewlyActivated) {
indexes.push(current);
values.push(this.contents[current].value);
}
});
const deactivatedValues = [];
const deactivatedIndexes = [];
if (this.maxActivationLimit !== false &&
this.maxActivationLimitBehavior === 'circular') {
previousActiveIndexes.forEach((prev) => {
const isNewlyDeactivated = this.activeIndexes.every((current) => prev !== current);
if (isNewlyDeactivated) {
deactivatedIndexes.push(prev);
deactivatedValues.push(this.contents[prev].value);
}
});
}
const event = {
type: 'ACTIVATED_MULTIPLE',
values,
indexes,
deactivatedIndexes,
deactivatedValues,
time: new Date(),
};
this._inform(event);
if (lastActivated) {
this._activationCooldownTimer._setCooldown(activationOptions, lastActivated);
}
}
if (error) {
throw error;
}
}
activateNext(activationOptions) {
if (this.isEmpty()) {
return;
}
const index = this._getBoundedNextIndex(this.lastActivatedIndex);
this.activateByIndex(index, activationOptions);
}
activatePrevious(activationOptions) {
if (this.isEmpty()) {
return;
}
const index = this._getBoundedPreviousIndex(this.lastActivatedIndex);
this.activateByIndex(index, activationOptions);
}
activateFirst(activationOptions) {
if (this.isEmpty()) {
return;
}
this.activateByIndex(0, activationOptions);
}
activateLast(activationOptions) {
if (this.isEmpty()) {
return;
}
this.activateByIndex(this.getLastIndex(), activationOptions);
}
deactivateByIndex(index, activationOptions = {
isUserInteraction: true,
cooldown: undefined,
}) {
const deactivatedContent = this._doDeactivateByIndex(index, activationOptions);
if (!deactivatedContent) {
return;
}
const event = {
type: 'DEACTIVATED',
value: this.contents[index].value,
index,
time: new Date(),
};
this._inform(event);
this._activationCooldownTimer._setCooldown(activationOptions, deactivatedContent);
}
_doDeactivateByIndex(index, activationOptions) {
if (this._checkIndex(index)) {
throwIndexOutOfBoundsError('deactivateByIndex', 'index');
}
const indexOfIndex = this.activeIndexes.indexOf(index);
if (indexOfIndex === -1) {
return null;
}
if (this._activationCooldownTimer._isActive(activationOptions)) {
return null;
}
const deactivatedContent = this.activeContents[indexOfIndex];
this._deactivateContent(deactivatedContent);
this.activeIndexes.splice(indexOfIndex, 1);
this.active.splice(indexOfIndex, 1);
this.activeContents.splice(indexOfIndex, 1);
if (this.activeIndexes.length === 0) {
this._emptyLastActives();
this.direction = this._directions.next;
}
else {
this._setLastActives();
this.direction = this._getDirectionWhenMovingToIndex(deactivatedContent.index);
this.oppositeDirection = this.direction;
this.direction =
this.direction === this._directions.next
? this._directions.previous
: this._directions.next;
}
this._repairContents();
this.hasActiveChangedAtLeastOnce = true;
if (this._autoPlay) {
this._autoPlay._onDeactivation(activationOptions);
}
return deactivatedContent;
}
deactivate(item, activationOptions) {
const index = this.getIndex(item);
this.deactivateByIndex(index, activationOptions);
}
deactivateByPredicate(predicate, activationOptions = {
isUserInteraction: true,
cooldown: undefined,
}) {
if (this._activationCooldownTimer._isActive(activationOptions)) {
return undefined;
}
const deactivatedIndexes = [];
const deactivatedValues = [];
let lastRemoved = null;
this._execPred(predicate, (index) => {
const content = this._doDeactivateByIndex(index, activationOptions);
if (content) {
deactivatedIndexes.push(content.index);
deactivatedValues.push(content.value);
lastRemoved = content;
}
});
if (deactivatedIndexes.length === 0) {
return;
}
const event = {
type: 'DEACTIVATED_MULTIPLE',
values: deactivatedValues,
indexes: deactivatedIndexes,
time: new Date(),
};
this._inform(event);
if (lastRemoved) {
this._activationCooldownTimer._setCooldown(activationOptions, lastRemoved);
}
}
toggleByIndex(index, activationOptions) {
if (this._checkIndex(index)) {
throwIndexOutOfBoundsError('toggleByIndex', 'index');
}
if (this.contents[index].isActive) {
this.deactivateByIndex(index, activationOptions);
}
else {
this.activateByIndex(index, activationOptions);
}
}
toggle(item, activationOptions) {
const index = this.getIndex(item);
this.toggleByIndex(index, activationOptions);
}
play() {
this._autoPlay._play(true);
}
pause() {
this._autoPlay._pause();
}
stop() {
this._autoPlay._stop();
}
configureAutoPlay(autoPlayConfig) {
this._autoPlay._setConfig(autoPlayConfig);
if (autoPlayConfig) {
this._autoPlay._play(true);
}
else {
this._autoPlay._stop();
}
}
insertAtIndex(item, index) {
if (index < 0 || index > this.contents.length) {
throwIndexOutOfBoundsError('insertAtIndex', 'index');
}
const content = this._initializeABrokenContent(item, index, this.contents);
this.activeIndexes.forEach((i, aiIndex) => {
this.activeIndexes[aiIndex] = i >= index ? i + 1 : i;
});
this.contents.splice(index, 0, content);
if (index <= this.lastActivatedIndex) {
this.lastActivatedIndex += 1;
}
this._repairContents();
const event = {
type: 'INSERTED',
value: item,
index,
time: new Date(),
};
this._inform(event);
return content;
}
push(item) {
return this.insertAtIndex(item, this.contents.length);
}
unshift(item) {
return this.insertAtIndex(item, 0);
}
insertByPredicate(item, predicate, options = { mode: 'at' }) {
const mod = this._modeToMod(options.mode);
return this._execPred(predicate, (index) => {
const atIndex = Math.max(0, index + mod);
return this.insertAtIndex(item, atIndex);
});
}
removeByIndex(index) {
const value = this._doRemoveAtIndex(index);
const indexOfIndex = this.activeIndexes.indexOf(index);
if (indexOfIndex !== -1) {
this.activeIndexes.splice(indexOfIndex, 1);
this.active.splice(indexOfIndex, 1);
this.activeContents.splice(indexOfIndex, 1);
this.hasActiveChangedAtLeastOnce = true;
}
this.activeIndexes.map((i, aiIndex) => {
this.activeIndexes[aiIndex] = i >= index ? i - 1 : i;
});
if (this.isEmpty()) {
this._becameEmpty();
this._autoPlay._stop();
}
else {
this._setLastActives();
}
this._repairContents();
const event = {
type: 'REMOVED',
value,
index,
time: new Date(),
};
this._inform(event);
return value;
}
_doRemoveAtIndex(index) {
if (this._checkIndex(index)) {
throwIndexOutOfBoundsError('removeByIndex', 'index');
}
if (this.lastDeactivated && this.lastDeactivatedIndex === index) {
this._emptyLastDeactivated();
}
const value = this.contents[index].value;
this.contents.splice(index, 1);
return value;
}
remove(item) {
const index = this.getIndex(item);
return this.removeByIndex(index);
}
pop() {
if (this.isEmpty()) {
return undefined;
}
return this.removeByIndex(this.getLastIndex());
}
shift() {
if (this.isEmpty()) {
return undefined;
}
return this.removeByIndex(0);
}
removeByPredicate(predicate) {
if (this.isEmpty()) {
return [];
}
const removed = [];
this._execPred(predicate, (index) => {
const content = this.contents[index];
removed.push(content);
});
const removedIndexes = [];
removed.forEach((content, index) => {
const actualIndex = content.index - index;
this._doRemoveAtIndex(actualIndex);
removedIndexes.push(content.index);
});
if (this.isEmpty()) {
this._becameEmpty();
this._autoPlay._stop();
}
else {
removedIndexes.forEach((index) => {
const indexOfIndex = this.activeIndexes.indexOf(index);
if (indexOfIndex !== -1) {
this.activeIndexes.splice(indexOfIndex, 1);
this.active.splice(indexOfIndex, 1);
this.activeContents.splice(indexOfIndex, 1);
this.hasActiveChangedAtLeastOnce = true;
}
});
removedIndexes.forEach((removed) => {
this.activeIndexes.forEach((index, aiIndex) => {
this.activeIndexes[aiIndex] = index >= removed ? index - 1 : index;
});
});
this._setLastActives();
}
const removedValues = removed.map((r) => r.value);
if (removedIndexes.length > 0) {
this._repairContents();
const event = {
type: 'REMOVED_MULTIPLE',
indexes: [...removedIndexes],
values: [...removedValues],
time: new Date(),
};
this._inform(event);
}
return removedValues;
}
swapByIndex(a, b) {
if (this._checkIndex(a)) {
throwIndexOutOfBoundsError('swapByIndex', 'a');
}
if (this._checkIndex(b)) {
throwIndexOutOfBoundsError('swapByIndex', 'b');
}
if (a === b) {
return;
}
const itemA = this.contents[a];
const itemB = this.contents[b];
if (this.lastActivatedIndex === itemA.index) {
this.lastActivatedIndex = itemB.index;
}
else if (this.lastActivatedIndex === itemB.index) {
this.lastActivatedIndex = itemA.index;
}
const indexOfA = this.activeIndexes.indexOf(itemA.index);
const indexOfB = this.activeIndexes.indexOf(itemB.index);
if (indexOfA !== -1) {
this.activeIndexes[indexOfA] = itemB.index;
}
if (indexOfB !== -1) {
this.activeIndexes[indexOfB] = itemA.index;
}
itemA.index = b;
itemB.index = a;
this.contents[a] = itemB;
this.contents[b] = itemA;
this._repairContents();
const event = {
type: 'SWAPPED',
value: {
a: itemA.value,
b: itemB.value,
},
index: {
a,
b,
},
time: new Date(),
};
this._inform(event);
}
swap(a, b) {
const indexA = this.getIndex(a);
const indexB = this.getIndex(b);
this.swapByIndex(indexA, indexB);
}
moveByIndex(from, to) {
if (this._checkIndex(from)) {
throwIndexOutOfBoundsError('moveByIndex', 'from');
}
if (this._checkIndex(to)) {
throwIndexOutOfBoundsError('moveByIndex', 'to');
}
if (from === to) {
return;
}
const lastActivatedIndex = this.lastActivatedIndex;
if (lastActivatedIndex === from) {
this.lastActivatedIndex = to;
}
else if (to === lastActivatedIndex && from > lastActivatedIndex) {
this.lastActivatedIndex += 1;
}
else if (to === lastActivatedIndex && from < lastActivatedIndex) {
this.lastActivatedIndex -= 1;
}
else if (to > lastActivatedIndex && from < lastActivatedIndex) {
this.lastActivatedIndex -= 1;
}
else if (to < lastActivatedIndex && from > lastActivatedIndex) {
this.lastActivatedIndex += 1;
}
this.activeIndexes.forEach((index, aiIndex) => {
if (index === from) {
this.activeIndexes[aiIndex] = to;
return;
}
if (index > from && index > to) {
this.activeIndexes[aiIndex] = index;
return;
}
if (index < from && index < to) {
this.activeIndexes[aiIndex] = index;
return;
}
this.activeIndexes[aiIndex] = from > to ? index + 1 : index - 1;
});
const fromItem = this.contents[from];
this.contents.splice(from, 1);
this.contents.splice(to, 0, fromItem);
this._repairContents();
const event = {
type: 'MOVED',
value: fromItem.value,
index: {
from,
to,
},
time: new Date(),
};
this._inform(event);
}
move(item, to) {
const from = this.getIndex(item);
this.moveByIndex(from, to);
}
moveByIndexByPredicate(index, predicate, options = { mode: 'at' }) {
const mod = this._modeToMod(options.mode);
this._execPred(predicate, (i, length) => {
const atIndex = Math.min(Math.max(0, i + mod), length - 1);
this.moveByIndex(index, atIndex);
return true;
});
}
moveByPredicate(item, predicate, options) {
const index = this.getIndex(item);
this.moveByIndexByPredicate(index, predicate, options);
}
getIndex(item) {
const contents = this.contents;
const length = contents.length;
for (let i = 0; i < length; i++) {
if (contents[i].value === item) {
return i;
}
}
throw new ActiveListItemNotFoundError();
}
getLastIndex() {
return this.contents.length - 1;
}
_getBoundedNextIndex(index) {
let nextIndex = index + 1;
if (nextIndex >= this.contents.length) {
nextIndex = this.isCircular ? 0 : this.getLastIndex();
}
return nextIndex;
}
_getBoundedPreviousIndex(index) {
let previousIndex = index - 1;
if (previousIndex < 0) {
previousIndex = this.isCircular ? this.getLastIndex() : 0;
}
return previousIndex;
}
_getUnboundedNextIndex(index) {
const nextIndex = index + 1;
if (this.isCircular && nextIndex === this.contents.length) {
return 0;
}
return nextIndex;
}
_getUnboundedPreviousIndex(index) {
const previousIndex = index - 1;
if (this.isCircular && previousIndex < 0) {
return this.getLastIndex();
}
return previousIndex;
}
isEmpty() {
return this.contents.length === 0;
}
_getDirectionWhenMovingToIndex(next) {
const lastActivatedIndex = this.lastActivatedIndex;
if (this.isCircular) {
if (this.lastActivatedIndex === -1) {
return this._directions.next;
}
const lastActivatedLargerThanNext = this.lastActivatedIndex > next;
const lastIndex = this.getLastIndex();
const leftDistance = lastActivatedLargerThanNext
? this.lastActivatedIndex - next
: lastIndex - next + this.lastActivatedIndex + 1;
const rightDistance = lastActivatedLargerThanNext
? 1 + next + (lastIndex - this.lastActivatedIndex)
: next - this.lastActivatedIndex;
return leftDistance >= rightDistance
? this._directions.next
: this._directions.previous;
}
else {
return next >= lastActivatedIndex
? this._directions.next
: this._directions.previous;
}
}
_repairContents() {
let nextIndex = null;
let previousIndex = null;
if (this.lastActivatedIndex !== -1) {
nextIndex = this._getUnboundedNextIndex(this.lastActivatedIndex);
previousIndex = this._getUnboundedPreviousIndex(this.lastActivatedIndex);
}
this.contents.forEach((content, index) => {
content.index = index;
content.isNext = nextIndex === index;
content.isPrevious = previousIndex === index;
this._repairContent(content, index, this.contents);
});
}
_repairContent(content, index, contents) {
content.isFirst = index === 0;
content.isLast = index === contents.length - 1;
if (this.isCircular) {
content.hasNext = true;
content.hasPrevious = true;
}
else {
content.hasNext = index + 1 < contents.length;
content.hasPrevious = index - 1 >= 0;
}
}
_emptyLastActives() {
this.lastActivatedIndex = -1;
this.lastActivated = null;
this.lastActivatedContent = null;
}
_emptyLastDeactivated() {
this.lastDeactivatedIndex = -1;
this.lastDeactivated = null;
this.lastDeactivatedContent = null;
}
_becameEmpty() {
this._emptyLastDeactivated();
this._emptyLastActives();
this.activeContents.length = 0;
this.activeIndexes.length = 0;
this.active.length = 0;
this.hasActiveChangedAtLeastOnce = true;
}
_setLastActives() {
if (this.activeIndexes.length === 0) {
this._emptyLastActives();
return;
}
const newLastActiveIndex = this.activeIndexes[this.activeIndexes.length - 1];
const newLastActiveList = this.contents[newLastActiveIndex];
this.lastActivated = newLastActiveList.value;
this.lastActivatedContent = newLastActiveList;
this.lastActivatedIndex = newLastActiveIndex;
}
_deactivateContent(content) {
content.isActive = false;
this.lastDeactivated = content.value;
this.lastDeactivatedContent = content;
this.lastDeactivatedIndex = content.index;
}
_execPred(predicate, action) {
const contents = this.contents;
const length = contents.length;
for (let index = 0; index < length; index++) {
const content = this.contents[index];
const data = {
index,
content,
value: content.value,
activeList: this,
};
if (predicate(data)) {
const result = action(index, length);
if (result !== undefined) {
return result;
}
}
}
return null;
}
_inform(event) {
if (this._isInitializing) {
return;
}
this._history._push(event);
this._observer._inform(this, event);
}
_checkIndex(index) {
return index < 0 || index >= this.contents.length;
}
_modeToMod(mode) {
return mode === 'at' ? 0 : mode === 'after' ? 1 : -1;
}
}
function _callSubscriber(subscriberName, event, component, config) {
const methodName = 'on' +
event.type
.toLowerCase()
.split('_')
.reduce((acc, word) => {
const letters = word.split('');
letters[0] = letters[0].toUpperCase();
return acc + letters.join('');
}, '');
const method = config[methodName];
if (!method) {
if (config.debug) {
console.warn(`uiloos > ${subscriberName} event '${event.type}' was fired but '${methodName}' method is not implemented, this might not be correct.`);
}
return;
}
method(event, component);
}
function createActiveListSubscriber(config) {
return (activeList, event) => {
_callSubscriber('createActiveListSubscriber', event, activeList, config);
};
}
const common$2 = `uiloos > ViewChannel >`;
class ViewChannelAutoDismissDurationError extends Error {
constructor() {
super(`${common$2} autoDismiss > duration cannot be negative or zero`);
this.name = 'ViewChannelAutoDismissDurationError';
}
}
class _AutoDismiss {
constructor(view, config) {
this._autoDismissTimeoutId = null;
this._autoDismissStarted = new Date();
this._pauseStarted = null;
this._autoDismissCurrentDuration = 0;
this._config = null;
this._view = view;
this._config = config;
}
_play(inform) {
if (this._view.autoDismiss.isPlaying) {
return;
}
this._cancelTimer();
if (!this._config) {
return;
}
const duration = this._getDuration(this._config);
if (duration <= 0) {
throw new ViewChannelAutoDismissDurationError();
}
this._autoDismissCurrentDuration = duration;
this._autoDismissStarted = new Date();
if (this._pauseStarted === null) {
this._view.autoDismiss.duration = duration;
}
const result = this._config.result;
this._autoDismissTimeoutId = window.setTimeout(() => {
if (this._view.isPresented) {
this._view.autoDismiss.isPlaying = false;
this._view.autoDismiss.duration = 0;
this._view.viewChannel._doRemoveByIndex(this._view.index, result, 'AUTO_DISMISS');
}
this._autoDismissTimeoutId = null;
}, duration);
this._view.result.then(() => {
this._cancelTimer();
});
this._view.autoDismiss.isPlaying = true;
if (inform) {
const event = {
type: 'AUTO_DISMISS_PLAYING',
view: this._view,
index: this._view.index,
time: new Date(),
};
this._view.viewChannel._inform(event);
}
}
_pause() {
if (!this._view.autoDismiss.isPlaying) {
return;
}
this._pauseStarted = new Date();
this._cancelTimer();
this._view.autoDismiss.isPlaying = false;
const event = {
type: 'AUTO_DISMISS_PAUSED',
view: this._view,
index: this._view.index,
time: new Date(),
};
this._view.viewChannel._inform(event);
}
_stop() {
if (!this._view.autoDismiss.isPlaying && !this._pauseStarted) {
return;
}
this._cancelTimer();
this._pauseStarted = null;
this._view.autoDismiss.isPlaying = false;
this._view.autoDismiss.duration = 0;
const event = {
type: 'AUTO_DISMISS_STOPPED',
view: this._view,
index: this._view.index,
time: new Date(),
};
this._view.viewChannel._inform(event);
}
_cancelTimer() {
if (this._autoDismissTimeoutId !== null) {
window.clearTimeout(this._autoDismissTimeoutId);
this._autoDismissTimeoutId = null;
}
}
_getDuration(config) {
if (this._pauseStarted) {
return (this._autoDismissCurrentDuration -
(this._pauseStarted.getTime() - this._autoDismissStarted.getTime()));
}
return config.duration;
}
}
class ViewChannelView {
constructor(viewChannel, index, data, priority, autoDismissConfig) {
var _a;
this.autoDismiss = {
isPlaying: false,
duration: 0,
};
this._resolve = null;
this.isPresented = true;
this.viewChannel = viewChannel;
this.index = index;
this.data = data;
this.priority = priority;
this.result = new Promise((resolve) => {
this._resolve = resolve;
});
this._autoDismiss = new _AutoDismiss(this, autoDismissConfig);
this._autoDismiss._play(false);
this.autoDismiss.duration = (_a = autoDismissConfig === null || autoDismissConfig === void 0 ? void 0 : autoDismissConfig.duration) !== null && _a !== void 0 ? _a : 0;
}
dismiss(result) {
this.viewChannel.dismiss(this, result);
}
play() {
this._autoDismiss._play(true);
}
pause() {
this._autoDismiss._pause();
}
stop() {
this._autoDismiss._stop();
}
changeData(data) {
this.viewChannel.changeData(this, data);
}
}
class ViewChannelIndexOutOfBoundsError extends Error {
constructor(method) {
super(`${common$2} ${method} > "index" is out of bounds`);
this.name = 'ViewChannelIndexOutOfBoundsError';
}
}
class ViewChannelViewNotFoundError extends Error {
constructor(method) {
super(`${common$2} ${method} > "ViewChannelView" not found in views array`);
this.name = 'ViewChannelViewNotFoundError';
}
}
class ViewChannel {
constructor(config = {}, subscriber) {
this.views = [];
this._history = new _History();
this.history = this._history._events;
this._observer = new _Observer();
licenseChecker._checkLicense();
if (subscriber) {
this.subscribe(subscriber);
}
this.initialize(config);
}
initialize(config) {
this._history._events.length = 0;
this._history._setKeepHistoryFor(config.keepHistoryFor);
this._clearViews();
const event = {
type: 'INITIALIZED',
time: new Date(),
};
this._inform(event);
}
subscribe(subscriber) {
return this._observer._subscribe(subscriber);
}
unsubscribe(subscriber) {
this._observer._unsubscribe(subscriber);
}
unsubscribeAll() {
this._observer._clear();
}
present(viewConfig) {
const priority = viewConfig.priority ? viewConfig.priority : 0;
const priorityArray = Array.isArray(priority) ? priority : [priority];
const index = this._getIndexForPriority(priorityArray);
const view = new ViewChannelView(this, index, viewConfig.data, priorityArray, viewConfig.autoDismiss);
this.views.splice(index, 0, view);
this._repairIndexes();
const event = {
type: 'PRESENTED',
view,
index,
time: new Date(),
};
this._inform(event);
return view;
}
_doRemoveByIndex(index, result, reason) {
if (index < 0 || index >= this.views.length) {
throw new ViewChannelIndexOutOfBoundsError('dismissByIndex');
}
const view = this.views[index];
this.views.splice(index, 1);
this._repairIndexes();
view.isPresented = false;
view.autoDismiss.duration = 0;
view.autoDismiss.isPlaying = false;
view._resolve(result);
const event = {
type: 'DISMISSED',
view,
index,
reason,
time: new Date(),
};
this._inform(event);
}
dismissByIndex(index, result) {
this._doRemoveByIndex(index, result, 'USER_INTERACTION');
}
dismiss(view, result) {
if (!view.isPresented) {
return;
}
const index = this.views.indexOf(view);
if (index === -1) {
throw new ViewChannelViewNotFoundError('dismiss');
}
this.dismissByIndex(index, result);
}
dismissAll(result) {
if (this.views.length === 0) {
return;
}
const indexes = [];
const dismissedViews = [...this.views];
this._clearViews();
dismissedViews.forEach((view) => {